How to use modularity effectively when building a thunk
A thunk can be built as one large workflow, with every instruction, tool and connection in one place. It works better as a set of smaller modules, each with one job and a clear boundary. This article explains why, and when to use each kind of module. For what each mechanism is and how to set it up, see Building thunks from reusable parts.
This is guidance, not a set of rules. A quick prototype can skip most of it. A thunk that will run in production, change over time, or be shared with other people benefits from all of it.
Why build in modules
Smaller units are easier to understand. A step that does one thing, or a tool with a name, inputs and an output, can be read and reasoned about on its own. Someone new to the thunk can follow the workflow one module at a time.
One module can change without touching the rest. When the parts of a thunk depend on each other only through their boundaries (a step's inputs and outputs, a tool's name and arguments), you can rewrite or replace one part and leave the rest alone. Moving from a test system to the real one, or from one vendor to another, becomes a change in one place.
Modules make the thunk more reliable. Each module can be tested by itself, so a failure points at one part instead of the whole workflow. AI agents are also more reliable when the work they are given is smaller and the content they read is limited to what the task needs. A step that does one job with a few tools makes fewer mistakes than a step that does five jobs with thirty.
The rest of this article covers three places where modularity pays off:
Putting each external system behind a module.
Dividing the work inside a thunk into steps and scoped tools.
Using interchangeable modules when the same workflow serves many kinds of work items.
Put each external system behind a module
Whenever the workflow reads from or changes an outside system (a CRM, a ticketing system, a database, a payments service), put that system behind a module: a set of tools with a fixed contract. The workflow calls the module's tools and never talks to the system directly.
Use a separate thunk for the module
Build the module as its own MCP server thunk: a thunk that has tools but no workflow. The workflow thunk reaches it through a plug-in connection, which names the tools the workflow needs without naming who provides them. See Reuse thunk interfaces and plug-in connections.
Keeping the module in a separate thunk means:
It can be built, tested and changed by a different person than the workflow.
Several workflow thunks can share it.
A mock of the system, with test records you control, can stand in for the real system while you build and test. Switching to the real system is a change to the plug-in connection's binding, not to the workflow.
Tools inside the workflow thunk itself are fine for a prototype, or for a system that only one thunk will ever use. Move them into their own thunk when a second thunk needs them, or when you want to test against a mock.
Give the module an interface from the start
Describe the module's tools as an interface: the names, inputs and outputs every implementation must provide. Do this when you build the first version, not when a second version appears. In practice there is almost always a second version early on: the mock you test against.
Declare an output for every tool whose result the workflow reads. Without one, a different implementation can match the interface and still return different fields.
Shape the interface around the business, not the vendor
Name the tools for what the workflow needs to do (find_customer, get_open_cases, issue_refund), with inputs and outputs in the workflow's terms. Don't copy the vendor's API endpoints one for one.
A business-level interface keeps the workflow independent of the system behind it. When the real system's API looks different, implement the interface with a thin adapter: an MCP server thunk whose tools call the real system and translate its answers into the interface's shape. The workflow doesn't change.
Base the fields, ids and error answers on what the real system returns (an unknown id, a refund larger than the charge), so that the mock behaves like the real system and the adapter stays thin.
Split by capability, and keep writes separate
Make each module about one business capability ("customer lookup", "case updates") rather than one module for everything a large system can do. Keep the tools that change records apart from the tools that only read them. Then each step can be given only the tools it needs, and a step that should only look things up can't change anything.
A write that must follow a rule, such as a refund whose amount is computed from a policy, belongs in a Custom Code Tool that checks the rule before it calls the write tool, not in step directions the agent might misread.
Example: a CRM behind a plug-in connection
The CRM example in Reuse thunk interfaces and plug-in connections shows the whole pattern:
Define an interface for the CRM tools the workflow needs, such as
find_accountandget_account, each with inputs and an output.Build an MCP server thunk from the interface. Its tools start with the right names and inputs, and you write their implementations, against a mock spreadsheet or the real CRM.
On the workflow thunk, add a plug-in connection named
CRMthat requires the interface, and bind it to that server. The workflow's agent callsfind_account_crmandget_account_crm.Later, bind
CRMto a different implementation (the production CRM, or an adapter in front of a third-party server). The steps and their directions stay the same.
Divide the work inside a thunk
Steps are parts of the process
A step is a meaningful part of the business workflow: something a person who knows the process would recognize, such as "check the claim against policy" or "draft the reply". Each step has its own directions, its own inputs and outputs on the work item, and it can have its own review gate. The Workflow Plan is the list of these steps, and it should read like the standard operating procedure it automates.
A step is also limited by practical constraints. By default, each run of a step's AI agent gets about five minutes of active working time, counting the AI model's work and the tool calls it makes. When it runs out, the agent is asked to wrap up its work. A step whose work regularly takes longer, or that reads more content than fits comfortably in one task, should be split into smaller steps, each still a meaningful part of the process. Splitting along the lines of the process (gather the facts, decide, act) usually works better than splitting at arbitrary points.
Tools are what the agent works with
A tool is something the agent is given so it can do its work: look something up, compute a value, send a message. A tool by itself is not part of the process. The same tool can be used by several steps, and a step's directions say what to do, not how each tool works inside.
Scope the tools in two ways:
Give each step only the tools it needs. Turn off the tools and connections a step doesn't use, and use conditional tool availability when a tool should be offered only for some work items. A smaller set of tools means fewer wrong choices.
Package a piece of work as a tool so the agent's context stays small. When a step needs a multi-part task that has its own instructions and reference material, such as researching a supplier or checking a document against a checklist, build it as a Custom AI Tool. The tool runs its own AI agent with its own instructions and tools, and the step's agent sees only the result.
Choose the kind of tool
Use | When |
Custom AI Tool | The work needs judgment, has its own instructions, or reads a lot of content the step's agent doesn't need to see. |
Custom Code Tool | The work follows fixed rules: a calculation, a validation, a write that must obey a policy, or calls to other tools in a set order. Code gives the same answer every time and uses no AI unless it calls an AI tool. |
Tool Wrapper | An existing tool, often a connection's tool, is too general. A wrapper fixes some of its arguments and gives it a name and inputs for one job. |
Use interchangeable modules for workflows that vary by work item
Some workflows are the same in outline for every work item but differ in the details. A customer support workflow always identifies the person, understands the issue, decides what to do and does it. But a marketplace that serves customers, couriers and merchants has different policies, different records and different actions for each of them. Writing every variant into one set of step directions makes the steps long, hard to change, and less reliable.
Instead, keep the workflow generic and put what varies in modules that are selected per work item. All the modules implement the same interface, and the workflow picks one for each work item.
What a module can supply
The variation can be any combination of three things:
Instructions. Treat a policy as dynamic instructions, not as data: the rules for deciding what to do, which belong to the module and change with the kind of work item. The module applies its policy and returns a decision, or returns the policy guidance that applies to the case in front of it. The step's directions stay generic: "evaluate the claim with the policy tool and follow its decision."
Data. Each module can read its own records and reference material, such as a spreadsheet of rates and limits kept in that module's content folder.
Actions. Each module can carry out the decision in its own way: a refund for one kind of user, a payout adjustment for another.
How a module is selected
A plug-in connection can have binding cases as well as its default binding. Each case pairs a value with a connection that implements the same interface. Add cases in the plug-in connection's Binding section.
On the step that uses the plug-in, set Bind by Column Value in the step's AI Tools section to the work-item column that holds the selecting value. When the step runs, the work item's value in that column picks the case. Values are compared without regard to capitals or surrounding spaces. A value that matches no case uses the default binding.
Choose the selecting value in an earlier step and record it on the work item. For example, an intake step classifies the person as a customer, courier or merchant and writes it to a User type column, and the later steps bind by that column. The choice is then visible on the work item, can be reviewed and corrected, and stays the same for every step that follows.
Decide what happens when no case matches. A default binding that handles the general case lets the workflow continue. A plug-in with no default offers no tools for an unmatched value, so the step runs without the module and can't do that part of its work. Pick the behavior that is safer for your process.
When to use interchangeable modules
This is the advanced route, for applications that need to scale: many variants, variants added over time, or variants owned by different teams. Each variant is built and tested on its own, and a new variant is a new module and a new binding case, with no change to the workflow.
For more local control of behavior, simpler mechanisms are usually enough:
A tool argument, when a tool behaves slightly differently for a few kinds of work items.
Conditional tool availability, when some work items should get an extra tool.
A step run condition, when a step applies only to some work items. See Control when a step runs and proceeds.
Test each module
Modules are only as useful as the confidence you have in them. Test them at each level:
Each tool. Give every module tool its own test cases, including the requests it should refuse. See Automated tests for custom tools.
Each implementation of an interface. Run the same test cases against every implementation, the mock and the real system, or every binding case, so you know they behave the same way where they should.
The workflow. Run the workflow's test work items with its plug-in connections bound to mocks, so the workflow is tested without changing real records. See Automated tests for workflow steps and Test plan design.
A module's tests are its contract with everyone who uses it. Run them again after every change.
What modularity costs
The benefits of a modular design far outweigh its costs, but it helps to know what they are:
Time and AI cost. A Custom AI Tool runs its own AI agent, so each call adds time and AI usage. Use code tools where the work doesn't need judgment. Tool call history and performance shows what each AI tool call cost and how long it took.
Following a run across thunks. When a workflow calls tools in another thunk, part of the story of a work item is in that thunk's tool call history. Look there when a module's answer is unexpected.
Access. Using another thunk's tools requires access to that thunk, so the people who build the workflow need to be given access to the module thunks it uses.
Shared code. Each Custom Code Tool holds its own code. Helper code that several code tools need is copied into each of them today, so keep such helpers short.
