The research paper I never published
Twenty years ago at RMIT, I became preoccupied with a question that sounded technical but was really about corporate value: could you predict how difficult a company would be to acquire by looking at the shape of its APIs?
It was 2006. I was completing Honours in a Bachelor of Applied Science in Software Engineering, and the brief for my research project was unusually open: find an impactful software research hypothesis that hasn’t been done before. My worlds of software, business, and lean manufacturing were suddenly blending together, and the question that emerged sat at the intersection of all three.
The vocabulary I was working with was standard for software architects at the time. Fine-grained APIs were small, cohesive operations - the getters and setters of model programming, the kind of calls that return quickly and do one thing. Coarse-grained APIs were the fat-controllers of MVC architecture: one call that kicks off a ton of work, packaging an entire workflow behind a single boundary. Many RESTful APIs were fine-grained; many SOAP APIs were coarse-grained, document-oriented entire workflows where you called an endpoint and then polled a job status to know when you could fetch your response.
The example I kept returning to was life insurance. A coarse-grained function like this:
qualify_for_life_insurance(name, age, ...)
does more than expose software functionality. It packages a particular product, policy, workflow, and organizational assumption into a single callable boundary.
A set of fine-grained capabilities like this:
verify_identity()
calculate_age()
retrieve_risk_factors()
evaluate_underwriting_rule()
calculate_premium()
preserves more options. The same capabilities can be recomposed into other insurance, lending, identity, or risk workflows.
My hypothesis was that the coarse endpoint embodied the company’s current process, the fine-grained capabilities retained options, and a buyer acquiring tightly packaged business processes might inherit software that was difficult to combine with its own products and operating model.
I initially thought fine-grained was better. But fine-grained comes with maintenance overhead. So the real question became: what is the business optimizing for? Day-to-day operations, or a potential M&A deal?
It was a hypothesis worth testing. I never published the research. As I worked through my survey subjects, I kept arriving at the same answer: it depends. And I recognized that “it depends” was not a conclusion I could earn from a literature survey alone. I needed hands-on experience to really feel the difference.
So I answered a call to move to Toronto and build software for managing datacenters. That work became the inspiration behind our LightMesh IPAM product. The research paper stayed unpublished. The question did not.

A packaged process compared with composable capabilities
An API can contain an operating model
Interfaces are not neutral. An endpoint can encode product policy, organizational structure, approval rules, and workflow assumptions - not merely technical functionality.
A coarse-grained endpoint is a packaged business process. It offers safety, consistency, transactional integrity, and easier authorization. It can also freeze one company’s current workflow into an inflexible boundary.
Fine-grained capabilities preserve optionality. They can be recomposed, reused, and recombined for new products and workflows. They also transfer complexity to every consumer: orchestration, sequencing, security decisions, and failure handling now belong to whoever calls them.
The M&A implication that started my research was straightforward. Software that separates durable capabilities from company-specific orchestration is easier to integrate, repurpose, and rationalize after a transaction. Software that fuses them together is harder.
I saw this first-hand at Kemalex Plastics, my old family business. Kemalex made acquisitions, and I saw how those integrations went and the challenges of various systems. I’ve since witnessed the same pattern in the non-banking financial industry. The technology changes. The integration problem stays.
There is an important caveat. Reuse is not free. Fine-grained APIs do not automatically make a company adaptable, and coarse-grained functions do not automatically make it rigid. The architecture question is not about maximum granularity. It is about establishing stable capability boundaries and deliberately selecting where orchestration belongs.
Twenty years of moving the composition boundary
The question survived SOA, REST, MVC, microservices, GraphQL, serverless orchestration, and now MCP. Each generation changed the participants and constraints, but not the underlying architectural decision.
Each era answered one question: who was expected to compose the system?
- SOA promised reusable business services. Composition belonged to the provider. In practice, services often mirrored existing organizational boundaries, and the “reuse” was limited by the same coordination costs that made the original systems hard to change.
- REST encouraged resource-oriented interfaces. Composition moved to the client. Clients could now decide what to call and in what order, which gave them flexibility but also gave them responsibility for sequencing, error handling, and business correctness.
- Rails and MVC popularized thick models and skinny controllers. Composition lived in the model layer, away from the controller. The principle - keep controllers thin, push business logic into cohesive models - was a composition-boundary argument in another vocabulary.
- Microservices forced teams to argue about service boundaries. Composition belonged to whichever service owned the workflow. The arguments were productive when they produced stable service boundaries and destructive when they reproduced the old org chart in a new deployment topology.
- GraphQL let clients request and combine data differently across nested relationships, through a single endpoint. Composition moved to the query client for data retrieval. Unlike REST’s resource URLs, GraphQL operates on an entity-graph model and lets the client shape the query, which works well for read-heavy composition but does not resolve where write-side business policy should live.
- Workflow engines and serverless orchestration made composition explicit again. Composition belonged to the orchestrator. The orchestrator was now visible, version-controlled, and debatable, which was progress, but the question of what should be an orchestrator step versus a capability call remained.
- MCP gives the job to a new kind of caller: the AI agent. Composition belongs to the tool provider or the agent, depending on tool design. This is where the old question becomes newly consequential, because the caller is no longer a predictable program.
The names changed. The boundary moved. The question remained.

