app
AI Flow
Public Made by Adomby adom
Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board.
← Commit history
Recognize Codex queue auto-start by exact client message identity
3 files changed
+26−3
docs/codex-supervisor.md+1−1@@ -23,4 +23,4 @@ On **any** user pause or cancellation, the AI must run `pause` immediately befor State and receipts live in `<run>/supervisor/{status,state,pause}.json` and `events.jsonl`. A process PID alone is not proof it is alive: check the last check time and the process. A `start-acknowledged` receipt is not proof the model completed subsequent work. Verify the next turn in the same conversation. No engineering gate, board or acceptance limit is changed by this helper. -Validation: ten policy/dispatch tests cover active/approval states, explicit pause, failed/interrupted turns, completion, malformed guards, queue interference, duplicate suppression, uncertain dispatch, state races and no-progress. Live metadata discovery was verified against Codex 0.154.0-alpha.6.2. End-to-end automatic continuation remains unverified until an actual completed turn is followed by a receipt and new turn in that same thread. Source availability, local activation and a packaged release are separate claims.+Validation: eleven policy/dispatch tests cover active/approval states, explicit pause, failed/interrupted turns, completion, malformed guards, queue interference, duplicate suppression, uncertain dispatch, state races and no-progress. Live metadata discovery was verified against Codex 0.154.0-alpha.6.2. Live continuation was observed in the same electronic-load thread: queue/add auto-started its next turn, carrying the exact client message ID. The initial observer incorrectly classified that as a race; it now attributes its own auto-start by client ID, covered by an additional regression test. Source availability, local activation and a packaged release are separate claims.
tools/continuation/supervisor.py+10@@ -155,6 +155,16 @@ def check(run, directory, thread, state): write(directory/'state.json', state) # A newer human turn may have started meanwhile; remove only our item. latest = client.rpc('thread/read', {'threadId': thread, 'includeTurns': False})['thread']['status']+ # queue/add may auto-start immediately in the IDE. Attribute activity+ # by the exact client ID, not by timing or an assumption of a race.+ if latest['type'] == 'active' and not (directory/'pause.json').exists():+ recent = client.rpc('thread/turns/list', {'threadId': thread, 'limit': 1,+ 'sortDirection': 'desc', 'itemsView': 'summary'})['data']+ if recent and any(item.get('type') == 'userMessage'+ and item.get('clientId') == state['dispatchId']+ for item in recent[0].get('items', [])):+ state.update(dispatch='auto-start-verified', startedTurn=recent[0]['id'])+ return 'continuation-dispatched', state if latest['type'] != 'idle' or (directory/'pause.json').exists(): client.rpc('thread/queue/delete', {'threadId': thread, 'queuedSubmissionId': queued['id']}) state['dispatch'] = 'removed-after-state-change'
tools/continuation/test_supervisor.py+15−2@@ -39,6 +39,7 @@ class FakeClient: race=False read_count=0 fail=False+ auto=False def __init__(self,*args): pass def close(self): pass def rpc(self,method,params):@@ -46,7 +47,11 @@ class FakeClient: if method=='thread/read': type(self).read_count+=1 return {'thread':{'status':{'type':'active' if self.race and self.read_count>=3 else 'idle'}}}- if method=='thread/turns/list':return {'data':[{'id':'t','status':'completed'}]}+ if method=='thread/turns/list':+ if self.auto and self.read_count>=3:+ sent=next(p for m,p in self.calls if m=='thread/queue/add')+ return {'data':[{'id':'own-next-turn','status':'inProgress','items':[{'type':'userMessage','clientId':sent['clientUserMessageId']}]}]}+ return {'data':[{'id':'t','status':'completed'}]} if method=='thread/queue/list':return {'data':[]} if method=='thread/queue/add': if self.fail:raise TimeoutError('uncertain dispatch')@@ -55,7 +60,7 @@ class FakeClient: class DispatchTests(unittest.TestCase): def setUp(self):- FakeClient.calls=[];FakeClient.race=False;FakeClient.read_count=0;FakeClient.fail=False+ FakeClient.calls=[];FakeClient.race=False;FakeClient.read_count=0;FakeClient.fail=False;FakeClient.auto=False def run_check(self, path, state): with patch.object(s,'discover',return_value=({'socket':'test'},{})), patch.object(s,'Codex',FakeClient), patch.object(s,'guard',return_value={'decision':'continue','mayStop':False}), patch.object(s,'progress_hash',return_value='same'): return s.check(path,path,'exact-thread',state)@@ -82,6 +87,14 @@ class DispatchTests(unittest.TestCase): self.assertEqual(action,'paused-dispatch-race') self.assertIn(('thread/queue/delete',{'threadId':'exact-thread','queuedSubmissionId':'q'}),FakeClient.calls) self.assertFalse(any(m=='thread/queue/start' for m,_ in FakeClient.calls))+ def test_auto_start_is_recognized_without_deleting_or_restarting(self):+ with tempfile.TemporaryDirectory() as d:+ FakeClient.race=True;FakeClient.auto=True+ action,state=self.run_check(Path(d),{})+ self.assertEqual(action,'continuation-dispatched')+ self.assertEqual(state['dispatch'],'auto-start-verified')+ self.assertEqual(state['startedTurn'],'own-next-turn')+ self.assertFalse(any(m in ['thread/queue/start','thread/queue/delete'] for m,_ in FakeClient.calls)) def test_pause_and_no_progress(self): with tempfile.TemporaryDirectory() as d: p=Path(d);s.write(p/'pause.json',{})