Skip to main content
Wildo.ai Coming soon
← All posts

Building serious software

Why applications need an engine

Your next B2B application will need permissions, audit trails, emails, webhooks and translated interfaces. So did the last one. Yet you will spend part of the project wiring them together again.

A customer buys your product for what it does for their business. Meanwhile, your team is deciding how an audit event identifies a record, which language an email should use, and whether a permission check still holds when someone calls the API directly.

This work is necessary. Rebuilding its machinery for every product is expensive—and every new implementation is another place to get it wrong.

React, Express, Tailwind, Zod, WorkOS, Resend: we have excellent tools. What we keep rebuilding is the application knowledge that connects them. Which object is this? Who owns it? What does this action mean? What should happen when it succeeds?

That is the layer I wanted to build with Wildo: an engine that understands enough about the application to carry the common work, while leaving its specific business rules and experience to the product.

Three distinct business interfaces above shared engine machinery, connected through a business-model declaration.
A shared engine supplies recurring mechanisms. Each application supplies its own model, rules and experience. Conceptual illustration.

The repeated work contains something specific

The difficulty is that these apparently common features depend on the application they serve.

An audit log needs to know which business action happened, who performed it and which object changed. An email needs the right recipient, language and context. A permission check needs more than a logged-in user: it needs to understand the object, its organization and the action being attempted.

A business record connected to its owner, an action and a recipient, with a hand placing the ownership declaration.
Shared mechanisms need application context: which object, whose responsibility, what action and for whom.

Even a simple field can carry several decisions. Is it required? Who may change it? Is it a reference to another record? How should that record be named in a selector?

Those decisions often end up expressed separately in a database model, an API handler, a form, a notification and a policy check. Each implementation is reasonable on its own. The work is making them agree—and noticing when they stop agreeing.

The cost is more than coding time. Each team needs enough knowledge of authorization, delivery failures, audit evidence and interface behavior to make those connections correctly. Repeating the implementation also repeats the opportunity to miss something.

So we repeat the common mechanisms because they need product-specific information, and we have not given them a shared way to read it.

Useful parts still need a common model

The web ecosystem has made individual parts of the job dramatically easier.

React gives us components for interfaces. Express handles HTTP routing and middleware. Tailwind brings styling into composition, and shadcn/ui supplies component code we can own and adapt. Zod makes data contracts executable through schema validation and TypeScript inference.

These tools can work together. Zod schemas, for example, can already be shared between a server and a form. The remaining question is how far the shared meaning reaches: does it also describe ownership, action permissions, record references, audit treatment and presentation choices?

Specialized products take on substantial mechanisms too:

TechnologyWork it takes onDecisions the application still needs to express
WorkOSIdentity, organizations and access-management capabilitiesHow a person’s authority applies to the product’s objects and actions
ResendSending email through an APIWhich business event warrants a message, to whom, with what context
SvixWebhook delivery infrastructureWhat an event means, when to emit it and which application data it may expose
i18nextTranslation lookup, language handling and formattingWhich words describe each field, action and state in its business context
MintlifyPublishing product and API documentationThe behavior, rules and explanations the documentation must accurately describe

Those are useful divisions of responsibility. Buying delivery infrastructure can save a great deal of engineering. It still leaves the application responsible for saying what should be delivered and why.

Consider a declared “approve” action. Its HTTP route, permission check, interface label, audit event and notification all refer to the same business decision. Without a shared model, the connections live in application code that someone has to write and maintain. Adding another SDK does not establish that agreement by itself.

An approval action connected by tangled threads to separate API, permissions, audit and email modules, each carrying its own approval note.
One business action, several mechanisms to keep in agreement. The repeated work lies in explaining and maintaining those connections.

That is the distinction I care about: reusing a mechanism, and giving several mechanisms the same understanding of the application. Wildo builds on libraries and providers; its engine adds the domain declarations that connect their work.

Game engines offer a useful comparison

Game developers have been building on this kind of idea for years.

Unreal’s gameplay framework supplies concepts such as actors, controllers and game modes. Unity’s GameObjects and components give developers a common way to assemble objects and behavior. These are concepts from the domain, with an execution model behind them.

A creator shapes a game world above gameplay mechanisms; another shapes a business workspace above objects, relationships and actions.
The analogy is a shared domain foundation: the engine supplies mechanisms, while the creator shapes the game or application.

The engine does not invent the game. It provides structures and mechanisms that a particular game can build on and extend.

For B2B software, the corresponding vocabulary might include organizations, business objects, relationships, operations, permissions and events. The products can be very different while sharing much of the machinery around those concepts.

This is not an untouched field. Frameworks such as ABP already organize applications around domain objects and business rules, alongside reusable infrastructure. Low-code platforms and application generators explore other versions of the idea.

The interesting question is where to draw the boundary. How much can an engine understand without forcing every application into the same product?

Choose what the language should express

“Business software” covers an enormous range of needs. A system broad enough to describe anything can become as much work to learn and maintain as the code it replaces. A narrow system can be pleasant until the first important requirement does not fit.

