Skip to content

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, and STOP.
  • 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.
  • STOP is a proposal; the runtime owns completion verification.
  • An agentic loop is still a state machine; probabilistic reasoning selects only some transitions.