personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

convey/chat: surface talent_errored on watchdog timeout

Record a terminal talent_errored chat-stream event before removing timed-out spawned talents so result/<use_id> no longer stays stuck active after the watchdog fires. Also relax the exec-dispatch runtime test to assert routing contract instead of full prompt formatting, and add a regression test for timed-out talent result state.

+92 -16
+6
convey/chat.py
··· 734 734 kind, 735 735 logical_use_id, 736 736 ) 737 + append_chat_event( 738 + "talent_errored", 739 + use_id=use_id, 740 + name=str(talent_state["target"]), 741 + reason=CHAT_WATCHDOG_REASON, 742 + ) 737 743 _active_talents.pop(use_id, None) 738 744 append_chat_event( 739 745 "chat_error",
+86 -16
tests/test_chat_runtime.py
··· 386 386 assert sol_messages[-1]["requested_task"] == "research it" 387 387 assert spawned_events[-1]["name"] == "exec" 388 388 assert spawned_events[-1]["task"] == "research it" 389 - assert spawn_calls == [ 390 - { 391 - "prompt": "Task: research it\n\nContext hints:\n{'k': 'v'}\n\n" 392 - "Location: app=sol path=/app/sol facet=work\n\n" 393 - "Recent chat:\n**Sol**: I am looking into that.", 394 - "name": "exec", 395 - "provider": None, 396 - "config": { 397 - "app": "sol", 398 - "path": "/app/sol", 399 - "facet": "work", 400 - "chat_parent_use_id": "1713625500000", 401 - }, 402 - "use_id": spawned_events[-1]["use_id"], 403 - } 404 - ] 389 + assert len(spawn_calls) == 1 390 + spawn_call = spawn_calls[0] 391 + assert spawn_call["name"] == "exec" 392 + assert spawn_call["use_id"] == spawned_events[-1]["use_id"] 393 + assert spawn_call["config"] == { 394 + "app": "sol", 395 + "path": "/app/sol", 396 + "facet": "work", 397 + "chat_parent_use_id": "1713625500000", 398 + } 399 + assert "research it" in str(spawn_call["prompt"]) 405 400 assert len(timers) == 2 406 401 assert timers[0].cancelled is True 407 402 with chat._state_lock: ··· 514 509 assert chat._current_chat_use_id is None 515 510 assert chat._current_chat_state is None 516 511 assert "1713629000001" not in chat._watchdog_timers 512 + 513 + 514 + def test_chat_watchdog_marks_timed_out_talent_result_as_errored(tmp_path, monkeypatch): 515 + import convey.chat as chat 516 + 517 + _setup_journal(tmp_path, monkeypatch) 518 + _reset_chat_state(chat) 519 + timers = _install_fake_timers(monkeypatch) 520 + 521 + monkeypatch.setattr("convey.chat._emit_cortex_event", lambda *args, **kwargs: None) 522 + monkeypatch.setattr("convey.chat._emit_error", lambda *args, **kwargs: None) 523 + monkeypatch.setattr( 524 + "convey.utils.spawn_agent", lambda *args, **kwargs: kwargs["use_id"] 525 + ) 526 + 527 + with chat._state_lock: 528 + logical_use_id = chat._reserve_use_id_locked() 529 + talent_use_id = chat._reserve_use_id_locked() 530 + 531 + append_chat_event( 532 + "talent_spawned", 533 + use_id=talent_use_id, 534 + name="exec", 535 + task="summarize", 536 + started_at=int(talent_use_id), 537 + ) 538 + 539 + with chat._state_lock: 540 + chat._current_chat_use_id = logical_use_id 541 + chat._current_chat_state = { 542 + "raw_use_id": None, 543 + "trigger": {"type": "owner_message", "message": "help"}, 544 + "location": {"app": "sol", "path": "/app/sol", "facet": "work"}, 545 + "retry_count": 0, 546 + } 547 + chat._active_talents[talent_use_id] = { 548 + "chat_use_id": logical_use_id, 549 + "target": "exec", 550 + "task": "summarize", 551 + "location": {"app": "sol", "path": "/app/sol", "facet": "work"}, 552 + } 553 + 554 + chat._run_next_action( 555 + { 556 + "kind": "talent", 557 + "logical_use_id": logical_use_id, 558 + "target": "exec", 559 + "use_id": talent_use_id, 560 + "task": "summarize", 561 + "context": {}, 562 + "location": {"app": "sol", "path": "/app/sol", "facet": "work"}, 563 + } 564 + ) 565 + 566 + timers[-1].fire() 567 + 568 + assert chat._read_result_state(talent_use_id) == { 569 + "state": "errored", 570 + "reason": "chat took too long — try again", 571 + "task": "summarize", 572 + } 573 + parent_errors = [ 574 + event 575 + for event in read_chat_events(chat._today_day()) 576 + if event["kind"] == "chat_error" 577 + ] 578 + talent_errors = [ 579 + event 580 + for event in read_chat_events(chat._today_day()) 581 + if event["kind"] == "talent_errored" 582 + ] 583 + assert parent_errors[-1]["use_id"] == logical_use_id 584 + assert parent_errors[-1]["reason"] == "chat took too long — try again" 585 + assert talent_errors[-1]["use_id"] == talent_use_id 586 + assert talent_errors[-1]["reason"] == "chat took too long — try again" 517 587 518 588 519 589 def test_cortex_finish_logs_warning_for_unrouteable_use_id(