popoto.fields.observation¶
popoto.fields.observation
¶
ObservationProtocol + RecallProposal — Outcome-driven memory effects.
Provides lifecycle hooks for passive behavioral inference on memory models. The application layer reports how the agent used retrieved memories; the ORM applies effects atomically.
Three hooks
on_read(instance): Fire when query hydrates an instance. Delegates to AccessTrackerMixin staging.on_surfaced(instances, reason): Fire when proactive system pushes memories into agent context. Creates RecallProposal entries.on_context_used(instances, outcome_map): Fire when application reports how the agent responded. Applies effects based on outcome.
Five outcomes
acted: Memory content appeared in agent's response. Strengthen.dismissed: Agent explicitly ignored/rejected. Weaken.deferred: Agent didn't address it. No effects, pressure builds.contradicted: Agent explicitly contradicted. Aggressively weaken.used: Agent consumed the memory (read + reasoned) but did not act on it in the response. Confirms the staged read and auto-resolves predictions with a moderate error, but does NOT touch ConfidenceField, CyclicDecayField, or DecayingSortedField. Observable distinction fromdeferred:usedrecords a confirmed-read trace,deferreddiscards staged reads.
See Also
For the effects-per-field matrix (what each outcome does to ConfidenceField, CyclicDecayField, DecayingSortedField, AccessTracker, PredictionLedger), see the "Effects Matrix" section of docs/features/observation-protocol.md.
RecallProposal
Internal ORM infrastructure for tracking proactively surfaced memories. Redis ZSET keyed by model class and partition, scored by surfaced_at. TTL-based expiration (default 1 hour).
Example
from popoto import ObservationProtocol, RecallProposal
After agent processes memories:¶
outcome_map = { memory1.db_key.redis_key: "acted", memory2.db_key.redis_key: "dismissed", } ObservationProtocol.on_context_used(memories, outcome_map)
ACTED_CONFIDENCE_SIGNAL = Defaults.ACTED_CONFIDENCE_SIGNAL
module-attribute
¶
Confidence signal sent to ConfidenceField on 'acted' outcome. Higher values corroborate the memory more strongly. Optimal range: [0.5, 1.0]. Insensitive within this range (nDCG stable).
CONTRADICTED_CONFIDENCE_SIGNAL = Defaults.CONTRADICTED_CONFIDENCE_SIGNAL
module-attribute
¶
Confidence signal sent to ConfidenceField on 'contradicted' outcome. Lower values contradict the memory more aggressively. Optimal range: [0.05, 0.3]. Insensitive within this range (nDCG stable).
ACTED_CYCLE_STRENGTHEN_FACTOR = Defaults.ACTED_CYCLE_STRENGTHEN_FACTOR
module-attribute
¶
CyclicDecayField amplification factor on 'acted' outcome. CLIFF EFFECT: values < 1.0 cause a 23% nDCG drop in temporal scheduling. Must be >= 1.0. Optimal range: [1.0, 2.0]. Default 1.2 is safe.
DISMISSED_CYCLE_WEAKEN_FACTOR = Defaults.DISMISSED_CYCLE_WEAKEN_FACTOR
module-attribute
¶
CyclicDecayField damping factor on 'dismissed' outcome. Values < 1.0 weaken the cycle amplitude. Optimal range: [0.3, 1.0]. Insensitive within this range.
CONTRADICTED_CYCLE_WEAKEN_FACTOR = Defaults.CONTRADICTED_CYCLE_WEAKEN_FACTOR
module-attribute
¶
CyclicDecayField aggressive damping factor on 'contradicted' outcome. Values < 1.0 weaken the cycle amplitude; lower = more aggressive. Optimal range: [0.3, 0.8]. Insensitive within this range.
AUTO_DISCHARGE_CONFIDENCE_THRESHOLD = Defaults.AUTO_DISCHARGE_CONFIDENCE_THRESHOLD
module-attribute
¶
Below this confidence level, pressure is auto-resolved on contradicted outcome. Very low confidence records should not continue building pressure. Optimal range: [0.05, 0.3]. Insensitive within this range.
ObservationProtocol
¶
Lifecycle hooks for passive behavioral inference on memory models.
All methods are static — the protocol is a stateless coordinator that dispatches effects based on outcome type.
Source code in src/popoto/fields/observation.py
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | |
on_read(instance, pipeline=None)
staticmethod
¶
Fire when query hydrates an instance. Delegates to AccessTrackerMixin staging.
If the instance's model uses AccessTrackerMixin, this calls
instance.on_read(). Otherwise it's a no-op.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
instance
|
A Model instance that was just read from Redis. |
required | |
pipeline
|
Optional Redis pipeline for batch operations. |
None
|
Source code in src/popoto/fields/observation.py
on_surfaced(instances, reason='proactive', partition=None, pipeline=None)
staticmethod
¶
Fire when proactive system pushes memories into agent context.
Creates RecallProposal entries for tracking. Side-effect-free on the memories themselves.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
instances
|
List of Model instances being surfaced. |
required | |
reason
|
Why the memories were surfaced. Default "proactive". |
'proactive'
|
|
partition
|
Optional partition key for multi-agent setups. |
None
|
|
pipeline
|
Optional Redis pipeline for batch operations. |
None
|
Source code in src/popoto/fields/observation.py
on_context_used(instances, outcome_map, pipeline=None)
staticmethod
¶
Fire when application reports how agent responded to surfaced memories.
For each instance, looks up its outcome in outcome_map and applies the corresponding effects atomically.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
instances
|
List of Model instances that were in the agent's context. |
required | |
outcome_map
|
Dict mapping instance Redis keys (str) to outcome strings: "acted", "used", "dismissed", "deferred", "contradicted". Instances not in the map default to "deferred". |
required | |
pipeline
|
Optional Redis pipeline for batch operations. |
None
|
Note
This method validates outcome_map strictly against
VALID_OUTCOMES. Application-specific outcomes (e.g. a custom
"echoed" label) must be coerced to one of the five valid
values before calling, otherwise a ValueError is raised.
See docs/features/observation-protocol.md (where the
protocol lives) for guidance on mapping bespoke outcomes into
the canonical vocabulary.
Raises:
| Type | Description |
|---|---|
ValueError
|
If any outcome string is not a valid outcome. |
Source code in src/popoto/fields/observation.py
RecallProposal
¶
Internal tracking for proactively surfaced memories.
Key pattern: $RP:{ClassName}:pending:{partition} -> ZSET scored by surfaced_at Statuses: pending -> acted | used | dismissed | deferred | contradicted | expired TTL: default 3600s (1 hour). Unresolved proposals treated as deferred.
This is internal ORM infrastructure, not a user-facing Model.
Source code in src/popoto/fields/observation.py
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 | |
create_batch(instances, reason='proactive', partition=None, pipeline=None)
classmethod
¶
Create pending proposals for a batch of instances.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
instances
|
List of Model instances being surfaced. |
required | |
reason
|
Why the memories were surfaced. |
'proactive'
|
|
partition
|
Optional partition key. |
None
|
|
pipeline
|
Optional Redis pipeline for batch operations. |
None
|
Source code in src/popoto/fields/observation.py
resolve(instance, outcome, partition=None, pipeline=None)
classmethod
¶
Remove a resolved proposal from the pending set.
Idempotent — returns 0 if already removed (e.g., by expiration).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
instance
|
The Model instance whose proposal to resolve. |
required | |
outcome
|
The outcome string (for logging). |
required | |
partition
|
Optional partition key. |
None
|
|
pipeline
|
Optional Redis pipeline for batch operations. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
int |
Number of members removed (0 or 1). |
Source code in src/popoto/fields/observation.py
expire_stale(model_class, partition=None, ttl=None, pipeline=None)
classmethod
¶
Remove proposals older than TTL. Returns expired member keys.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model_class
|
The Model class to check. |
required | |
partition
|
Optional partition key. |
None
|
|
ttl
|
TTL in seconds. Default DEFAULT_TTL (3600). |
None
|
|
pipeline
|
Optional Redis pipeline for batch operations. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
list |
List of expired member key strings. |
Source code in src/popoto/fields/observation.py
get_pending(model_class, partition=None)
classmethod
¶
Return all pending proposals as (member_key, surfaced_at) pairs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model_class
|
The Model class to check. |
required | |
partition
|
Optional partition key. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
list |
List of (member_key_str, surfaced_at_float) tuples. |