Agent & Command Transitions – Planning and Executing Work in Agentic Nets
In my Petri-net based runtime (Agentic Nets), every agentic process is built from a small set of transition “building blocks”. Two of the most powerful ones are the Agent transition and the Command transition. Together, they form a pattern that appears again and again:
- Agent transitions – let an LLM or agent plan what should be done, based on tokens and prompts.
- Command transitions – execute that plan on remote executors in a controlled, observable way.
In this article, I’ll show how these two building blocks work together using a concrete example: the Grep Assistant Net, a tiny agentic process that turns a natural language code search request into safe grep/find commands, executes them, and returns results.
The Grep Assistant Net – from natural language to shell and back
The Grep Assistant Net contains four places and two transitions:
- Places:
- Examples – example tokens that show how tasks map to command tokens.
- Grep Tasks – incoming grep/search tasks in natural language.
- Commands – structured command tokens ready for execution.
- Responses – execution results (stdout, stderr, exit codes, etc.).
- Transitions:
- Plan Grep – an Agent transition that turns a task into one or more command tokens.
- Execute Grep – a Command transition that batches and runs those commands on a remote bash executor.
The flow looks like this:
Examples + Grep Tasks → Plan Grep (Agent) → Commands → Execute Grep (Command) → Responses
Let’s look at both building blocks in detail.
Agent transition: “Plan Grep”
An Agent transition is the place where the LLM/agent lives inside the Petri net. It takes tokens as structured input, applies a prompt, and produces new tokens as structured output – exactly like any other transition, just with an LLM behind it.
Here’s the inscription for the Plan Grep transition:
{
"id": "plan-grep",
"kind": "task",
"presets": {
"input": {
"placeId": "grep", // Grep Tasks
"host": "grep-assistant@localhost:8080",
"arcql": "FROM $ LIMIT 1",
"take": "FIRST",
"consume": true
},
"examples": {
"placeId": "examples", // Example mappings
"host": "grep-assistant@localhost:8080",
"arcql": "FROM $",
"take": "ALL",
"consume": false
}
},
"postsets": {
"commands": {
"placeId": "commands",
"host": "grep-assistant@localhost:8080"
}
},
"action": {
"type": "agent",
"nl": "You are a software development assistant that produces bash commands for code search tasks.\n\n\
## LEARNING FROM EXAMPLES\n\
Study the example tokens provided in the examples preset. Each example shows:\n\
- A task description (example_task)\n\
- The correct command token format (example_command_token)\n\n\
Your output MUST follow the EXACT same format as shown in the examples.\n\n\
## YOUR TASK\n\
The task is: ${input.task}\n\
Context: ${input.context}\n\n\
## CONSTRAINTS\n\
- All commands MUST operate within /Users/alexejsailer/Developer/AgenticOS directory only\n\
- Use grep, find, or other read-only search commands\n\
- No destructive operations (rm, mv, etc.)\n\n\
## OUTPUT FORMAT\n\
Produce command tokens in this EXACT format:\n\
{\"kind\": \"command\", \"id\": \"cmd-unique-id\", \"executor\": \"bash\", \"command\": \"exec\", \"args\": {\"command\": \"actual shell command here\"}, \"expect\": \"text\"}\n\n\
CRITICAL: The args field MUST be a JSON object with a command key.\n\n\
Output all command tokens to the commands postset.",
"modelId": "grep-assistant"
},
"emit": [
{
"to": "commands",
"from": "@response",
"when": "success"
}
],
"mode": "SINGLE"
}
Presets: giving the agent real, structured context
Two presets feed the agent:
input– a single token from Grep Tasks, containing the user’s request (input.task,input.context, etc.).examples– all tokens from Examples, showing how tasks and command tokens should look. They are not consumed, so the agent can reuse them for many runs.
In other words: the Agent transition doesn’t just see “a prompt”; it sees tokens – which you can create, inspect, and version like any other runtime data.
The nl field: the Agent’s “system prompt” as part of the net
The action.nl field is the agent’s system prompt. Here it encodes:
- Its role (“software development assistant that produces bash commands”).
- How to learn from examples stored as tokens.
- Hard constraints (read-only commands, directory restrictions, no destructive operations).
- The exact output schema for command tokens.
This is the core idea of the Agent building block: an Agent transition is not a hidden “magic LLM”; it is a transition with an explicit prompt and explicit schemas, defined alongside all other inscriptions.
Emit: turning LLM output into tokens
The Agent’s response is exposed as @response. The emit rule:
{"to": "commands", "from": "@response", "when": "success"}
takes the structured response (one or multiple command tokens) and drops them into the Commands place. The Agent building block is done: it has translated natural language into internal, executable tokens.
Command transition: “Execute Grep”
A Command transition is the counterpart to the Agent transition. While an Agent decides what should be done, a Command transition is responsible for actually doing it – but still in a controlled, token-based way.
It reads command tokens, groups and batches them, sends them to remote executors (via polling / channels), waits for results, and writes structured response tokens back into the net.
Here is the inscription for Execute Grep:
{
"id": "execute-grep",
"kind": "task",
"presets": {
"commands": {
"placeId": "commands",
"host": "grep-assistant@localhost:8080",
"arcql": "FROM $ WHERE $.kind==\"command\" LIMIT 10",
"take": "ALL",
"consume": true
}
},
"postsets": {
"results": {
"placeId": "responses",
"host": "grep-assistant@localhost:8080"
}
},
"action": {
"type": "command",
"inputPlace": "commands",
"groupBy": "executor",
"batching": {
"mode": "PER_EXECUTOR",
"maxBatchSize": 10
},
"dispatch": [
{
"executor": "bash",
"channel": "default"
}
],
"await": "ALL",
"timeoutMs": 60000
},
"emit": [
{
"to": "results",
"from": "@response.batchResults",
"when": "success"
}
],
"mode": "SINGLE"
}
Presets: selecting command tokens
The commands preset binds up to 10 command tokens from the Commands place:
arcql: "FROM $ WHERE $.kind==\"command\" LIMIT 10"– only tokens withkind == "command", up to a maximum of 10 per batch.take: "ALL"– bind all matching tokens as one batch.consume: true– remove them from Commands once they are handed to the executor.
Each of these tokens came from the Agent, and typically looks like:
{
"kind": "command",
"id": "cmd-some-id",
"executor": "bash",
"command": "exec",
"args": {
"command": "grep -R \"Agentic Nets\" src/"
},
"expect": "text"
}
Action: dispatching to remote executors
The action block describes how command tokens are mapped to remote executors:
"type": "command"– this is a Command transition."inputPlace": "commands"– the commands originate from the Commands place."groupBy": "executor"– commands are grouped by theirexecutorfield, so each executor (bash, python, docker, …) can receive its own batch."batching": { "mode": "PER_EXECUTOR", "maxBatchSize": 10 }– up to 10 commands per executor per batch."dispatch": [{ "executor": "bash", "channel": "default" }]– defines which executors exist and on which channel they listen (polling from the master)."await": "ALL"– wait for all dispatched commands to finish."timeoutMs": 60000– global timeout of 60 seconds for the batch.
Internally, remote executors can be separate processes or even remote nodes that poll for work, execute the commands, and send back results. The master node stays in charge of orchestration and state.
Emit: capturing batch results as tokens
When the batch has finished, the runtime exposes its results as @response.batchResults. The emit rule:
{"to": "results", "from": "@response.batchResults", "when": "success"}
drops all result tokens into the Responses place. Each token can carry fields like stdout, stderr, exitCode, duration, etc. From there, another transition could summarize results, send them back to a user, or store them in a log/metrics system.
Agent + Command as a reusable pattern
The interesting part is not the grep example itself, but the pattern behind it:
- Agent transition – takes high-level intent (tokens + prompt) and emits a concrete plan as command tokens.
- Command transition – takes that plan and executes it in the real world (shell, services, tools), while keeping everything tokenized and observable.
Once you have these two building blocks, grep is just one use case. The same pattern can drive:
- Static analysis – agent generates
lint/analyzercommands, command transition runs them. - Build steps – agent plans a sequence of build or test commands, command transition executes them in order.
- Infrastructure helpers – agent creates read-only inspection commands for Kubernetes, AWS CLIs, etc., command transition runs them on locked-down executors.
In all cases, the Agent transition is responsible for thinking and formatting; the Command transition is responsible for doing and reporting. The Petri net glues everything together, keeps the state as tokens, and makes the entire agentic process inspectable and replayable.
In upcoming posts I’ll combine Agent and Command with the other building blocks (Pass, Map, HTTP, etc.) to show larger nets that not only search code, but also refactor it, run tests, and coordinate human-in-the-loop approvals – all as explicit models instead of opaque “agent magic”.