Skip to content

Observation and Environment State

Observation: the loop's connection to reality

An agentic loop does not respond only to its own previous text. It needs fresh data from the environment.

We can call this an observation.

Examples:

  • tool result,
  • API response,
  • file content,
  • test output,
  • database query result,
  • deployment state,
  • metrics/logs,
  • user response,
  • approval/rejection,
  • external event.

Mental model:

Environment
    ↓
Observation
    ↓
Runtime state
    ↓
Model context
    ↓
Next decision

The key rule is:

A model's internal assumption is not environment state.

Current state vs model assumption

A coding agent may previously have observed:

PaymentRetryTest = failing

Then it modifies code.

It must not assume:

"It probably passes now."

It needs a new observation:

run_test(PaymentRetryTest)
        ↓
exit_code = 0

This is the essence of closed-loop behavior.

Authoritative state

Not every piece of context has the same authority.

For a deployment:

User says: production runs v7.4.1
Old memory says: production runs v7.3.9
Deployment API says: production runs v7.4.2

If the question is current production state, the deployment API can be the canonical source.

Useful categories:

Authoritative current state
Derived state
Historical observation
User-reported state
Model hypothesis

Do not merge them into one undifferentiated truth set.

Observation envelope

In a production runtime, it can be useful to store observations with metadata.

For example:

{
  "type": "DEPLOYMENT_STATE",
  "source": "kubernetes-adapter",
  "observed_at": "2026-08-30T14:40:00Z",
  "scope": "production/payment-service",
  "status": "SUCCESS",
  "data": {
    "version": "7.4.2",
    "ready_replicas": 4,
    "desired_replicas": 4
  }
}

This is stronger than storing only:

"Everything looks healthy."

because it preserves:

  • source,
  • timestamp,
  • scope,
  • structured payload,
  • error state.

Stale observations

An observation can become outdated.

For example:

14:00 deployment healthy
14:05 new rollout begins
14:06 agent wants to restart one pod based on 14:00 state

The 14:00 observation may already be stale.

Sensitive actions may require refresh:

sensitive action
      ↓
refresh relevant state
      ↓
validate preconditions
      ↓
execute

Observe-before-act

This pattern is especially important before mutations.

For example:

Goal: stop failed staging deployment

Weak:

model remembers deployment is still running
↓
cancel_deployment(id)

Better:

get_deployment(id)
↓
status == RUNNING ?
↓
request cancel

This reduces stale-assumption and race-condition risk.

Time-of-check vs time-of-use

Classic TOCTOU problems also apply to agentic systems.

observe balance = 100 EUR
        ↓
other process changes balance
        ↓
agent acts based on old balance

Where needed, the tool/domain service must enforce the real invariant atomically.

A model observation does not replace a transaction boundary.

Observation normalization

External tool output is often large or provider-specific.

Instead of a full Kubernetes API response, the model may need only:

{
  "deployment": "payment-service",
  "version": "7.4.2",
  "ready": 3,
  "desired": 4,
  "conditions": [
    "Progressing=True",
    "Available=False"
  ]
}

Architecture:

External system
      ↓
Adapter
      ↓
Normalized observation
      ↓
Agent runtime

Benefits:

  • fewer tokens,
  • less coupling,
  • easier evaluation,
  • easier security filtering,
  • more stable context.

Raw data vs interpreted observation

It can be useful to keep three layers separate:

Raw evidence
     ↓
Normalized observation
     ↓
Model interpretation

For example, raw log line:

ERROR duplicate key value violates unique constraint payment_id_key

Normalized observation:

{
  "category": "DATABASE_CONSTRAINT_ERROR",
  "resource": "payment",
  "constraint": "payment_id_key"
}

Model hypothesis:

A retry may be attempting to insert the same payment twice.

The hypothesis is not the same as the observation.

Separate facts from inferences

This matters especially for incident diagnosis.

For example:

FACT:
Error rate increased from 0.2% to 12% at 14:03.

FACT:
Deployment v7.4.2 completed at 14:02.

HYPOTHESIS:
v7.4.2 caused the regression.

