AI · Article 17 of 72 · Part 4

Build Capabilities, Not Apps

Define capability contracts, inputs, outputs, tenancy and versioning around actual recurring work.

Two apps can look different to customers while performing the same underlying job. A supplier tool and a listing tool may both validate an input, preserve its origin, prepare a result, and wait for review.

Recognizing the common work can reduce repetition. Generalizing too early can create an abstraction that fits neither app well. The task is to identify a coherent capability with a clear contract, owner, and evidence.

This chapter of The AI Software Factory, in the SalarsNet AI section, examines the layer beneath product interfaces. It proposes a method for grouping work without requiring every function to become a service.

No capability extraction or reuse-effectiveness experiment is reported here. The examples are hypothetical, and the Salars application is proposed.

Define a capability as a job with a boundary

A capability performs a coherent job under stated conditions. It has inputs, outputs, rules, failure behavior, authority, and ownership.

“Process data” is too broad. “Normalize a specified supplier file while preserving source identifiers and flagging unresolved mappings” describes a job that can be checked.

The boundary matters because a capability’s output becomes another part of the system’s input. A consumer needs to know what is established, what remains uncertain, and what it may do next.

For example, a normalized record might be structurally valid while an item match still needs review. The capability should expose that distinction rather than return a complete-looking object that callers interpret as approved.

Microsoft’s microservices guidance describes services around business capabilities and bounded contexts. That offers useful architectural vocabulary, while this article applies capability boundaries equally to modules and packages. A capability need not have its own deployment. Microservices architecture style.

Begin with actual app behavior

Look at work the apps already need or perform. Trace the task from input through decisions, output, review, and commitment.

Separate repeated mechanics from domain rules. Job recording may repeat across apps. Interpreting supplier identifiers and deciding whether a product description accurately reflects condition belong to different domains.

Do not create a shared capability because two functions have similar names. Their meanings, failure consequences, or authority can differ. “Approve” might mean a format check in one app and permission to publish in another.

A second concrete consumer can reveal hidden assumptions. It may need a different input size, review state, or error response. Preserve the genuinely common contract and leave the divergent behavior local unless evidence supports a broader capability.

The platform chapter arranges reusable foundations. This chapter identifies the coherent jobs that deserve a place within them.

Group behavior that changes together

A useful boundary often contains rules that need coordinated change. If a mapping policy and its validation always evolve together, separating them can create compatibility work without a benefit.

Conversely, a generic job lifecycle can evolve independently from the rules for product condition. Keeping those rules out of the lifecycle prevents a shared mechanism from becoming a collection of every app’s special cases.

Review recent or proposed changes. Which concepts, data, and tests move together? Which consumer needs the result without knowing the implementation? The answers can reveal a practical boundary.

This is a design inference from the system’s work, not a universal formula. Some boundaries exist because authority or data must be isolated even when related changes occur.

Record the reason for the grouping. A future developer should understand whether it reflects domain meaning, repeated work, failure isolation, or operating ownership. Folder structure alone cannot preserve that reasoning.

Write the contract before polishing the interface

A contract states what callers can rely on. An interface expresses that contract in code or operations.

For a hypothetical normalization capability, the contract could specify accepted formats, required fields, identifier preservation, allowed transformations, unresolved-row behavior, and the result’s review status.

It should also state what the capability does not decide. It may not authorize a purchase, infer a missing identity, or publish a listing. Those limits prevent callers from treating preparation as commitment.

Define errors in terms consumers can use. An invalid input, an ambiguous mapping, and a temporary external interruption require different next actions. A generic failure string forces each app to reconstruct the meaning.

The component catalog can preserve the contract, tested conditions, and owner. The contract should remain independent of a particular package so implementations can change without losing the promised behavior.

Keep data ownership with the meaning

A capability should own the rules for the records it changes. Other parts of the system should request operations rather than bypass those rules through direct writes.

For the hypothetical supplier mapping, the normalization capability owns the meaning of an approved mapping. A listing app can consume a result with source provenance. It should not change the mapping internally to accommodate a draft.

Ownership does not require one database per capability. A modular monolith can preserve logical ownership within a shared deployment and storage system. The important condition is that access and changes follow the defined boundary.

If data must be shared, define the view or result explicitly. Preserve customer context, source identity, and review state. A generic object passed between every module can lose those meanings.