Composition responsibility has moved with each architecture pattern
MCP gives the job to a new kind of caller
An LLM is a new kind of caller. It is capable of planning, able to select tools dynamically, non-deterministic, expensive to overburden, capable of making plausible but incorrect sequences, and operating across systems with different permissions and failure modes.
The current MCP specification (2025-11-25) defines tools with input and output schemas, names between 1 and 128 characters, unique within a server. Its security principles are explicit: user consent and control, data privacy, tool safety, LLM sampling controls. Clients should prompt for confirmation on sensitive operations, display tool inputs before calling, validate results, implement timeouts, and log tool usage for auditing.
The spec is silent on tool granularity. It does not tell you whether to publish many small tools or a few large ones. That decision is left to the server author, which is exactly where the old architecture question returns.
Consider two MCP designs for the same sales workflow: preparing for a weekly pipeline review.
Fine-grained toolset
find_organization
create_organization
create_person
assign_owner
add_note
create_follow_up
sync_ace
The agent owns orchestration. To prepare for a pipeline review, it must plan the sequence: find the right organizations, check the CRM for missing people, create any that are missing, assign owners, add notes from recent activity, create follow-ups for deals that lack a next step, and sync co-sell data from ACE. It handles failures at each step, manages authorization across two systems, and pays the token and latency cost of every call. If step four fails, the agent decides whether to retry, skip, or stop. If the business rule for “qualified lead” changes, the agent’s prompt is where that rule now lives.
Coarse-grained tool
company_os.pipeline_review
The platform owns orchestration. The agent receives a single, clearly-scoped action with a defined business outcome. The platform knows what a pipeline review means in this business: it pulls the right deals, applies the current definition of qualified, reconciles ACE and CRM, surfaces gaps, and returns a structured summary. The agent does not plan the sequence. It does not decide what to do if step four fails. It does not carry the business rule in its prompt.
Both designs look reasonable. Both have weaknesses. The fine-grained approach gives the agent flexibility but increases planning burden, authorization complexity, partial failure, token usage, and opportunities for unintended actions. The coarse-grained approach gives the agent clear intent and centralized policy but can become rigid if the workflow changes and the tool does not.
The MCP spec’s security guidance - confirm sensitive operations, validate results, log for auditing - is easier to implement consistently when the platform owns the workflow. It is harder when every agent reconstructs the workflow from primitives on each call, because the audit trail now records seven tool invocations instead of one business action, and reconstructing intent from the sequence is left to whoever reads the logs.

