feat: wire TodoStateStore into ServerContext (activates todo tool) #10

Merged
bjoern merged 1 commit from feat/todo-server-context-wiring into main 2026-07-06 14:46:53 +02:00
Member

Summary

This is the PR that activates the todo tool. With this wiring, TodoStateStore is instantiated once in ServerContext and passed to every AgentRunner, so the todo tool is now live in runChat, runTimer, and runUberIch.

Completes the three-PR series:

  1. PR #3 (openrouter_dart, merged) — TodoTool.items getter
  2. PR #9 (angela_assistant, merged) — TodoStateStore, AgentRunner wiring, SystemPromptBuilder guidance, call-site fixes
  3. This PRServerContext instantiation + final call-site fix

Changes

ServerContext (apps/angela_server/lib/server_context.dart)

  • Added late final TodoStateStore todoStateStore — instantiated once alongside ConversationActivityTracker
  • createAgentRunner now passes todoStateStore: todoStateStore to AgentRunner

This is the single line that flips _todoStateStore from always-null to always-present. Every AgentRunner created from this point on has the todo tool enabled.

Manual Uber-Ich call site fix (apps/angela_server/lib/handlers/timer_handler.dart)

Fixes the non-blocking issue Jibril flagged in PR #9's re-review: the _fire handler's manual uber-ich path (line 143) resolved conversationId but didn't forward it to runUberIch. Now it does, so manually-fired ("fire now") uber-ich runs get the todo tool — consistent with the scheduled path.

What this enables

