@@ -778,6 +778,7 @@ def build_pipeline(
778778 DateTimeBlock ,
779779 PinnedFactsBlock ,
780780 RetrievedMemoryBlock ,
781+ TaskBoardBlock ,
781782 TurnNotesBlock ,
782783 )
783784
@@ -807,6 +808,7 @@ def build_pipeline(
807808 PinnedFactsBlock (),
808809 DateTimeBlock (),
809810 RetrievedMemoryBlock (),
811+ TaskBoardBlock (),
810812 TurnNotesBlock (),
811813 ]
812814 ),
@@ -1277,6 +1279,19 @@ def _record_execution(
12771279 return
12781280 spec = getattr (pipeline , "_memory_distill_spec" , None )
12791281 calls , failures , blocked = _tool_stats_from_events (state )
1282+ # TASK 먼저: 실행 카드가 이 턴이 기록된 TASK 를 적는다(Executions ↔ TASK 연결).
1283+ task = _record_task_turn (
1284+ provider ,
1285+ loop ,
1286+ state = state ,
1287+ input_text = input_text ,
1288+ output_text = output_text ,
1289+ success = success ,
1290+ cancelled = cancelled ,
1291+ calls = calls ,
1292+ failures = failures ,
1293+ blocked = blocked ,
1294+ )
12801295 try :
12811296 from xgen_agent_runtime .host .execution_record import record_turn_execution
12821297
@@ -1297,6 +1312,8 @@ def _record_execution(
12971312 tool_calls = calls ,
12981313 tool_failures = failures ,
12991314 blocked = blocked ,
1315+ task_id = getattr (task , "id" , "" ) or "" ,
1316+ task_title = getattr (task , "title" , "" ) or "" ,
13001317 ),
13011318 timeout = 10.0 ,
13021319 )
@@ -1305,6 +1322,84 @@ def _record_execution(
13051322 logger .debug ("geny_bridge: execution record failed (turn unaffected)" , exc_info = True )
13061323
13071324
1325+ def _record_task_turn (
1326+ provider : Any ,
1327+ loop : asyncio .AbstractEventLoop ,
1328+ * ,
1329+ state : PipelineState ,
1330+ input_text : Any ,
1331+ output_text : str ,
1332+ success : bool ,
1333+ cancelled : bool ,
1334+ calls : int ,
1335+ failures : int ,
1336+ blocked : int ,
1337+ ) -> Any :
1338+ """이 턴이 한 일을 이 대화의 현재 TASK 에 남긴다(memory.tasks, LLM 없음). 기록한 TASK 를 돌려준다.
1339+
1340+ 모델이 TASK 를 열지 않았어도 여러 단계 일이 분명하면(끝나지 않은 계획 2단계 이상, 도구를 쓰다
1341+ 끝나지 못한 턴) 요청으로 TASK 를 연다(호스트가 열었다고 적는다). 실패는 로그만.
1342+ """
1343+ from xgen_agent_runtime .memory .tasks import (
1344+ CURRENT_KEY ,
1345+ provider_tasks ,
1346+ record_turn ,
1347+ tool_calls_in ,
1348+ turn_messages ,
1349+ )
1350+
1351+ store = provider_tasks (provider )
1352+ if store is None :
1353+ return None
1354+ try :
1355+ from xgen_agent_runtime .host .execution_record import _as_text , classify_outcome
1356+ from xgen_agent_runtime .host .task_tools import sync_task_note
1357+ from xgen_agent_runtime .stages .s16_loop .completion_review import written_paths
1358+
1359+ msgs = turn_messages (list (state .messages or []))
1360+ n_calls = max (int (calls or 0 ), tool_calls_in (msgs ))
1361+ outcome = classify_outcome (
1362+ success ,
1363+ tool_calls = n_calls ,
1364+ tool_failures = failures ,
1365+ blocked = blocked ,
1366+ cancelled = cancelled ,
1367+ )
1368+ # TodoWrite 를 이번 턴에 썼을 때만 그 목록이 이 TASK 의 계획이다(지난 턴의 목록을 다른 TASK 에
1369+ # 덮어쓰지 않는다).
1370+ used = {
1371+ str (b .get ("name" ) or "" )
1372+ for m in msgs
1373+ if isinstance (m , dict ) and isinstance (m .get ("content" ), list )
1374+ for b in m ["content" ]
1375+ if isinstance (b , dict ) and b .get ("type" ) == "tool_use"
1376+ }
1377+ wrote_todos = "TodoWrite" in used
1378+
1379+ async def _go () -> Any :
1380+ task = await record_turn (
1381+ store ,
1382+ session_id = str (getattr (state , "session_id" , "" ) or "" ),
1383+ request = _as_text (input_text ),
1384+ answer = str (output_text or "" ),
1385+ outcome = outcome ,
1386+ tool_calls = n_calls ,
1387+ tool_failures = int (failures or 0 ),
1388+ files = written_paths (msgs ),
1389+ todos = state .shared .get ("executor.todos" ) if wrote_todos else None ,
1390+ current_id = str (state .shared .get (CURRENT_KEY ) or "" ),
1391+ task_tool_used = "Task" in used ,
1392+ )
1393+ if task is not None :
1394+ await sync_task_note (provider , store , task )
1395+ return task
1396+
1397+ return loop .run_until_complete (asyncio .wait_for (_go (), timeout = 10.0 ))
1398+ except Exception : # noqa: BLE001 — 기록은 best-effort
1399+ logger .debug ("geny_bridge: task record failed (turn unaffected)" , exc_info = True )
1400+ return None
1401+
1402+
13081403#: 끝나지 못한 턴을 기억에 남길 때 그 턴 끝에 붙이는 표식 — 다음 턴의 단기 기억 창이 이 턴을 "끝나지
13091404#: 않은 턴" 으로 읽는다. 위의 도구 호출·결과는 그때까지 실제로 한 일이다.
13101405UNFINISHED_TURN_NOTES = {
0 commit comments