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.