Skip to content

Commit 0258d49

Browse files
Merge pull request #4 from WilliamClifton-dev/codex/fix-live-rollback-manager
Fix live rollback Manager evidence routing
2 parents 3ada4bf + 1154cc5 commit 0258d49

4 files changed

Lines changed: 103 additions & 111 deletions

File tree

‎deploy/agentteams/run-live-rollback.ps1‎

Lines changed: 82 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -115,20 +115,6 @@ function Send-MatrixText {
115115
return [string]$response.event_id
116116
}
117117

118-
function Get-MatrixEvent {
119-
param(
120-
[Parameter(Mandatory)][string]$RoomId,
121-
[Parameter(Mandatory)][string]$EventId,
122-
[Parameter(Mandatory)][string]$AuthToken
123-
)
124-
125-
$roomSegment = [uri]::EscapeDataString($RoomId)
126-
$eventSegment = [uri]::EscapeDataString($EventId)
127-
return Invoke-Matrix -Method Get `
128-
-Path "/_matrix/client/v3/rooms/$roomSegment/event/$eventSegment" `
129-
-AuthToken $AuthToken -Body $null
130-
}
131-
132118
function Test-ExactMarker {
133119
param(
134120
[Parameter(Mandatory)]$event,
@@ -155,64 +141,66 @@ function Test-ExactMarker {
155141

156142
function Find-StrictMarkers {
157143
param(
158-
[Parameter(Mandatory)][string]$RoomId,
144+
[Parameter(Mandatory)][string[]]$RoomIds,
159145
[Parameter(Mandatory)][object[]]$Requirements,
160146
[Parameter(Mandatory)][string]$AuthToken,
161147
[Parameter(Mandatory)][long]$StartedAtMilliseconds
162148
)
163149

164150
$found = @{}
165-
$roomSegment = [uri]::EscapeDataString($RoomId)
166-
$from = ""
167-
for ($page = 0; $page -lt 20; $page++) {
168-
$path = "/_matrix/client/v3/rooms/$roomSegment/messages?dir=b&limit=100"
169-
if (-not [string]::IsNullOrWhiteSpace($from)) {
170-
$path += "&from=$([uri]::EscapeDataString($from))"
171-
}
172-
$feed = Invoke-Matrix -Method Get -Path $path `
173-
-AuthToken $AuthToken -Body $null
174-
$reachedStart = $false
175-
foreach ($event in @($feed.chunk)) {
176-
if (
177-
$null -ne $event.origin_server_ts -and
178-
$event.origin_server_ts -lt $StartedAtMilliseconds
179-
) {
180-
$reachedStart = $true
151+
foreach ($roomId in $RoomIds) {
152+
$roomSegment = [uri]::EscapeDataString($roomId)
153+
$from = ""
154+
for ($page = 0; $page -lt 20; $page++) {
155+
$path = "/_matrix/client/v3/rooms/$roomSegment/messages?dir=b&limit=100"
156+
if (-not [string]::IsNullOrWhiteSpace($from)) {
157+
$path += "&from=$([uri]::EscapeDataString($from))"
181158
}
182-
foreach ($requirement in $Requirements) {
159+
$feed = Invoke-Matrix -Method Get -Path $path `
160+
-AuthToken $AuthToken -Body $null
161+
$reachedStart = $false
162+
foreach ($event in @($feed.chunk)) {
183163
if (
184-
-not $found.ContainsKey($requirement.phase) -and
185-
(Test-ExactMarker -event $event `
186-
-ExpectedSender $requirement.sender `
187-
-Marker $requirement.marker `
188-
-StartedAtMilliseconds $StartedAtMilliseconds)
164+
$null -ne $event.origin_server_ts -and
165+
$event.origin_server_ts -lt $StartedAtMilliseconds
189166
) {
190-
$found[$requirement.phase] = [ordered]@{
191-
phase = $requirement.phase
192-
agentName = $requirement.agentName
193-
matrixUserId = $event.sender
194-
roomId = $RoomId
195-
eventId = $event.event_id
196-
originServerTimestamp = $event.origin_server_ts
197-
bindingSha256 = $requirement.bindingSha256
167+
$reachedStart = $true
168+
}
169+
foreach ($requirement in $Requirements) {
170+
if (
171+
-not $found.ContainsKey($requirement.phase) -and
172+
(Test-ExactMarker -event $event `
173+
-ExpectedSender $requirement.sender `
174+
-Marker $requirement.marker `
175+
-StartedAtMilliseconds $StartedAtMilliseconds)
176+
) {
177+
$found[$requirement.phase] = [ordered]@{
178+
phase = $requirement.phase
179+
agentName = $requirement.agentName
180+
matrixUserId = $event.sender
181+
roomId = $roomId
182+
eventId = $event.event_id
183+
originServerTimestamp = $event.origin_server_ts
184+
bindingSha256 = $requirement.bindingSha256
185+
}
198186
}
199187
}
200188
}
201-
}
202-
if ($found.Count -eq $Requirements.Count -or $reachedStart) {
203-
break
204-
}
205-
$from = [string]$feed.end
206-
if ([string]::IsNullOrWhiteSpace($from)) {
207-
break
189+
if ($reachedStart) {
190+
break
191+
}
192+
$from = [string]$feed.end
193+
if ([string]::IsNullOrWhiteSpace($from)) {
194+
break
195+
}
208196
}
209197
}
210198
return $found
211199
}
212200

213201
function Wait-StrictMarkers {
214202
param(
215-
[Parameter(Mandatory)][string]$RoomId,
203+
[Parameter(Mandatory)][string[]]$RoomIds,
216204
[Parameter(Mandatory)][object[]]$Requirements,
217205
[Parameter(Mandatory)][string]$AuthToken,
218206
[Parameter(Mandatory)][long]$StartedAtMilliseconds,
@@ -221,7 +209,7 @@ function Wait-StrictMarkers {
221209

222210
$markers = @{}
223211
while ([DateTimeOffset]::UtcNow -lt $Deadline) {
224-
$markers = Find-StrictMarkers -RoomId $RoomId `
212+
$markers = Find-StrictMarkers -RoomIds $RoomIds `
225213
-Requirements $Requirements -AuthToken $AuthToken `
226214
-StartedAtMilliseconds $StartedAtMilliseconds
227215
if ($markers.Count -eq $Requirements.Count) {
@@ -312,9 +300,6 @@ if (
312300
}
313301
$matrixDomain = $manager.matrixUserID.Split(":", 2)[1]
314302
$adminMatrixUserId = "@$adminUser`:$matrixDomain"
315-
if ($adminMatrixUserId -ne $manager.matrixUserID) {
316-
throw "Matrix admin identity does not match the AgentTeams Manager."
317-
}
318303
$login = Invoke-Matrix -Method Post -Path "/_matrix/client/v3/login" -Body @{
319304
type = "m.login.password"
320305
identifier = @{ type = "m.id.user"; user = $adminMatrixUserId }
@@ -324,6 +309,16 @@ $authToken = [string]$login.access_token
324309
if ([string]::IsNullOrWhiteSpace($authToken)) {
325310
throw "Matrix login did not return an access credential."
326311
}
312+
$joinedRooms = Invoke-Matrix -Method Get `
313+
-Path "/_matrix/client/v3/joined_rooms" -AuthToken $authToken -Body $null
314+
$joinedRoomIds = @($joinedRooms.joined_rooms)
315+
if (
316+
$joinedRoomIds -notcontains $manager.roomID -or
317+
$joinedRoomIds -notcontains $team.teamRoomID
318+
) {
319+
throw "Matrix admin cannot observe the Manager and Team rooms."
320+
}
321+
$roomIds = @($manager.roomID, $team.teamRoomID)
327322

328323
$startedAt = [DateTimeOffset]::UtcNow
329324
$deadline = $startedAt.AddSeconds($TimeoutSeconds)
@@ -332,61 +327,38 @@ $rollbackMarker = "[$TaskId] ROLLBACK_REQUESTED $bindingSha256"
332327
$executedMarker = "[$TaskId] ROLLBACK_EXECUTED $bindingSha256"
333328
$verifiedMarker = "[$TaskId] ROLLBACK_VERIFIED $bindingSha256"
334329
$prompt = @"
335-
$($investigator.matrixUserID) [$TaskId] Coordinate a real AgentTeams rollback trace for the supplied L1 candidate. The independent AgentLoom host will execute the candidate and rollback after collecting your role-owned events.
330+
$($manager.matrixUserID) [$TaskId] Run a strict AgentTeams rollback workflow through team $TeamName. Delegate coordination to $($investigator.matrixUserID). The independent AgentLoom host will execute the candidate and rollback after collecting the role-owned events.
336331
337332
Candidate SHA-256: $failedPatchSha256
338333
Candidate diff:
339334
$failedPatch
340335
341336
Required interaction:
342-
1. Ask $($verifier.matrixUserID) to review the candidate against exact-boundary pagination behavior. The Verifier must post the exact standalone line $failureMarker from its own identity when it rejects the candidate.
343-
2. Stop and wait for the Manager's exact standalone $rollbackMarker event.
344-
3. After that Manager event, directly mention $($implementer.matrixUserID). The Implementer acknowledges restoring the approved snapshot by posting the exact standalone line $executedMarker from its own identity.
345-
4. Only after the Implementer event, directly mention $($verifier.matrixUserID) again. The Verifier posts the exact standalone line $verifiedMarker from its own identity.
337+
1. Investigator asks $($verifier.matrixUserID) to review the candidate against exact-boundary pagination behavior. The Verifier posts the exact standalone line $failureMarker from its own identity when it rejects the candidate, then Investigator reports that failure to Manager.
338+
2. Only after receiving the failure report, Manager posts the exact standalone line $rollbackMarker from its own identity and directs Investigator to continue.
339+
3. Investigator directly mentions $($implementer.matrixUserID). The Implementer acknowledges restoring the approved snapshot by posting the exact standalone line $executedMarker from its own identity.
340+
4. Only after the Implementer event, Investigator directly mentions $($verifier.matrixUserID) again. The Verifier posts the exact standalone line $verifiedMarker from its own identity.
346341
347-
Do not claim Human approval: this is an isolated L1 snapshot restore. Do not create another task ID. Do not include any credential in messages.
342+
Never fabricate another identity's marker. Do not claim Human approval: this is an isolated L1 snapshot restore. Do not create another task ID. Do not include any credential in messages.
348343
"@.Trim()
349-
$null = Send-MatrixText -RoomId $team.teamRoomID -Text $prompt `
350-
-MentionUserId $investigator.matrixUserID -AuthToken $authToken
344+
$null = Send-MatrixText -RoomId $manager.roomID -Text $prompt `
345+
-MentionUserId $manager.matrixUserID -AuthToken $authToken
351346

352-
$failureRequirements = @(
347+
$requirements = @(
353348
[ordered]@{
354349
phase = "VERIFICATION_FAILED"
355350
agentName = "agentloom-verifier"
356351
sender = $verifier.matrixUserID
357352
marker = $failureMarker
358353
bindingSha256 = $bindingSha256
359-
}
360-
)
361-
$failureEvents = Wait-StrictMarkers -RoomId $team.teamRoomID `
362-
-Requirements $failureRequirements -AuthToken $authToken `
363-
-StartedAtMilliseconds $startedAt.ToUnixTimeMilliseconds() -Deadline $deadline
364-
$failureEvent = $failureEvents.VERIFICATION_FAILED
365-
366-
$managerEventId = Send-MatrixText -RoomId $team.teamRoomID `
367-
-Text "$rollbackMarker`nRestore the approved snapshot and retain the failed candidate evidence." `
368-
-MentionUserId $investigator.matrixUserID -AuthToken $authToken
369-
$managerEvent = Get-MatrixEvent -RoomId $team.teamRoomID `
370-
-EventId $managerEventId -AuthToken $authToken
371-
if (
372-
-not (Test-ExactMarker -event $managerEvent `
373-
-ExpectedSender $manager.matrixUserID -Marker $rollbackMarker `
374-
-StartedAtMilliseconds $failureEvent.originServerTimestamp) -or
375-
$managerEvent.origin_server_ts -le $failureEvent.originServerTimestamp
376-
) {
377-
throw "Manager rollback request event is not ordered after the failure."
378-
}
379-
$managerEvidence = [ordered]@{
380-
phase = "ROLLBACK_REQUESTED"
381-
agentName = "agentloom-manager"
382-
matrixUserId = $managerEvent.sender
383-
roomId = $team.teamRoomID
384-
eventId = $managerEvent.event_id
385-
originServerTimestamp = $managerEvent.origin_server_ts
386-
bindingSha256 = $bindingSha256
387-
}
388-
389-
$finalRequirements = @(
354+
},
355+
[ordered]@{
356+
phase = "ROLLBACK_REQUESTED"
357+
agentName = "agentloom-manager"
358+
sender = $manager.matrixUserID
359+
marker = $rollbackMarker
360+
bindingSha256 = $bindingSha256
361+
},
390362
[ordered]@{
391363
phase = "ROLLBACK_EXECUTED"
392364
agentName = "agentloom-implementer"
@@ -402,23 +374,25 @@ $finalRequirements = @(
402374
bindingSha256 = $bindingSha256
403375
}
404376
)
405-
$finalEvents = Wait-StrictMarkers -RoomId $team.teamRoomID `
406-
-Requirements $finalRequirements -AuthToken $authToken `
407-
-StartedAtMilliseconds $managerEvent.origin_server_ts -Deadline $deadline
377+
$events = Wait-StrictMarkers -RoomIds $roomIds `
378+
-Requirements $requirements -AuthToken $authToken `
379+
-StartedAtMilliseconds $startedAt.ToUnixTimeMilliseconds() -Deadline $deadline
408380
if (
409-
$finalEvents.ROLLBACK_EXECUTED.originServerTimestamp -le
410-
$managerEvent.origin_server_ts -or
411-
$finalEvents.ROLLBACK_VERIFIED.originServerTimestamp -le
412-
$finalEvents.ROLLBACK_EXECUTED.originServerTimestamp
381+
$events.ROLLBACK_REQUESTED.originServerTimestamp -le
382+
$events.VERIFICATION_FAILED.originServerTimestamp -or
383+
$events.ROLLBACK_EXECUTED.originServerTimestamp -le
384+
$events.ROLLBACK_REQUESTED.originServerTimestamp -or
385+
$events.ROLLBACK_VERIFIED.originServerTimestamp -le
386+
$events.ROLLBACK_EXECUTED.originServerTimestamp
413387
) {
414388
throw "Rollback role events are not strictly chronological."
415389
}
416390

417391
$roleEvents = @(
418-
$failureEvent,
419-
$managerEvidence,
420-
$finalEvents.ROLLBACK_EXECUTED,
421-
$finalEvents.ROLLBACK_VERIFIED
392+
$events.VERIFICATION_FAILED,
393+
$events.ROLLBACK_REQUESTED,
394+
$events.ROLLBACK_EXECUTED,
395+
$events.ROLLBACK_VERIFIED
422396
)
423397
$submission = [ordered]@{
424398
schemaVersion = "agentloom.live-rollback-submission/v1alpha1"

‎src/agentloom/live_rollback.py‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -521,8 +521,16 @@ def _validate_role_event_chain(
521521
raise ValueError("role events do not match the required rollback flow")
522522
if len({event.event_id for event in events}) != 4:
523523
raise ValueError("role events must use distinct Matrix event IDs")
524-
if len({event.room_id for event in events}) != 1:
525-
raise ValueError("role events must belong to one Team Room")
524+
business_rooms = {
525+
event.room_id
526+
for event in events
527+
if event.agent_name != "agentloom-manager"
528+
}
529+
all_rooms = {event.room_id for event in events}
530+
if len(business_rooms) != 1 or len(all_rooms) > 2:
531+
raise ValueError(
532+
"role events must use one Team Room and at most one Manager Room"
533+
)
526534
timestamps = [event.origin_server_timestamp for event in events]
527535
if timestamps != sorted(timestamps) or len(set(timestamps)) != 4:
528536
raise ValueError("role events must be strictly chronological")

‎tests/test_agentteams_deployment.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,12 @@ def test_live_rollback_runner_is_paid_guarded_and_collects_role_owned_events() -
216216
assert "$bindingSha256" in script
217217
assert "RESTORE_APPROVED_SNAPSHOT" in script
218218
assert "originServerTimestamp" in script
219+
assert "$manager.roomID" in script
220+
assert "joined_rooms" in script
221+
assert "[string[]]$RoomIds" in script
222+
assert "$roomIds = @($manager.roomID, $team.teamRoomID)" in script
223+
assert "catch {\n break\n }" not in script
224+
assert "$adminMatrixUserId -ne $manager.matrixUserID" not in script
219225
assert "initialPassword" not in script
220226
assert "Write-Output $authToken" not in script
221227

‎tests/test_live_rollback.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,11 @@ def _submission(path: Path) -> Path:
8585
"phase": phase,
8686
"agentName": agent_name,
8787
"matrixUserId": matrix_user_id,
88-
"roomId": "!agentloom:example.test",
88+
"roomId": (
89+
"!manager:example.test"
90+
if agent_name == "agentloom-manager"
91+
else "!agentloom:example.test"
92+
),
8993
"eventId": event_id,
9094
"originServerTimestamp": 1_700_000_000_000 + index,
9195
"bindingSha256": binding_sha256,

0 commit comments

Comments
 (0)