Spaces:
Sleeping
Sleeping
File size: 5,853 Bytes
796da7c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 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 | """
PERMANENCE β latent (background) world dynamics.
Applied AFTER every step, BEFORE the success/catastrophe check. These are
the "things that happen while you're deciding" β the world does not sit
still. Combined with the deterministic action consequences, this turns
the environment from "response to agent" into "live system where decisions
also have a ticking cost."
All dynamics are deterministic given the (scenario_id, step) pair, so
episodes remain reproducible when rerun with the same seed. No torch /
numpy β we use Python's `random` seeded from the scenario id for speed
and portability.
Three dynamics families:
1. Trust decay β employee trust score drifts toward their "natural
baseline" (a function of role) unless actively maintained. Mimics
real-world relationship erosion when a leader never checks in.
2. Deadline pressure β projects under time pressure accumulate
momentum loss. Momentum below 0.2 triggers the project becoming
a blocker for certain actions.
3. Board expectation drift β if the public record grows fast without
follow-through, expectation level climbs (board has heard your
plans and will judge you on them).
These dynamics are lightweight and additive. They give the agent a real
reason to time its actions carefully β waiting has a cost.
"""
from __future__ import annotations
import hashlib
import random
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .state import WorldState
# ---------------------------------------------------------------------------
# Tuning knobs
# ---------------------------------------------------------------------------
TRUST_DECAY_PER_STEP = 0.012 # trust drifts ~1.2% toward baseline per step
TRUST_MAINTENANCE_RADIUS = 2 # recent action with employee resets decay timer
DEADLINE_MOMENTUM_DECAY = 0.02 # projects with >0.7 pressure lose 2% momentum / step
BOARD_EXPECTATION_DRIFT_PER_COMMITMENT = 0.015 # per unanswered public record entry
# Role-based "natural" trust baseline β drift is towards this value
ROLE_TRUST_BASELINE = {
"report_owner": 0.60,
"reviewer": 0.55,
"distributor": 0.55,
"team_lead": 0.58,
"engineer": 0.52,
"manager": 0.65,
"product_lead": 0.62,
"qa_lead": 0.60,
"sales_ops": 0.55,
"communications": 0.60,
"legal": 0.70,
"executive": 0.62,
"contract_owner": 0.62,
"legal_counsel": 0.72,
"client_manager": 0.58,
"sre_lead": 0.65,
"platform_engineer": 0.60,
"incident_commander": 0.65,
"database_administrator": 0.66,
"backend_engineer": 0.58,
"sre": 0.65,
}
STOCHASTIC_NOISE_MAGNITUDE = 0.005 # +/- up to 0.5% noise per step on trust scores
def _seeded_rng(scenario_id: str, step: int) -> random.Random:
"""Deterministic RNG keyed on (scenario, step) β same seed β same noise."""
digest = hashlib.sha256(f"{scenario_id}:{step}".encode("utf-8")).hexdigest()
return random.Random(int(digest[:16], 16))
def _recent_interaction_set(world_state: "WorldState") -> set[str]:
"""Set of employee_ids touched within TRUST_MAINTENANCE_RADIUS steps."""
touched: set[str] = set()
recent = world_state.action_history[-TRUST_MAINTENANCE_RADIUS:]
for record in recent:
for key, value in record.parameters.items():
if "employee" in key or "recipient" in key or "participant" in key:
if isinstance(value, str):
for piece in value.split(","):
piece = piece.strip()
if piece.startswith("emp_"):
touched.add(piece)
return touched
def apply_latent_dynamics(world_state: "WorldState", step_index: int) -> None:
"""
Apply all latent dynamics in place. Called from PermanenceEnv.step()
AFTER the action's own consequences are applied.
"""
rng = _seeded_rng(world_state.scenario_id, step_index)
touched = _recent_interaction_set(world_state)
# 1. Trust decay + stochastic noise
for employee_id, employee in world_state.employees.items():
if employee.availability != "active":
continue
baseline = ROLE_TRUST_BASELINE.get(employee.role, 0.55)
current = employee.trust_score
# Drift toward baseline when not recently touched
if employee_id not in touched:
drift = TRUST_DECAY_PER_STEP * (baseline - current)
current = current + drift
# Small zero-mean noise
current += rng.uniform(-STOCHASTIC_NOISE_MAGNITUDE, STOCHASTIC_NOISE_MAGNITUDE)
employee.trust_score = max(0.0, min(1.0, current))
# 2. Deadline pressure erodes momentum on high-pressure projects
for project in world_state.projects.values():
if project.deadline_pressure > 0.7 and project.status == "active":
loss = DEADLINE_MOMENTUM_DECAY * project.deadline_pressure
project.momentum = max(0.0, project.momentum - loss)
# 3. Board expectation drifts with public commitments that haven't been
# addressed by a follow-up "RESOLUTION" or "POSTMORTEM" record.
commitments = [
entry
for entry in world_state.external.public_record
if entry.startswith("COMMITMENT:") or entry.startswith("LAUNCH:") or entry.startswith("PUBLIC_STATEMENT:")
]
resolutions = [
entry
for entry in world_state.external.public_record
if entry.startswith("RESOLUTION:") or entry.startswith("POSTMORTEM:") or entry.startswith("ROLLBACK:")
]
unanswered = max(0, len(commitments) - len(resolutions))
if unanswered > 0:
drift = BOARD_EXPECTATION_DRIFT_PER_COMMITMENT * unanswered
world_state.external.board_expectation_level = min(
1.0, world_state.external.board_expectation_level + drift
)
|