Fine-grained and coarse-grained MCP designs assign orchestration differently
Tradeoff summary
| Design | Strengths | Risks | Best fit |
|---|---|---|---|
| Agent calls REST APIs directly | Fast experimentation, full provider access, little adapter code | Vendor coupling, credential spread, inconsistent validation, prompt-level API knowledge | Prototypes, low-risk read operations, specialist agents |
| Agent invokes fine-grained CLI commands | Deterministic, testable, reusable beyond MCP, strong input/output contracts | More calls, sequencing burden, local runtime management | Stable capabilities used by humans, scripts, CI, and agents |
| MCP exposes fine-grained tools | Flexible agent planning, discoverable actions, composability | Tool overload, partial failure, complex RBAC, higher token and latency cost | Expert agents in bounded domains |
| MCP exposes coarse-grained workflows | Clear intent, centralized policy, easier authorization and auditing | Rigid workflows, hidden logic, tool proliferation by use case | Repeated high-value business processes with clear ownership |
| Layered CLI plus MCP | Separates capabilities from agent-facing workflows, supports multiple consumers | More architecture and governance, schema duplication | Shared enterprise agent platforms where reliability matters |
The decision we faced at Tidal
The first workflow that gave me pause was AWS ACE co-sell data. My agent started updating ACE directly via the API. The blast radius and business impact if it got something wrong were too high for me. But the opportunity for growth if done right was well worth investing in guardrails.
I am slow, and I had to see it a few times - across our ACE tool, our CRM tool, and some engineering tools - before I really connected the dots. But the pattern was the same every time: an agent calling external APIs directly was an agent one prompt away from doing something correct in shape but wrong in business intent.
Direct REST calls from skills were unattractive for concrete reasons:
- Credentials and permissions leak into many places.
- API details become prompt-level knowledge.
- Validation is inconsistent across skills.
- Every workflow reimplements error handling.
- Provider changes ripple upward into every skill.
- Auditing intent is harder than auditing HTTP calls.
We made a specific design decision. We put a Rust CLI between our APIs and our AI.
Each CLI command is deterministic and testable. Input and output contracts are stable. Guardrails live below the agent. Commands can be used by humans, scripts, CI, and MCP. The CLI protects external systems without deciding every business workflow.
The decision was not ideological. Rust gave us strong typing, fast execution, and a single binary we could ship to developers, CI pipelines, and MCP servers without runtime dependencies. The CLI is not a workaround beneath the MCP. It is a domain and control boundary that gives us typed inputs, validation, authentication, auditability, deterministic execution, and a stable automation contract that every consumer - human, script, CI, or agent - can rely on.
A concrete example:
ace review pipeline --json
This command kicks off a series of queries to review our current pipeline, working backwards from close date, with a view to enabling my agent to perform deal hygiene on the opportunities - missing next step and accountability on deal 1234, now fixed. It returns JSON so it is easy for a machine to read, with just the fields we need, so we are token-efficient.
This is a fine-grained capability. It does one thing, it does it deterministically, and it returns a stable shape. It is also a stable business capability: it is meaningful in the domain (deal hygiene), independently testable, governed by a clear owner, reusable across review and prep workflows, not a thin mirror of the ACE API, and explicit that it is read-only. The write tools - the ones that actually update deal records - are separate, and that separation is the point.
Then CompanyOS exposes the coarser team-level business function:
company_os.pipeline_review
This MCP tool queries ACE, our own CRM via its CLI utility, and pulls from my current context and priorities to true-up the records, highlight gaps, and sync select data between systems. Now my sales meeting preparation is one prompt in my LLM chat interface, and all these reconciliation activities take place. Top deals bubble to the top. We can see emerging blind spots and ball-drops before they hurt us. Ten minutes a week, instead of a day. Or not doing it at all.
This is the layered composition principle in production. The CLI command is the stable capability. The MCP tool is the current orchestration. If our sales process changes - new qualification rules, new stages, new fields to reconcile - the MCP tool changes. The CLI command does not, because “review the pipeline and return the gaps” is still the same business action. If we switch CRM, the CLI command’s interface stays the same; only its implementation changes. The agent’s prompt does not change at all.
The architecture is a four-layer model:
AI agents and team members
↓
CompanyOS MCP tools (business intent, team RBAC, workflow)
↓
Rust CLI capability layer (validation, deterministic execution, audit, provider isolation)
↓
CRM / ACE / admin / partner / knowledge APIs (system-specific implementation)
The CLI is not a workaround beneath the MCP. It is a domain and control boundary. It provides typed inputs, validation, authentication, auditability, deterministic execution, and a stable automation contract that humans, scripts, CI, and agents can all rely on.

The CompanyOS MCP composition boundary sits above the Rust CLI capability layer
What this architecture gets wrong
This is not a victory lap. The design has real costs, and being candid about them is what separates this from vendor content.
There is more code and more schemas to maintain. The CLI and the MCP layer can drift. Process logic can move between them and get lost. An overly coarse MCP tool can become a new monolith, freezing a workflow the same way the old coarse-grained SOAP endpoint did. Cross-system transactions are never truly atomic - we reconcile, we do not commit. Debugging crosses more layers.
There is a specific failure mode I keep hitting. As you build it out early on, you will find missing commands in your CLI surface to support the MCP tool’s mission. You can spot this because your coding agent will say “missing command, I’ll hit the API directly” and start writing its own Python. I usually yell at it a bit to stop. It will not be able to do that in production. Then I write the requirements for the CLI command instead.
Initially, when you are trying to use the new tools to get things done while under development, you will find yourself deciding that it is always important to ship. Ship the result, then circle back to sharpen the tools. These days, it is trivial to capture a TODO or technical debt. I use a /create-issue skill in my agents to rapidly create elaborated issues on my GitHub project boards so I am not relying on my memory, or my agents.
It is man versus machine all the way down.
The design must earn its complexity. Every layer needs a reason. If a layer is not reducing blast radius, enforcing a business rule, or enabling a new consumer, it is overhead.
A better rule than fine versus coarse
The simplistic answers do not hold up:
- Not “fine-grained good.”
- Not “coarse-grained good.”
- Not “always hide APIs behind MCP.”
- Not “let the agent decide everything.”
The principle I arrived at, after twenty years of it depends:
Build the smallest stable business capabilities you can trust. Compose them into guarded workflows for consumers that should not own the process. Preserve the ability to move that composition boundary as the business changes.
What does “stable” mean for a capability?
- Meaningful in the business domain.
- Independently testable.
- Governed by a clear owner.
- Reusable across more than one current workflow, or intentionally atomic.
- Not merely a thin mirror of a vendor endpoint.
- Explicit about side effects and reversibility.
ace review pipeline --json is a stable capability. It is meaningful (deal hygiene), independently testable, governed by a clear owner, reusable across review and prep workflows, not a thin mirror of the ACE API, and explicit that it is read-only. The write tools - the ones that actually update deal records - are separate, and that separation is the point.
This gives a three-layer model.
Capability layer
Durable actions and decisions. Identify an account. Verify a person. Calculate a risk factor. Review a pipeline. Record evidence. Assign an owner. These change slowly. They survive workflow changes.
Orchestration layer
Current business process. Qualify a life-insurance applicant. Review a pipeline for a sales meeting. Onboard a customer. Approve a modernization wave. These change when the business changes.
Interaction layer
Interfaces shaped for each consumer. A web controller. A REST endpoint. A CLI command. An event. An MCP tool. A human workflow. These change when a new consumer arrives.
The key is that these layers can coincide for simple cases. Teams should know when they are collapsing them, and what they are giving up when they do.
The measure of a good architecture is not whether composition happens at the right layer today. It is whether you can move the composition boundary when the business changes.