My answer with Wildo is to concentrate on B2B SaaS and internal business applications, and give their recurring concepts an explicit language.

By language, I mean declarations that have defined consequences. A resource describes a type of business object. Its fields describe information. Its relationships describe connections to other objects. Its operations describe what people or services can do with it.

In Wildo, these declarations live in TypeScript: Zod-based field schemas, resource configurations, relationship declarations and UI behavior definitions. They are connected inputs, rather than one enormous schema expected to say everything.

A required field can contribute to both API validation and standard form behavior through shared field declarations. An operation can declare its inputs, allowed roles and resource scope. A relationship can distinguish a reference from ownership, which matters when the engine builds context or handles related records. The engine has something concrete to work with across layers.

Objects, relationships and actions in a shared declaration connected to data, APIs and forms.
An explicit business vocabulary gives several application layers the same model to work with.

It is tempting to call this a fourth- or fifth-generation language. I would rather be precise about what it does: it is a domain-specific declarative layer, backed by an application engine and extended with ordinary code. The useful test is how much repeated implementation it removes while preserving the decisions the product needs to make.

Configuration is a control panel for behavior

This is where configuration becomes more interesting than a collection of feature switches.

It lets an application choose how shared mechanisms should behave: which operations exist, their contracts and access rules, the information a standard view presents, the components used to present it, or the notifications an operation should trigger.

Those choices need a runtime behind them. Otherwise, we have only moved the repeated work into another file format.

In Wildo’s resource system, the declarations feed contract construction, resource services and API routing. The frontend uses field metadata and UI behavior definitions to build standard interactions. The connection is concrete in the mechanisms around an operation:

  • Audit emission uses the operation’s risk and evidence policy. High-impact writes meet the automatic audit floor; lower-risk writes can opt in. The shared path records the action and its subject rather than requiring a separate logging call in every ordinary handler.
  • Declared notifications associate an operation with recipients and channels. The engine resolves those recipients and dispatches configured messages after commit. The application still decides what deserves a message and supplies its content.
  • Outbound webhooks provide signed delivery, retries and delivery records for configured events. A product defines which events and data it exposes; it does not need a new delivery implementation for each business object.
  • Structured labels give resources and components a shared language tree to read. The interface resolves the words for a field or action from that structure, rather than inventing its own name wherever it appears.

The point is to keep the recurring mechanisms in the engine, while making their application-specific choices explicit.

Example: assigning responsibility

A task belongs to an organization and refers to a person responsible for it. An “assign lead” action accepts a new person and is restricted to administrators. The chosen person must also be eligible within that organization.

The operation contract, role rule and reference constraints describe different parts of that decision. A custom handler can add any further business rule. A standard selector can be replaced with a richer interface without redefining what the operation accepts.

That example has a concrete counterpart in Wonder Todos, our example application, explained in the custom-operation guide: its ASSIGN_LEAD operation declares an input contract, an administrator role requirement and constraints on the referenced user. Permission to assign someone and eligibility to receive the assignment are separate questions.

Declarations, configuration controls and custom interface and code modules connected to one application engine.
Declare the application model, configure shared behavior, and implement the parts specific to the product. These are complementary extension points.

Customization belongs in the design

An application engine earns its place when the product needs something unusual.

For Wildo, that means several ways to change behavior, each with a different responsibility:

What the product needsWhere it belongs
Different fields, relationships or operation contractsResource declarations
Different standard views, sections or presentation choicesUI behavior configuration
A different field control or visual componentComponent injection and presets
A business calculation, additional check or action-specific processingCustom operations and implementations
Hands fit custom interface and service modules into distinct matching connections on a shared engine.
Custom interfaces and service logic have their own extension points and contracts.

The frontend resolves registered components by their reference, preset and, where applicable, field category. Replacing a component means implementing its contract: inputs, callbacks and interaction behavior still matter. It is a deliberate extension point, not a promise that arbitrary markup will work everywhere.

On the backend, a custom implementation can run at a defined phase around a resource operation. This lets a product add its own logic without immediately taking over the entire service pipeline.

There is also a full override. That is a larger responsibility: replacing the pipeline means owning behavior that the normal path would have supplied, including the relevant persistence and audit work. Customization is useful when the boundary is clear, not when the framework hides what has been bypassed.

I want the ordinary case to be straightforward, and the unusual case to remain possible. Both are part of the engine’s design.

AI makes the question more urgent

Coding agents make it much easier to produce another implementation of the same mechanism. That does not remove the need to understand its consequences, connect it to the rest of the product and maintain it later.

An application engine changes what we ask the agent to build. Instead of assembling every supporting mechanism again, it can work with shared declarations, configure existing behavior and write the parts that are specific to the business.

A product designer and a coding agent build an application from a business workflow on an existing engine.
People define the business intent. Coding agents implement it using the shared foundation.

The application still needs judgment. Someone has to decide what an action means, what a user should experience and which rules must hold. An engine gives those decisions a place to live and mechanisms that can act on them.

That is what I want Wildo to make practical: spend more of the work on what makes the application useful, and less of it rebuilding the foundations that let it function.