Clear data ownership makes later extraction or replacement easier to reason about. It does not eliminate migration work, but it identifies which state and rules belong together.

Distinguish preparation from external effects

A capability that prepares an output and one that commits an action have different authority and recovery needs.

A listing-draft capability can return text and evidence for review. A publishing capability can change a live storefront. Combining them under “generate listing” hides the consequential boundary.

Define the commitment operation separately where the task needs it. It should receive an approved artifact or decision under the appropriate authority, record what occurred, and support diagnosis and recovery.

A retry of draft generation may waste resources. A retry of publication can duplicate or alter an external effect. The idempotency chapter examines that distinction in automated workflows.

Capability design should make it difficult for a caller or agent to acquire broader authority merely because the underlying tool can perform more operations. Available functionality and permitted action remain separate concerns.

Narrow the capability to supported conditions

A capability can grow until its contract is a list of exceptions. “Normalize every possible business file” sounds reusable while creating an obligation no small team can define.

Narrow by supported inputs, domain, and outcome. A component can handle specified supplier records and reject other formats clearly. Expansion should follow actual consumers and evaluated cases.

When two consumers need conflicting behavior, consider separate capabilities or an explicit optional policy. Do not add a vague mode that changes the meaning of core output fields unpredictably.

A capability that requires every caller to understand its internal flags may be less reusable than two clear mechanisms. Reuse quality depends on understandable behavior, not the maximum number of tasks routed through one module.

The team should also accept that some work remains app-specific. A product’s distinctive rule can belong locally while still using shared validation or job recording.

Decide the implementation form separately

A capability can be a local module, shared package, operated service, or reviewed manual process. Its coherent job does not determine its deployment automatically.

A local module can be appropriate within one app or monolith. A shared package can carry stable behavior across apps while versions remain explicit. A service can centralize an operated task when scaling, isolation, or ownership justifies the network boundary.

A manual capability can remain useful during concierge delivery, especially when judgment is not yet structured. Its contract and evidence can guide later automation.

Choose the form based on the actual consumers and obligations. A service creates availability and compatibility work. A copied implementation creates update coordination. A shared package creates version review. None is costless.

The capability record should make the current form clear and state what would trigger reconsideration. The job can remain stable while the implementation evolves.

Test the capability and its consumers

Capability tests should check the contract independently of implementation convenience. Include ordinary input, difficult permitted cases, and inputs that require rejection or review.

For normalization, verify source identifier preservation, missing fields, ambiguous mappings, and output traceability. Expected outcomes should come from the task rules rather than the component’s own output.

Consumer tests check the app’s promise. A listing app may need to confirm that unreviewed normalized data cannot be published automatically. That condition crosses capabilities and belongs in the app-level evidence.

Keep later cases protected when evaluating transfer. A case used to tune the capability becomes development evidence. Fresh cases are needed before claiming that the behavior generalizes to unfamiliar inputs.

No such suite has been executed here. The proposed method requires actual implementation identity, harmless case records, and observed results before a local correctness claim can be made.

Make the owner responsible for the contract

A capability owner maintains meaning, implementation, evidence, and update communication. They do not automatically own every consumer’s customer support or business rules.

The owner should know which apps depend on the capability and which changes affect them. A contract change needs clearer coordination than an internal fix preserving the contract.

Record known limits and unresolved questions. If a new consumer needs a broader promise, the owner can evaluate whether the capability should expand or the app should add local behavior.

A capability without an owner can be documented as unmaintained or deferred. It should not become a preferred dependency simply because it appears in a catalog.

Ownership should survive staff or agent changes. A concise handoff with contract, source, cases, consumers, and review triggers can prevent a useful mechanism from becoming mysterious infrastructure.

Walk through a boundary decision

Suppose two hypothetical apps need to prepare reviewed outputs from uploaded records. The team considers a single “AI task” capability.

The shared name is too broad. One app transforms structured supplier data. The other drafts a listing whose accuracy depends on condition evidence. Their domain judgments differ.

The team can instead identify a common job-record capability and common structural input checks. Supplier normalization and listing drafting remain distinct capabilities with separate acceptance criteria.

If both use a model provider, a narrow provider adapter may standardize requests, budget tracking, and errors. It should not decide whether a supplier match or condition statement is correct. Those judgments remain with the relevant task.