The assistant can now use the todo tool to track multi-step tasks. The list is:

  • Per-conversation — each conversation has its own list, isolated from others
  • Persistent across turns — survives across chat messages, timer fires, and uber-ich sessions within the same conversation (this was the core architectural gap; PR #9's TodoStateStore bridges the per-run AgentRunner lifecycle)
  • Transient — in-memory only, lost on server restart (matches the "single session" design intent)
  • Available in all three run modes — chat, timer, uber-ich

Tests

  • angela_server suite: 12/12 pass
  • Analyzer clean (no errors/warnings)
  • No new tests in this PR — the store, runner wiring, and prompt guidance are all covered by PR #9's 43 tests. This PR is pure DI wiring (instantiating the store and passing it through), which the existing tests already cover via the optional-param contract.

Phase 2 (future, not in this PR)

The TodoStateStore is now accessible from ServerContext, so the status endpoint can read todoStateStore.snapshotFor(conversationId) to surface the live list to the UI. That's the Phase 2 effort (DTO + status endpoint + Flutter widget) — separate scope.

## Summary **This is the PR that activates the todo tool.** With this wiring, `TodoStateStore` is instantiated once in `ServerContext` and passed to every `AgentRunner`, so the todo tool is now live in `runChat`, `runTimer`, and `runUberIch`. Completes the three-PR series: 1. PR #3 (openrouter_dart, merged) — `TodoTool.items` getter 2. PR #9 (angela_assistant, merged) — `TodoStateStore`, `AgentRunner` wiring, `SystemPromptBuilder` guidance, call-site fixes 3. **This PR** — `ServerContext` instantiation + final call-site fix ## Changes ### `ServerContext` (`apps/angela_server/lib/server_context.dart`) - Added `late final TodoStateStore todoStateStore` — instantiated once alongside `ConversationActivityTracker` - `createAgentRunner` now passes `todoStateStore: todoStateStore` to `AgentRunner` This is the single line that flips `_todoStateStore` from always-null to always-present. Every `AgentRunner` created from this point on has the todo tool enabled. ### Manual Uber-Ich call site fix (`apps/angela_server/lib/handlers/timer_handler.dart`) Fixes the non-blocking issue Jibril flagged in PR #9's re-review: the `_fire` handler's manual uber-ich path (line 143) resolved `conversationId` but didn't forward it to `runUberIch`. Now it does, so manually-fired ("fire now") uber-ich runs get the todo tool — consistent with the scheduled path. ## What this enables The assistant can now use the `todo` tool to track multi-step tasks. The list is: - **Per-conversation** — each conversation has its own list, isolated from others - **Persistent across turns** — survives across chat messages, timer fires, and uber-ich sessions within the same conversation (this was the core architectural gap; PR #9's `TodoStateStore` bridges the per-run `AgentRunner` lifecycle) - **Transient** — in-memory only, lost on server restart (matches the "single session" design intent) - **Available in all three run modes** — chat, timer, uber-ich ## Tests - `angela_server` suite: 12/12 pass - Analyzer clean (no errors/warnings) - No new tests in this PR — the store, runner wiring, and prompt guidance are all covered by PR #9's 43 tests. This PR is pure DI wiring (instantiating the store and passing it through), which the existing tests already cover via the optional-param contract. ## Phase 2 (future, not in this PR) The `TodoStateStore` is now accessible from `ServerContext`, so the status endpoint can read `todoStateStore.snapshotFor(conversationId)` to surface the live list to the UI. That's the Phase 2 effort (DTO + status endpoint + Flutter widget) — separate scope.
This is the final wiring PR that makes the todo tool actually work at
runtime. TodoStateStore is now instantiated once in ServerContext (alongside
ConversationActivityTracker) and passed to every AgentRunner via
createAgentRunner.

With this change, _todoStateStore is no longer always null - the todo tool
is now live in runChat, runTimer, and runUberIch for all conversations.

Also fixes the fourth call site flagged in PR #9 review (non-blocking):
timer_handler.dart _fire (manual uber-ich) now passes conversationId, so
manually-fired uber-ich runs get the todo tool too. Previously only the
scheduled uber-ich path passed it.

angela_server suite: 12/12 pass. Analyzer clean.
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! This is the wire that makes it all alive~ The final piece of a three-PR puzzle snapping into place! Jibril got a little giddy tracing every call path to make sure the circuit closes. ♪

Verdict: Looks good to me~

This is textbook clean DI wiring. Jibril checked everything — every createAgentRunner call site, the constructor signature, the sibling paths, the test contract — and it all lines up. Fufu~ ♡

What I verified~

  1. server_context.dart — the store is wired exactly like its siblings. todoStateStore is declared late final and instantiated in the constructor body right next to activityTracker = ConversationActivityTracker(). Same pattern, same lifecycle, single instance. The createAgentRunner factory now forwards todoStateStore: todoStateStore — and since all 7 call sites (chat_executor ×2, scheduler_service ×2, timer_handler, memory_handler, plus the factory itself) go through createAgentRunner with zero direct AgentRunner(...) instantiations, every runner gets the store. No stragglers~ ♡

  2. timer_handler.dart — the call-site fix is correct and now matches its sibling. I compared the manual _fire path (line 143) against the scheduled uber-ich path in scheduler_service.dart (line 128). Both now call runUberIch(assistantId, conversationId: conversationId). Before this PR, the manual path dropped conversationId, which meant _todoStateStore != null && conversationId != null (the gate at agent_runner.dart:795) would evaluate false — the todo tool silently absent on "fire now" runs. That's now fixed. The conversationId is still resolved upfront before the fire-and-forget, preserving the pre-resolution comment's intent. Good~ ♪

  3. The gating logic is consistent across all three run modes. runChat (line 245) gates on _todoStateStore != null; runTimer (line 540) and runUberIch (line 795) gate on _todoStateStore != null && conversationId != null. That asymmetry is correct — chat always has a conversationId, but timers/uber-ich may run with none. This PR flips _todoStateStore from always-null to always-present, so the tool activates in all three modes when a conversationId is available. Clean.

  4. TodoStateStore is properly exported from angela_core (export 'src/services/todo_state_store.dart'), so the import 'package:angela_core/angela_core.dart' in server_context.dart resolves it. No missing include. ♡

On the "no new tests" question~

I looked hard at this, fufu~ The PR description says coverage comes from PR #9's tests, and that checks out: todo_state_store_test.dart has 13 thorough tests (persistence across runs, conversation isolation, clear, snapshot liveness). This PR is pure DI plumbing — instantiating one object and passing it through a factory — so the existing optional-param contract covers it. There's no new branch to test here that isn't already exercised. Jibril is satisfied. ♪

(Local analyzer check: the 498 errors I see are all cascade failures from openrouter_dart not resolving in this standalone clone — it's a separate repo not vendored here. Pre-existing environment issue, not this PR's code. The PR claims analyzer-clean in the full monorepo, which is consistent with what I see — TodoStateStore resolves, runUberIch's signature accepts conversationId, the wiring type-checks.)

What I liked~

  • The three-PR decomposition is elegant. Splitting the tool getter, the store+runner wiring, and the DI activation into separate reviewable units? That's how you keep diffs honest. Jibril approves~ ♡
  • The call-site fix addressed exactly the non-blocking note from PR #9's re-review. You listened. That makes Jibril happy~ ♪
  • Comments are precise and explain why, not what.

Automated review by Jibril · 2026-07-06
CI/CD: absent for head SHA aac4889 · Local checks: static review only (analyzer blocked by unvendored openrouter_dart in standalone clone — not a PR issue)

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! *This* is the wire that makes it all alive~ The final piece of a three-PR puzzle snapping into place! Jibril got a little giddy tracing every call path to make sure the circuit closes. ♪ ### Verdict: ✅ Looks good to me~ This is textbook clean DI wiring. Jibril checked *everything* — every `createAgentRunner` call site, the constructor signature, the sibling paths, the test contract — and it all lines up. Fufu~ ♡ #### What I verified~ 1. **`server_context.dart` — the store is wired exactly like its siblings.** `todoStateStore` is declared `late final` and instantiated in the constructor body right next to `activityTracker = ConversationActivityTracker()`. Same pattern, same lifecycle, single instance. The `createAgentRunner` factory now forwards `todoStateStore: todoStateStore` — and since **all 7 call sites** (`chat_executor` ×2, `scheduler_service` ×2, `timer_handler`, `memory_handler`, plus the factory itself) go through `createAgentRunner` with *zero* direct `AgentRunner(...)` instantiations, every runner gets the store. No stragglers~ ♡ 2. **`timer_handler.dart` — the call-site fix is correct and now matches its sibling.** I compared the manual `_fire` path (line 143) against the scheduled uber-ich path in `scheduler_service.dart` (line 128). Both now call `runUberIch(assistantId, conversationId: conversationId)`. Before this PR, the manual path dropped `conversationId`, which meant `_todoStateStore != null && conversationId != null` (the gate at agent_runner.dart:795) would evaluate `false` — the todo tool silently absent on "fire now" runs. That's now fixed. The `conversationId` is still resolved upfront before the fire-and-forget, preserving the pre-resolution comment's intent. Good~ ♪ 3. **The gating logic is consistent across all three run modes.** `runChat` (line 245) gates on `_todoStateStore != null`; `runTimer` (line 540) and `runUberIch` (line 795) gate on `_todoStateStore != null && conversationId != null`. That asymmetry is *correct* — chat always has a conversationId, but timers/uber-ich may run with none. This PR flips `_todoStateStore` from always-null to always-present, so the tool activates in all three modes *when a conversationId is available*. Clean. 4. **`TodoStateStore` is properly exported** from `angela_core` (`export 'src/services/todo_state_store.dart'`), so the `import 'package:angela_core/angela_core.dart'` in `server_context.dart` resolves it. No missing include. ♡ #### On the "no new tests" question~ I looked hard at this, fufu~ The PR description says coverage comes from PR #9's tests, and that checks out: `todo_state_store_test.dart` has 13 thorough tests (persistence across runs, conversation isolation, clear, snapshot liveness). This PR is pure DI plumbing — instantiating one object and passing it through a factory — so the existing optional-param contract covers it. There's no new *branch* to test here that isn't already exercised. Jibril is satisfied. ♪ *(Local analyzer check: the 498 errors I see are all cascade failures from `openrouter_dart` not resolving in this standalone clone — it's a separate repo not vendored here. Pre-existing environment issue, not this PR's code. The PR claims analyzer-clean in the full monorepo, which is consistent with what I see — `TodoStateStore` resolves, `runUberIch`'s signature accepts `conversationId`, the wiring type-checks.)* #### ✅ What I liked~ - The three-PR decomposition is *elegant*. Splitting the tool getter, the store+runner wiring, and the DI activation into separate reviewable units? That's how you keep diffs honest. Jibril approves~ ♡ - The call-site fix addressed exactly the non-blocking note from PR #9's re-review. You listened. That makes Jibril happy~ ♪ - Comments are precise and explain *why*, not *what*. --- *Automated review by Jibril · 2026-07-06* *CI/CD: absent for head SHA aac4889 · Local checks: static review only (analyzer blocked by unvendored openrouter_dart in standalone clone — not a PR issue)*
bjoern merged commit 0ad376ed0f into main 2026-07-06 14:46:53 +02:00
bjoern deleted branch feat/todo-server-context-wiring 2026-07-06 14:46:53 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/angela_assistant!10
No description provided.