Decision and Action Selection¶
Every loop iteration chooses what happens next¶
Given:
goal + current state + observations
the loop must decide:
What happens next?
That is the decision/action selection layer.
Basic mental model:
Goal
+ Canonical state
+ Relevant observations
+ Available capabilities
+ Policy
↓
Decision
↓
Validated action
↓
Execution
The model may help make the decision, but the runtime controls which action types exist and which are executable.
Structured next-action contract¶
Weak model output:
"I think we should look at the logs now and maybe restart the service afterwards."
It is unclear:
- which action should happen now,
- which tool to use,
- which arguments to pass,
- whether restart is only an idea or an execution request,
- whether approval is needed.
Better:
{
"action": "USE_TOOL",
"capability": "query_logs",
"arguments": {
"service": "payment-service",
"window_minutes": 15
},
"purpose": "Check whether the error spike is concentrated in retry failures."
}
The runtime receives exactly one next step.
Action taxonomy¶
A small explicit action set can help.
For example:
USE_TOOL
USE_SKILL
ASK_HUMAN
WAIT
REPLAN
STOP_SUCCESS
STOP_PARTIAL
STOP_BLOCKED
Another system may use:
ACT
ASK
WAIT
STOP
The exact enum is not important. The principle is:
Model output should be an explicit runtime decision contract, not prose control flow.
USE_TOOL¶
Example:
{
"action": "USE_TOOL",
"tool": "read_file",
"arguments": {
"path": "src/payment/RetryService.java"
}
}
Runtime:
validate action schema
↓
check tool is available
↓
authorize scope
↓
execute
↓
store observation
USE_SKILL¶
If a reusable higher-level capability exists:
{
"action": "USE_SKILL",
"skill": "analyze_test_failure",
"input": {
"test": "PaymentRetryTest"
}
}
The skill itself may use multiple steps internally, but from the outer loop it is one capability.
ASK_HUMAN¶
The model may detect missing information or a policy-required human decision.
{
"action": "ASK_HUMAN",
"question": "May I modify the public REST contract to resolve this incompatibility?",
"reason": "The current fix would introduce a breaking API change."
}
Runtime state:
RUNNING
↓
WAITING_FOR_HUMAN
There is no need to keep asking the model again in a busy loop.
WAIT¶
For long-running execution, waiting can be a legitimate action:
WAIT until build completes
Prefer event/scheduler-driven waiting:
WAITING_FOR_EVENT
↓
build_completed event
↓
resume
over:
poll every second forever
REPLAN¶
If the original plan no longer works:
{
"action": "REPLAN",
"reason": "Staging environment is unavailable",
"constraints": [
"production write access remains forbidden"
]
}
Replanning does not mean rewriting the top-level goal or policy.
STOP¶
The model may propose:
{
"action": "STOP_SUCCESS",
"evidence": [
"PaymentRetryTest passed",
"RetryRegressionSuite passed"
]
}
But:
model proposes stop
↓
runtime completion checks
↓
accept / reject stop
One action per iteration¶
It is often simpler if the model chooses one logical next action per iteration.
Weak:
{
"actions": [
"delete old deployment",
"deploy new version",
"restart database",
"send email"
]
}
The environment may change between these operations.
Prefer:
select action
↓
execute
↓
observe
↓
select next action
An exception can be safe, prevalidated batches of parallel reads.
Parallel action selection¶
Independent read-only observations can be parallelized.
For an incident:
query metrics ─┐
query logs ─┼→ combine observations
read deploy ─┘
This requires explicit dependency analysis.
Parallel mutation is much riskier.
Deterministic routing vs model decision¶
Not every next-action choice needs an LLM.
Deterministic¶
if input invalid → STOP_INVALID_INPUT
if approval missing → ASK/WAIT
if max_iterations reached → STOP_BUDGET
if required test not run → run test
Model-driven¶
Which source file is most relevant next?
Which diagnostic query best distinguishes hypothesis A from B?
Which skill fits this ambiguous request?
Hybrid¶
Runtime narrows allowed actions
↓
Model selects among safe candidates
↓
Runtime validates selection
This is often a strong pattern.
Capability filtering before model calls¶
Do not expose actions the model is not allowed to use.
For a read-only review agent:
Available:
- read_file
- fetch_diff
- read_test_result
Not exposed:
- merge_pr
- push_commit
- delete_branch
This is stronger than exposing every tool and saying:
"Please don't use dangerous tools."
Keep decision context minimal¶
To choose the next action, the model usually needs:
current goal
current progress
relevant observations
available capabilities
active constraints
remaining budget
It does not necessarily need the full historical transcript.
Reason fields are useful but not authoritative¶
A structured decision can include a short reason:
{
"action": "USE_TOOL",
"tool": "read_recent_deployments",
"reason": "The error spike started one minute after a release."
}
This helps debugging and observability.
But runtime security decisions must not rely on model reasoning such as:
"I really need production shell access"
That is not authorization proof.
Preconditions¶
An action can have explicit preconditions.
For example:
restart_service
may require:
- environment == staging
- current service state freshly observed
- no active deployment
- user has restart permission
The runtime enforces them.
The model can only propose:
{
"action": "USE_TOOL",
"tool": "restart_service",
"arguments": {
"environment": "staging",
"service": "payment-service"
}
}
Postconditions¶
Actions may need verification afterwards.
restart_service
↓
operation accepted
↓
observe service health
↓
verify desired state
Tool-call success is not necessarily goal success.
Idempotency awareness¶
If a write action returns an ambiguous result:
create_refund → timeout
the next decision should not automatically be the same write again.
Runtime metadata such as:
action_id
idempotency_key
execution_status
should manage transactional concerns.
The model does not need to own these details.
Decision confidence¶
A field such as:
{
"action": "USE_TOOL",
"tool": "read_deployment",
"confidence": 0.62
}
may be useful, but numeric confidence is not automatically an objective probability.
It can be a routing signal, not a security gate.
Often more useful uncertainty representation is:
known evidence
missing evidence
competing hypotheses
Ambiguity handling¶
Suppose the user says:
"Delete the old release."
If several releases could be “old”, then:
ASK_HUMAN
is better than model guess + deletion.
A useful policy:
ambiguous + high-impact action
→ clarify
Example: incident loop¶
State:
Goal: identify checkout error cause
Observation: error spike at 14:03
Observation: deployment at 14:02
Model decision:
{
"action": "USE_TOOL",
"tool": "query_logs",
"arguments": {
"service": "payment-service",
"from": "14:02",
"to": "14:10"
},
"purpose": "Determine the dominant error signature after the deployment."
}
The runtime validates read permission, executes, stores the observation, and starts a new iteration.
Example: coding loop¶
State:
Goal: fix duplicate charge retry bug
Patch applied
Target test not yet run
No model call is necessarily required here.
A deterministic controller can do:
patch changed
AND required test pending
→ run required test
The model may be needed only if the test fails and diagnosis becomes ambiguous.
This shows that an agentic loop can contain many deterministic transitions.
State-machine interpretation¶
A decision is effectively a state-transition proposal:
Current state S
↓
decision(action)
↓
validated transition
↓
New state S'
This is very close to ordinary software engineering.
Anti-pattern: free-form command execution¶
Weak:
Model output:
"Run: rm -rf ..."
Runtime:
shell(model_text)
There is no typed action boundary.
Better:
Model chooses allowed capability
↓
structured args
↓
validation/authorization
↓
execution adapter
Anti-pattern: LLM routing on every iteration¶
If state already tells us:
required test pending
there is no reason to spend tokens and latency asking the model whether to run it.
Agentic does not mean every transition is probabilistic.
Anti-pattern: hidden multi-step action¶
A tool such as:
do_everything(command: string)
makes authorization, audit, retry, approval, and observation harder.
Prefer explicit task-level capabilities.
Anti-pattern: accepting STOP without verification¶
Instead of:
Model: STOP_SUCCESS
Runtime: done
use:
STOP proposal
↓
completion contract check
↓
verified completion
Takeaways¶
- The decision layer selects the next explicit action from the current goal/state/observations.
- Model output should be a structured action contract, not prose control flow.
- Useful actions include
USE_TOOL,USE_SKILL,ASK_HUMAN,WAIT,REPLAN, andSTOP. - Do not delegate deterministic transitions to an LLM unnecessarily.
- A strong pattern is: runtime filters candidates → model selects → runtime validates.
- Expose only capabilities the model can actually use.
- For ambiguous high-impact actions, clarification or approval is better than guessing.
- Actions may need precondition checks before execution and postcondition checks afterwards.
STOPis a proposal; the runtime owns completion verification.- An agentic loop is still a state machine; probabilistic reasoning selects only some transitions.