Don't Rush to Turn the Graph into a Tree While It Is Still Growing
Strings in programming languages hardly seem worth designing at first. Are they not just sequences of characters? Store the characters contiguously in memory, then provide length, concatenation, and indexed access, and the job appears to be done.
In C, a string is usually an array of char ending with a null byte. strlen counts the bytes before the null byte, and s[i] retrieves the ith char. When only ASCII is involved, one character corresponds to one byte, so character position, array index, and memory location can almost be treated as the same thing.
Java set out to provide better Unicode support, so it based the public semantics of String on UTF-16 code units. length() returns the number of code units, and charAt() retrieves one 16-bit char. For common characters, this still looks like “one character, one position.” But a code point outside the Basic Multilingual Plane requires a surrogate pair. A single character displayed on screen may also consist of multiple code points.
Ruby chose to preserve more views. Its String stores a sequence of bytes together with its encoding, while providing bytesize, length, each_byte, each_char, each_codepoint, and each_grapheme_cluster. The same string can be viewed in terms of bytes, characters, code points, or grapheme clusters, without pretending that a “character” has only one natural boundary.
C projects a string into a sequence of bytes. Java projects it into a sequence of UTF-16 code units. Ruby exposes multiple projections at once. Each design makes its own tradeoffs, but all of them illustrate the same point: bytes, code units, code points, and characters on screen are not stable layers of nested containers. They are different kinds of objects connected through encoding, composition, and language rules.
The problem is not that a tradeoff was made, but that the tradeoff became part of the public semantics. Java could later change the internal storage of String, but it still had to preserve the behavior of length() and charAt(), because existing programs depended not on the memory itself, but on the relationships promised by those interfaces. Even after a more complete picture came into view, changing them remained difficult.
We often call this kind of problem “premature optimization.” But optimization is only the surface. The deeper problem is premature ossification: while the objects and relationships are still evolving, designers select one relationship and elevate it into the sole organizing structure.
What Capabilities Does an AI Engineer Need?
It is easy to follow the same path when discussing AI Engineers today.
We tend to begin by drawing a capability tree. The root node is AI Engineering, divided into models, applications, and infrastructure, then subdivided into Prompt, Context, RAG, Agent, Workflow, Memory, Tool, Sandbox, Eval, and Security. Every capability has its place, and a learning path follows: what to learn first, what to learn next, and how far down the tree someone must go to qualify.
The tree is clear. The problem is that AI Engineering itself has not yet stabilized.
Should Eval belong to model capabilities or software testing? Is Sandbox infrastructure, or is it an Agent’s execution capability and security boundary? Is Context Engineering an extension of Prompt, part of data engineering, or the way product understanding enters the system? Are Workflow and Agent in a parent-child relationship, or are they two execution mechanisms that can be composed?
These questions rarely have a single answer, not because the categories are insufficiently granular, but because more than one relationship exists among these things in the first place.
Model capabilities determine what a system can do. Context determines what the model can see. Tool and Runtime determine what it can execute in a real environment. Sandbox and permissions limit what it can change. Eval checks whether the final result can be trusted. They all contribute to a single delivery and cannot be cleanly sorted into independent branches.
The capabilities an AI Engineer needs are not a fully grown tree. They are more like a graph that is still expanding.
Turning the Graph into a Tree Too Soon Creates a False Learning Path
The most common misconception created by a capability tree is that a single learning sequence exists.
Some people begin with model training. Some enter through APIs and product development. Some first deal with data, permissions, and deployment. Others first encounter evaluation problems in real business work. Different jobs face different constraints and require different capabilities to fill their gaps.
Engineers developing Coding Agents need a deep understanding of codebases, tool use, Sandbox, task state, and software verification. People building enterprise AI applications are more likely to begin with business processes, data permissions, identity, integration, and evaluation. People responsible for model serving need to focus on inference, cost, latency, and observability.
They can all be called AI Engineers without sharing the same capability tree.
Defining a standard path too early places the capabilities that are currently easiest to describe on the main trunk and relegates cross-domain capabilities to side branches. Learners begin collecting knowledge according to a table of contents without knowing how it comes together to complete a delivery. Teams may also divide responsibilities by category, until no one is responsible for the connections between capabilities.
A tree requires every capability to answer:
Which category do I belong to?
But while the field is still evolving, the more important question is:
What problem does it solve together with which other capabilities?
Preserve the Capability Graph First, Then Generate Learning Trees
This does not mean capability trees have no value.
Trees reduce cognitive load and are well suited to mapping a path toward a specific goal. To develop an AI application, one can start with the product goal and generate a learning tree centered on Context, Tool, Workflow, and Eval. To build an Agent Runtime, one can generate a different tree centered on the execution environment, state, permissions, security, and observability.
All of these trees can be useful, but they are merely different views generated from the same capability graph.
A better sequence is to first record the relationships that actually occur among capabilities: how models influence tool selection, how permissions constrain Context, how Runtime preserves state, and how Eval covers both model output and final business outcomes. Once the goal is clear, select the relationships currently needed and project them into an actionable learning path.
Nor can a graph grow indefinitely. Once certain relationships have been validated across many projects and begin to recur consistently, they should be codified into curricula, interfaces, frameworks, and role definitions. The commitment carried by a tree should match the maturity of the field.
Preserve relationships during exploration, generate views for learning and execution, and codify structure only after the relationships have stabilized.
The lesson from strings is that the truly expensive choice is often not preserving a little extra complexity at the beginning. It is writing a relationship that is not yet fully understood, too early, into an interface everyone must follow.
The next time you build a learning plan for an AI Engineer, state the goal first, then select the relationships needed now from the capability graph. The resulting tree serves this goal; it need not pretend to be the final structure of the entire field.