Constrained Creativity
Free-form agents can be difficult to verify in high-stakes workflows; Constrained Creativity gives them a domain-specific language of approved operations to compose into checkable workflows.
Read the paper ↗What it is
- Constrained creativity An agent chooses how to combine approved actions instead of writing arbitrary code or issuing unrestricted commands.
- Domain-specific language A small task-specific language names allowed operations and defines how they can be combined into a workflow.
- Specialized runtime A domain-focused executor carries out the workflow operations and handles their implementation details.
Why it matters
Fixed playbooks can be too rigid, while unrestricted generated code is harder to inspect. A task-specific action language preserves choice over the workflow while bounding what the agent can ask the system to do.
Before we get into the machinery, here is the short version: instead of asking an agent to write any code or issue any command, give it a small set of meaningful actions and let it decide how to combine them. A runtime executes those actions, while the allowed vocabulary keeps the plan inside a boundary people can inspect.
- Reliability: In high-stakes tasks, a capable agent still may not be reliable enough to run fully autonomously. Free-form coding can be costly and hard to verify.
- Flexibility: A fixed playbook is easier to trust, but only covers situations someone anticipated. The agent needs some freedom to respond to unfamiliar cases.
- Control: The design problem is choosing the boundary: enough options to handle the task, but not arbitrary actions.
So how do you give the agent meaningful options without handing it a blank programming canvas? Start with an abstraction: hide low-level details and keep the concepts that matter. The canonical example is network red-teaming. “Scan a network” and “infect a host” are useful choices for an agent; the runtime can handle the messy shell commands and track network state. The analogy is a remote control with labeled buttons rather than exposed wiring—but the analogy breaks because a real DSL can permit compositions, not just isolated button presses.
This abstraction is also a tradeoff. More constraints make behavior easier to understand and verify, but they can rule out actions the task needs. Too much freedom leads back toward arbitrary code. The aim is not maximum restriction; it is the least freedom that still lets the agent do the job. What turns that chosen abstraction into something the agent can actually use?
The answer is a domain-specific language, or DSL: a small language whose vocabulary consists of the task’s approved operations and whose rules say how they may fit together. Think of a troubleshooting DSL with operations for finding candidate causes, comparing a time window against a baseline, and ranking evidence. The agent still chooses the investigation, but it chooses from those building blocks instead of inventing analysis code from scratch. That is the key distinction from merely asking for valid JSON or SQL: correctly formatted output can still express the wrong operation.
The DSL can serve as both the agent’s planning language and the runtime’s execution plan. The model chooses operations; a specialized system carries them out, whether that means querying telemetry, running statistical analysis, processing events, or taking a controlled action. The runtime owns details that would otherwise distract the agent—and can enforce the DSL’s operation boundary. But does a DSL force every task into one rigid, hand-written sequence?
No: the operations can be composed into different workflow shapes. A sequence passes one result to the next step; a parallel workflow runs independent branches; an iterative workflow can revisit analysis; and a state machine makes allowed transitions explicit. The troubleshooting example might compare 10am with a baseline and then rank changes, while another investigation chooses a different allowed composition. The key is that the agent chooses the workflow without choosing arbitrary low-level actions.
I like this boundary because it separates “what should happen next?” from “how do I safely execute that operation?” But it is not magic: if the abstraction is too narrow, new cases do not fit; if it is too broad, checking the space becomes harder. So the real design question is not how to remove creativity, but how to give it a shape the runtime and its operators can govern.
Read the deep dive
Why People Wanted It
An agent can be capable and still be unreliable when it must act without supervision. In high-stakes work, the tension is real: a fixed playbook is easy to inspect but misses situations no one anticipated; arbitrary generated code can handle more cases, but is harder to verify. The problem is not simply whether an agent can reason. It is how much freedom its actions should have.
So the real question is: can you leave the agent room to choose a response without leaving it room to do anything at all?
The Core Move
Give the agent a task-specific abstraction, express it as a domain-specific language (DSL) of approved operations, and let the agent compose those operations into a workflow a specialized runtime can check and execute.
The essential boundary is: the agent chooses a program from a designed action space, rather than writing arbitrary code or issuing arbitrary low-level actions.
Why the Abstraction Sets the Freedom Boundary
Now the version for readers who want more of the machinery: the abstraction determines which distinctions the agent can express, so it sets both the useful flexibility and the limits of the DSL.
An abstraction hides implementation details while retaining the concepts that matter to the task. For network red-teaming, “scan a network” or “infect a host” is a more useful agent-level operation than a shell command: the runtime can own low-level execution and network state, while the agent chooses a task-relevant action. In incident analysis, the useful vocabulary instead concerns hypotheses, comparisons, and evidence ranking. A good abstraction is therefore not just a shorter command set; it selects the objects and actions that make sense for the domain.
The design tradeoff is structural. A narrow vocabulary is easier to understand and verify, but rules out workflows that need missing operations. A broad vocabulary can cover more cases, but makes the action space harder to inspect and control. The target is the least freedom sufficient for the task, with room to expand the abstraction when genuine requirements demand it.
Why a DSL Constrains Actions, Not Just Output Format
Here is the part that gets technical: a DSL constrains both the operations the agent may select and the legal ways it may compose them.
A schema can ensure that generated output is well-formed, but well-formed output can still request the wrong analysis. A DSL goes further: its operators represent domain-approved work, and its composition rules define what counts as a valid workflow. The agent can still decide which investigation to run, but each step must be expressible in that vocabulary and accepted by its rules.
That makes the DSL a language for reasoning as well as execution. The model plans in meaningful domain operations; a specialized runtime interprets those operations using the right underlying tools, such as queries, statistical analysis, or stateful event processing. The model need not be integrated with or jointly trained alongside a symbolic reasoner: the boundary comes from the action space and runtime. But what workflow shapes should that language allow?
Why Workflow Shape Determines What the Agent Can Express
Now for the deeper constraint: the same operation vocabulary can support different kinds of control flow, and each exposes a different kind of choice to the agent.
A sequential chain lets one result feed a later step. A parallel directed acyclic graph (DAG) can run independent operations as branches and combine their results. An iterative workflow can repeat analysis as intermediate results change what should happen next. A finite-state machine makes the agent’s progress explicit through states and permitted transitions. These are not merely different syntaxes: they determine whether the agent can order, branch, revisit, or transition among operations.
For a troubleshooting DSL, for example, a workflow might find candidate causes, compare a time window with a baseline, score the evidence, then rank the candidates. That chain is one possible composition, not a fixed playbook: another incident can call for a different permitted workflow. The runtime can execute the chosen structure, while the DSL’s rules bound the available steps and combinations. How much flexibility is enough before the workflow itself becomes difficult to check?
Minimal Causal Code
This toy runtime makes the boundary visible: the model proposes operation names and arguments, while Python dispatches only registered operations. It illustrates a sequential workflow, not a complete DSL parser or verifier.
def compare_windows(data, current, baseline):
return {
key: data[current].get(key, 0) - data[baseline].get(key, 0)
for key in set(data[current]) | set(data[baseline])
}
def rank_changes(changes):
return sorted(changes.items(), key=lambda item: abs(item[1]), reverse=True)
OPS = {
"compare_windows": compare_windows,
"rank_changes": rank_changes,
}
def run(workflow, context):
value = None
for operation, arguments in workflow:
if operation not in OPS:
raise ValueError(f"operation not allowed: {operation}")
inputs = {**context, **arguments}
if value is not None:
inputs["changes"] = value
value = OPS[operation](**inputs)
return value
workflow = [
("compare_windows", {"current": "10am", "baseline": "9am"}),
("rank_changes", {}),
]
The whitelist is the key causal boundary: the proposed workflow cannot dispatch an operation that the runtime has not registered. A production DSL also needs to define valid arguments, types, composition rules, and any domain-specific safety checks.
What You Gain, and What You Lose
You gain a smaller, more legible action space: the agent can choose and compose meaningful operations, while execution stays with a domain-specific runtime. That makes the workflow easier to inspect than arbitrary generated code and more adaptable than a fixed playbook.
You do not get reliability merely by calling something a DSL. The abstraction can omit an operation the task needs, or admit combinations that are legal syntactically but poor for the task. A constrained agent is less flexible than an unrestricted one by design; expanding its vocabulary restores capability but also expands what must be checked. The practical question is not whether to constrain, but where to draw the boundary between useful choice and uncontrolled action.
Give the agent room to choose the workflow, not an unrestricted license to invent its actions.