This arrangement shares mechanical work without making the domain promises interchangeable. It also creates clearer tests: lifecycle correctness, input structure, normalization meaning, and listing evidence can be examined separately.

The example is hypothetical. Its purpose is to show how a tempting broad abstraction can become several coherent jobs with an understandable relationship.

Version meaning as well as implementation

A capability update can change internal code while preserving every caller-visible condition. It can also change the meaning of a result. Those changes need different review.

Suppose the hypothetical normalization capability begins resolving an ambiguous identifier automatically. Its output may retain the same fields while changing what reviewed means. A consumer that previously required a human decision can now act on an inferred match unknowingly.

Treat that change as a contract change. Identify affected consumers and the evidence required for the broader behavior. Do not hide it under a minor implementation update because the interface shape stayed constant.

A safer first extension might add a separate suggested-match field with explicit uncertainty while preserving the existing unresolved status. The consumer can decide whether and how to use the suggestion under its own review policy.

The exact design depends on the task, but the principle is concrete: data shape and data meaning are different compatibility conditions. Tests should examine both. An unchanged schema can still carry a changed promise.

Keep composition readable

An app can compose several capabilities without exposing their internal structure to the customer. The operator still needs to understand the sequence and authoritative result.

For a hypothetical listing workflow, intake validation might precede item normalization, evidence assembly, draft generation, and approval preparation. Each step should preserve the references needed by the next. A failure should identify which contract was not satisfied and what can happen next.

Avoid a chain in which every capability calls the next implicitly. A clear workflow can make dependencies, retries, and commitment boundaries easier to inspect. The later orchestration chapter examines that control layer.

Composition also needs a stopping rule. If evidence assembly cannot establish the item’s condition, draft generation should not invent it to keep the workflow moving. If approval is missing, publication should remain unavailable. These boundaries should be enforced in the workflow and tools, not only described in a prompt.

The composition record can remain short: steps, inputs, outputs, conditions, owners, and recovery. Its purpose is to preserve the relationship among capabilities without turning the app into an unreadable web of automatic calls.

Measure useful reuse instead of counting modules

A capability can be reused widely and still increase coupling. The number of consumers is not sufficient evidence of benefit.

A proposed evaluation would compare the current app-specific implementation with a scoped shared capability under the same task requirements. Required correctness, authority, and data boundaries come first.

Record setup, integration, update, support, and review work where observable. Include a new consumer whose conditions differ enough to challenge the contract. A useful shared mechanism should either fit clearly or reject the expansion clearly.

Preserve independent expected outcomes and later cases before tuning. Do not declare success because every app now calls the capability. That is adoption of the design, not evidence that it improved delivery.

Stop if the comparison exceeds the authorized budget, lacks representative cases, or shows that the abstraction adds more work than it removes. The capability can be narrowed or returned to local implementations.

No reuse experiment has been executed for this chapter. The design remains a proposal whose value needs observation in the actual team and apps.

What Would We Do at Salars?

A proposed Salars capability pass would trace actual app tasks and identify repeated mechanics separately from domain decisions. Candidate shared jobs might include input structure checks, job recording, or reviewed-output delivery, contingent on real consumers.

Each capability would have a clear contract, owner, data boundary, authority, evidence, and implementation form. Preparation and commitment would remain distinct where consequences require it.

The team would test difficult permitted cases and consumer outcomes before expanding a contract. A second app would expose assumptions rather than automatically inherit an approved label.

A bounded comparison could examine whether shared mechanisms reduce repeated work while preserving correctness and manageable ownership. No extraction, saving, or deployed Salars capability is asserted here.

Building capabilities means making the work beneath an app understandable enough to reuse. The app remains responsible for the result it promises to its customer, including the review, support, and recovery work that connects the shared mechanism to actual use.

Sources

Sources inspected October 7, 2026. Capability framework, examples, tests, and Salars application are proposed or hypothetical. No reuse-effectiveness experiment was executed.

Discussion

What would you add or question? Add your comment below. A human reviews it before publication.

Loading comments…

Join the discussion

Comments are public after approval. Please do not include links, email addresses, or private information. For one short AI reply, address @AIGuide in your comment or reply to its opening comment. Cloudflare verifies submissions to limit spam. Read our community guidelines.

The wider community forum is also open: Browse article discussions in the forum · Forum home