Orthogonality — 02: Agent Design as Basis Selection
A tool set is not a list of what an agent can do — it is a basis for its action space, and the useful question about a new tool is what direction it adds.
Suppose we take the previous idea seriously:
Architecture is a choice of coordinates for variation.
For an agent, one obvious place where those coordinates appear is the tool set. A tool defines a kind of action the model can take. So choosing tools is not merely choosing which APIs to expose.
It is choosing how the agent’s action space is represented.
Imagine building an agent for company research. It may need to:
get revenue
get margin
calculate revenue growth
calculate margin growth
compare revenue growth with peers
compare margin growth with peers
compare management guidance with actuals
identify unusual trends
...
The most direct architecture is almost embarrassingly simple. Make one tool for every task.
get_revenue()
get_margin()
get_revenue_growth()
get_margin_growth()
compare_revenue_growth_to_peers()
compare_margin_growth_to_peers()
...
This can work quite well. Each tool has a clear purpose. The model does very little orchestration. Individual actions can be tested and controlled. And, in a loose sense, the tool set spans the task space we already know about.
The problem is how it gets that span. By enumeration.
Every new task becomes a new point in the tool list. If you currently have
your architecture is approximately
The representation grows with the number of observed tasks.
But these tasks are not actually independent. get_revenue_growth and get_margin_growth differ in the metric, not in the structure of the operation. compare_revenue_growth_to_peers and compare_margin_growth_to_peers repeat even more of the same structure.
The tool set is describing points when it could be describing directions.
That suggests another representation. Instead of beginning from individual tasks, look for operations that recur across families of tasks. For example:
retrieve(entity, metric, period)
transform(operation, data)
compare(left, right, dimension)
Now:
Revenue growth
=
retrieve(company, revenue, t0)
+ retrieve(company, revenue, t1)
→ transform(growth)
while:
Peer revenue-growth comparison
=
retrieve(company, revenue, t0..t1)
+ retrieve(peers, revenue, t0..t1)
→ transform(growth)
→ compare(company, peers, revenue growth)
and:
Guidance vs. actual
=
retrieve(company, guidance, FY)
+ retrieve(company, revenue, FY)
→ compare(guidance, actual, revenue)
The exact taxonomy is not important. retrieve / transform / compare is not a universal answer. The change in representation is.
We moved from enumerating concrete tasks to representing the structure from which those tasks can be generated. This is the agent-design version of span. Not linear span, literally. Tool composition is not vector addition. But the analogy gives us a useful object to reason about: the generative span of a tool set.
A primitive is valuable when parameterization and composition allow it to cover an entire family of behaviors rather than one previously observed behavior. That is the real leverage of abstraction. One primitive represents a direction through the task space.
But broad span is not enough. We could compress the entire research agent into three tools:
research_company()
analyze_company()
evaluate_company()
The tool count is now wonderfully small. Unfortunately, almost nothing else improved.
Does research_company include calculating metrics? Does analyze_company retrieve missing information? Does evaluate_company perform analysis first?
Each tool can plausibly contain the behavior of the others. The representation is compressed, but still entangled.
Compression is not decomposition.
This matters because the model must choose among the tools. If two tools correspond to semantically distinct actions, tool selection has meaning. If they describe largely overlapping regions of behavior, the architecture has manufactured an ambiguity and handed it to the model as a reasoning problem.
This is where orthogonality returns. A good tool basis should not merely cover a large action space. Its primitives should add sufficiently different directions to that space.
If you already have a generic web-search capability, adding
search_company_web
search_finance_web
search_news_web
may improve convenience or control, but it does not necessarily add much new expressive structure. Adding access to structured financial statements is different. It introduces a genuinely different information-access mode.
The useful question is therefore not:
How many tools do we have?
It is:
What new direction does this tool add?
But this argument has an obvious dangerous conclusion. Why not make the tools perfectly distinct by pushing them all the way down?
http_get
parse_json
select_field
subtract
divide
sort
These operations are wonderfully primitive. They can compose into an enormous number of behaviors. They also turn a simple financial comparison into a small software project the model has to invent every time.
The basis may be expressive. The composition cost is terrible.
This is why tool granularity cannot be solved by saying “higher-level” or “lower-level.” A good abstraction sits one level above the concrete tasks we care about: high enough to capture reusable structure, but low enough that its semantic role remains distinct.
Fourier offers a useful reminder here. Sine and cosine are not powerful because they are the smallest operations from which a signal could theoretically be constructed. They are powerful because, for certain problems, they form a representation well aligned with the structure of the space.
Tool design has the same dependency. A research agent, a coding agent, and an execution agent should not be expected to share some universal atomic basis. Their useful action spaces have different structure.
So basis selection for agents has three competing demands.
The primitives should have enough generative span that the architecture does not degenerate into a dictionary of tasks. They should have low enough semantic overlap that choosing one action rather than another actually means something. And they should sit at a granularity where useful behavior can be composed without asking the model to reconstruct the application from atoms.
This also gives us a way to diagnose a common failure mode. Suppose every new agent failure produces a new specialized tool.
The local success rate may improve. But after enough iterations, the architecture becomes a record of historical failures:
failure
→ special tool
new failure
→ another special tool
new failure
→ another special tool
At some point the question should change. Perhaps the task truly requires a new capability. But perhaps the existing capabilities already span it, and the basis simply makes the composition unnatural.
That is not a missing-tool problem. It is a representation problem.
A good tool set does not enumerate what an agent can do. It gives the agent a coordinate system from which behavior can be composed.