Explicit separation makes the system more auditable.

Missing observations

If required data is unavailable:

query_metrics → timeout

do not create fake state.

Use an explicit observation status:

{
  "type": "ERROR_RATE",
  "status": "UNAVAILABLE",
  "reason": "METRICS_TIMEOUT"
}

The next decision can be:

RETRY
USE_ALTERNATIVE_SOURCE
ASK_HUMAN
STOP_BLOCKED

Unknown vs false

A three-value mental model is often important:

TRUE
FALSE
UNKNOWN

For example:

"Is production healthy?"

If the metrics API is unavailable, the answer is not:

healthy = false

but:

health = UNKNOWN

UNKNOWN prevents missing data from being converted into invented positive or negative facts.

Observation freshness policy

Different data needs different freshness.

For example:

Architecture guideline      → days/months
Repository file content     → current commit
Deployment state            → seconds/minutes
Account balance             → immediate/current
Human approval              → current run state

Capability metadata can include:

freshness_requirement = 30s

or an action-specific rule:

Before mutating deployment, state must be observed within last 10 seconds.

Observation cache

Caching can help, but it can also be dangerous.

Good candidates:

repository metadata
static policy
unchanged artifact hash

Risky candidates:

current balance
current deployment state
lock ownership
approval status

Cache policy should be domain-specific, not a generic agent trick.

Event-driven observations

Not every observation is polled by the agent.

Events can include:

build finished
approval granted
deployment failed
user replied

Then:

Event
 ↓
update run state
 ↓
resume loop

This becomes important for long-running agents.

Human response as observation

Suppose the agent asks:

"Should I investigate environment A or B?"

The user replies:

"Production."

This can be represented as an observation in the current run:

WAITING_FOR_HUMAN
       ↓
human_response(environment=production)
       ↓
RUNNING

rather than as unrelated new top-level input.

Observation history vs current state

Keep these distinct:

Observation log = what happened
Current state    = what the runtime believes is true now

For example:

obs1: test failed
obs2: patch applied
obs3: test passed

Current state:

target_test = PASS

History is useful for audit/debugging; current state is useful for decisions.

Context projection

Not every observation needs to be replayed into every model call.

Weak:

all 100 previous tool results
+ all logs
+ all intermediate text

Better:

canonical state
      ↓
context builder
      ↓
current goal
relevant recent observations
important historical evidence
active constraints

This connects observation design to context engineering.

Example: incident diagnosis

Goal: explain checkout error spike

Iteration 1:

Action: query error rate
Observation: 12% error from 14:03

Iteration 2:

Action: read recent deployments
Observation: payment-service v7.4.2 deployed 14:02

Iteration 3:

Action: query affected logs
Observation: duplicate-key errors began 14:03

Model hypothesis:

The new release likely introduced duplicate writes in retry path.

The runtime still stores facts and hypotheses separately.

Anti-pattern: conversation history as truth

Earlier assistant message:
"Production is probably on v7.4.1"

This does not become canonical environment state merely because it was written earlier.

Current truth can be re-observed.

Anti-pattern: prose-only tool result

Weak:

"Looks like deployment mostly works but one thing seems odd."

Better tool contract:

{
  "status": "DEGRADED",
  "ready": 3,
  "desired": 4,
  "unhealthy_pods": ["payment-7cc8f"]
}

Then let the model explain it.

Anti-pattern: write based on stale state

Avoid:

model saw state 10 minutes ago
→ destructive action now

when the domain requires current-state validation.

Takeaways

  • An observation is the loop's fresh connection to the environment.
  • Model assumptions, hypotheses, and environment facts are distinct categories.
  • Use authoritative sources and timestamped observations for current state.
  • Mutating actions often need observe-before-act and fresh precondition checks.
  • Normalize tool output, while keeping raw evidence distinguishable from model interpretation.
  • Missing data should be UNKNOWN/UNAVAILABLE, never invented values.
  • Freshness and caching policy are domain-specific.
  • Observation history and canonical current state are different things.
  • The context builder should project relevant state to the model instead of replaying everything.