-
Notifications
You must be signed in to change notification settings - Fork 1
fix: communication hardening -- meeting cooldown, circuit breaker backoff, debate fallback #1140
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
87461cd
0eff96f
be997fe
f26b5e0
65266ba
ff7c700
6aca96a
20912ff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -119,6 +119,11 @@ class MeetingTypeConfig(BaseModel): | |||||||||||||||||||||
| default_factory=MeetingProtocolConfig, | ||||||||||||||||||||||
| description="Meeting protocol configuration", | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| min_interval_seconds: int | None = Field( | ||||||||||||||||||||||
| default=None, | ||||||||||||||||||||||
| ge=1, | ||||||||||||||||||||||
| description="Minimum seconds between event-triggered meetings of this type", | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @model_validator(mode="after") | ||||||||||||||||||||||
| def _validate_frequency_or_trigger(self) -> Self: | ||||||||||||||||||||||
|
|
@@ -129,6 +134,9 @@ def _validate_frequency_or_trigger(self) -> Self: | |||||||||||||||||||||
| if self.frequency is None and self.trigger is None: | ||||||||||||||||||||||
| msg = "Exactly one of frequency or trigger must be set" | ||||||||||||||||||||||
| raise ValueError(msg) | ||||||||||||||||||||||
| if self.min_interval_seconds is not None and self.trigger is None: | ||||||||||||||||||||||
| msg = "min_interval_seconds requires trigger-based meetings" | ||||||||||||||||||||||
| raise ValueError(msg) | ||||||||||||||||||||||
| return self | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @model_validator(mode="after") | ||||||||||||||||||||||
|
|
@@ -235,6 +243,19 @@ class CircuitBreakerConfig(BaseModel): | |||||||||||||||||||||
| gt=0, | ||||||||||||||||||||||
| description="Cooldown period in seconds", | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| max_cooldown_seconds: int = Field( | ||||||||||||||||||||||
| default=3600, | ||||||||||||||||||||||
| gt=0, | ||||||||||||||||||||||
| description="Maximum cooldown period in seconds (caps exponential backoff)", | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @model_validator(mode="after") | ||||||||||||||||||||||
| def _validate_cooldown_bounds(self) -> Self: | ||||||||||||||||||||||
| """Ensure the exponential backoff cap is not below the base cooldown.""" | ||||||||||||||||||||||
| if self.max_cooldown_seconds < self.cooldown_seconds: | ||||||||||||||||||||||
| msg = "max_cooldown_seconds must be >= cooldown_seconds" | ||||||||||||||||||||||
| raise ValueError(msg) | ||||||||||||||||||||||
| return self | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
Comment on lines
251
to
260
|
||||||||||||||||||||||
| @model_validator(mode="after") | |
| def _validate_cooldown_bounds(self) -> Self: | |
| """Ensure the exponential backoff cap is not below the base cooldown.""" | |
| if self.max_cooldown_seconds < self.cooldown_seconds: | |
| raise ValueError( | |
| "max_cooldown_seconds must be greater than or equal to cooldown_seconds" | |
| ) | |
| return self |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,11 +37,11 @@ | |
| from synthorg.observability import get_logger | ||
| from synthorg.observability.events.conflict import ( | ||
| CONFLICT_AUTHORITY_FALLBACK, | ||
| CONFLICT_DEBATE_EVALUATOR_FAILED, | ||
| CONFLICT_DEBATE_JUDGE_DECIDED, | ||
| CONFLICT_DEBATE_STARTED, | ||
| CONFLICT_HIERARCHY_ERROR, | ||
| CONFLICT_LCM_LOOKUP, | ||
| CONFLICT_STRATEGY_ERROR, | ||
| ) | ||
|
|
||
| logger = get_logger(__name__) | ||
|
|
@@ -101,15 +101,37 @@ async def resolve(self, conflict: Conflict) -> ConflictResolution: | |
| conflict, | ||
| judge_id, | ||
| ) | ||
| except MemoryError, RecursionError: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| raise | ||
| except Exception: | ||
| logger.exception( | ||
| CONFLICT_STRATEGY_ERROR, | ||
| CONFLICT_DEBATE_EVALUATOR_FAILED, | ||
| conflict_id=conflict.id, | ||
| strategy="debate", | ||
| operation="judge_evaluate", | ||
| judge=judge_id, | ||
| ) | ||
| raise | ||
| try: | ||
| winning_agent_id, reasoning = self._authority_fallback( | ||
| conflict, | ||
| ) | ||
| except ConflictHierarchyError: | ||
| # Hierarchy tiebreak failed too -- fall back without | ||
| # hierarchy so we always produce a resolution. | ||
| logger.warning( | ||
| CONFLICT_HIERARCHY_ERROR, | ||
| conflict_id=conflict.id, | ||
| note="authority fallback hierarchy failed; " | ||
| "using seniority without hierarchy", | ||
| ) | ||
| best = pick_highest_seniority( | ||
| conflict, | ||
| hierarchy=None, | ||
| ) | ||
| winning_agent_id = best.agent_id | ||
| reasoning = ( | ||
| f"Debate fallback: authority-based judging " | ||
| f"(no hierarchy) -- {best.agent_id} " | ||
| f"({best.agent_level}) has highest seniority" | ||
| ) | ||
| else: | ||
| logger.warning( | ||
| CONFLICT_AUTHORITY_FALLBACK, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.