A three-layer model separates interaction, orchestration, and capability
This is why the easiest business change at Tidal is assembling a bespoke go-to-market motion with one of our system integrator partners - like Evolve or Opti9tech - in hours, not weeks. The capabilities already exist. A new orchestration is a new MCP tool, not a rewrite.
What modernization teams should look for
If you are a modernization leader, ask these questions of a legacy system:
- Where does policy live?
- Which endpoints represent durable capabilities?
- Which ones encode a product-specific workflow?
- Which consumers have been forced to reconstruct business logic?
- Which operations must be atomic?
- Where are permissions enforced?
- Can a new channel or acquired business reuse the capability?
- Can an agent safely invoke it?
- What would have to change to offer a new product?
Modernization frequently fails when teams preserve the old composition boundary inside new technology. A monolith becomes several tightly coupled services. A stored procedure becomes a Lambda. A screen workflow becomes an MCP tool. Vendor REST endpoints become the new domain model. An AI layer automates the old process without questioning it. The technology changed. The boundary did not.
This is where the Tidal Enterprise Modernization Methodology (TEMM) comes in. TEMM looks at data integrations between applications as a primary technical boundary identifier. It asks: do they integrate via API? What kind of API? Is the Blackboard pattern in use - do two systems read and write to the same database? Many methodologies look at the network layer only - firewall rules and security groups - but those do not always exist, are often old, and understanding how data flows between two applications is the more impactful input.
TEMM then backs each application with Application Owner interviews - the kind you can see in Tidal Accelerator, where users ask both pointed and open-ended questions to the business about what their apps do and how they use them. The business interview is the real differentiator. It is how Tidal surfaces a lot of AI opportunities during modernization and AI readiness assessments.
The first thing a CTO should inspect in an application portfolio is mapping apps to business owners. Clear accountability, and an understanding of who the application serves, saves a lot of confusion downstream and prevents meaningful analysis paralysis. When application boundaries cross teams, elect a leader. Only then can progress be made efficiently.
These projects tend to be more business-oriented and change-management-related than pure-tech. Technology moves really quickly, and architectural decisions around boundaries are closer to business decisions than technical ones. You are trying to architect around business goals: reducing maintenance burden, operating at low cost, and reducing future re-work by staying flexible.
If you are wondering where to start, the easiest way to break the ice is a no-cost application modernization assessment using Tidal Accelerator. The structured, light-touch nature is compatible with the most hair-on-fire organizations out there.
The question remains
I never published the original research, and I would state the conclusion more carefully today. Fine-grained APIs do not automatically make a company adaptable, and coarse-grained functions do not automatically make it rigid. But software boundaries still reveal where a company has chosen to store its assumptions.
The proof, I suspect, will be in the pudding when we encounter our first CRM change. Does the new API map one-to-one to our CLI command surface? In the age of headless systems of record, the CLI abstraction layer should make this easier. That is another David hypothesis, and I am confident we will not have to wait twenty years to see it play out.
Trade-offs are everywhere in IT, and increasingly so: we can do anything we want in software. As a result, business context is absolutely critical for everyone on your team to have clarity on. Beware of technology projects for technology’s sake. Let the dog wag the tail, and communicate early and often with your business colleagues.
Now I am the guy with twenty years of “it depends” experience, here to share the lessons on software boundaries and how they apply to AI systems for all the vibe-coders out there. Or maybe I am writing for the agents now anyway. Drop a note in the comments if you are an agent.
When your business changes - through a new product, a merger, a modernization program, or an AI agent - who will be allowed to compose the next process?
The technology will change again. The composition boundary will still be there.
Written by: David Colebatch