chat: replace triage backend with chat singleton
Third of three sub-lodes for the chat backend rewrite (parent plan:
chat-refactor). The big flip: new `/api/chat` singleton backend
replaces `/api/triage`, and every legacy chat path is removed.
Backend (new)
- `convey/chat.py` is the singleton chat runtime and endpoint surface.
Single-process Flask worker assumption is stated in a top-of-file
comment. Module-level `threading.Lock` guards the single-slot chat
generate. `start_chat_runtime(app)` wires callosum cortex/finish +
cortex/error subscriptions and performs the idempotent crash-
recovery scan on boot.
- Endpoints: POST /api/chat (append owner_message, schedule generate),
GET /api/chat/session, GET /api/chat/stream/<day>, GET
/api/chat/result/<use_id>. Chat stream is the queue; no separate
in-memory queue exists. Source of truth for all responses is the
stream itself, reduced on the fly — never the exec talent log.
- Active-exec cap = 2 (3rd request fires a remediation chat generate
with the spec literal "max active — waiting for one to finish").
Loop cap = 3 consecutive exec cycles without an owner_message
(then append chat_error with "chat had trouble — try again"). Any
owner_message resets the loop counter.
- `convey/utils.spawn_agent()` and `think/cortex_client.cortex_request()`
now accept an optional caller-supplied `use_id`. Default keeps the
auto-allocation path.
- Exec dispatch prompt is assembled inline in chat.py per spec E2
(task + context hints + location + last 6 chat turns; no digest).
`spawn_agent(name="exec", ...)` — never "unified".
Cleanup
- Deleted `convey/triage.py` (renamed to `convey/chat.py`),
`apps/home/events.py`, `think/conversation.py`,
`talent/conversation_memory.py`, `talent/triage.md`,
`tests/test_conversation.py`, `tests/test_home_events.py`.
- Deleted the `_resolve_talent_path` `unified` alias branch and the
transient `_UNDISCOVERED_SYSTEM_TALENTS` narrowing that 2a had added.
- Removed `think/cortex.py`'s `TRIAGE_AGENT_NAMES`-based display
decoration. `convey/chat.py` now produces the `display` field for
chat-flow finish events directly.
- `think/awareness.py::compute_thickness()` previously pulled recent
exchanges from the deleted `think.conversation`; it now reads the
chat stream via a local helper.
- `convey/templates/app.html` — minimal URL swap from `/api/triage` to
`/api/chat` with recovery-shape adjustment. Full UI rewrite is
lode 3.
Tests
- New: `test_chat_runtime.py` (cap logic, crash recovery, cortex
correlation), `test_convey_chat.py` (endpoint contracts),
`test_no_legacy_chat_imports.py` (regression gate for the deleted
symbols and "unified" string), plus the chat-turn FTS5 coverage
case in `test_journal_index.py`.
- `conftest.py` has an autouse cleanup that resets the module-level
chat runtime between tests.
- `make ci`: 3740 passed, 4 skipped (pre-existing sandbox-only plus
the 3 search/graph harness skips from 2a). `make verify-api`:
51 endpoints green. `make review` browser phase blocked on
pinchtab startup — infrastructure issue unrelated to 2c.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>