Parent
#2 (PRD: /timebox — LLM-assisted next-day timeboxing)
What to build
Replace the /done echo stub from #4 with real schedule generation, implemented as a deep, independently-testable scheduler module. Read the PRD (#2) Implementation Decisions section first — all design decisions are settled there.
The scheduler module owns three responsibilities behind a small interface:
- Target-date computation (pure function): given the current datetime, a timezone, and a cutoff hour → the date being planned. Normally tomorrow in the configured timezone; if local time is between 00:00 and 02:59 (hours strictly below the cutoff), the current day instead. Use the stdlib zoneinfo.
- Schedule generation: given the raw task messages (verbatim, order preserved), the target date, and an injected LLM client → a structured result. The LLM parses each message into a task with optional duration/constraints (e.g. "deep work 90m morning", "call mom after 6pm"), estimates missing durations, honors freeform constraints including waking-window overrides stated by the user (e.g. "start my day at 7"), and produces a non-overlapping plan inside the default 09:00–22:00 window. If everything cannot fit, the LLM unilaterally drops the least important / least time-pressured tasks and gives a one-line reason per drop. Result contract: a schedule list of {start HH:MM, end HH:MM, task, optional note} plus a dropped list of {task, reason}. Enforce the contract with a Pydantic schema via langchain's .with_structured_output().
- Rendering (pure function): structured result → formatted Telegram text — time ranges + task names, then a "Dropped" section with reasons; omit the Dropped section when empty.
LLM plumbing: OpenRouter through langchain-openai's ChatOpenAI with base_url https://openrouter.ai/api/v1 — mirror the pattern in the sibling second_brain_summarizer repo (src/second_brain/agent/llm.py). New deps: langchain>=0.3,<0.4 and langchain-openai>=0.3,<0.4.
Config additions, following the existing Config class pattern (env getter + default + warning): OPENROUTER_API_KEY (required — fail fast at startup with a clear error if missing), TIMEBOX_LLM_MODEL (default deepseek/deepseek-v4-flash), TIMEBOX_TIMEZONE (default Asia/Singapore), TIMEBOX_CUTOFF_HOUR (default 3). Keep TIMEBOX_CUTOFF_HOUR separate from the existing DAY_CUTOFF_HOUR — do not couple them.
Testing (first tests in this repo — introduce pytest and a tests/ directory): cover the scheduler module only, asserting external behavior. Target-date computation around the cutoff and across timezone boundaries (server-UTC vs Asia/Singapore disagreeing on "today"); rendering with and without dropped items; generation happy path with a fake injected LLM. Do not test the Telegram handler.
Keep the prompt in one obvious place in the module — it will be tuned after real use.
Acceptance criteria
Blocked by
Parent
#2 (PRD: /timebox — LLM-assisted next-day timeboxing)
What to build
Replace the /done echo stub from #4 with real schedule generation, implemented as a deep, independently-testable scheduler module. Read the PRD (#2) Implementation Decisions section first — all design decisions are settled there.
The scheduler module owns three responsibilities behind a small interface:
LLM plumbing: OpenRouter through langchain-openai's ChatOpenAI with base_url https://openrouter.ai/api/v1 — mirror the pattern in the sibling second_brain_summarizer repo (src/second_brain/agent/llm.py). New deps: langchain>=0.3,<0.4 and langchain-openai>=0.3,<0.4.
Config additions, following the existing Config class pattern (env getter + default + warning): OPENROUTER_API_KEY (required — fail fast at startup with a clear error if missing), TIMEBOX_LLM_MODEL (default deepseek/deepseek-v4-flash), TIMEBOX_TIMEZONE (default Asia/Singapore), TIMEBOX_CUTOFF_HOUR (default 3). Keep TIMEBOX_CUTOFF_HOUR separate from the existing DAY_CUTOFF_HOUR — do not couple them.
Testing (first tests in this repo — introduce pytest and a tests/ directory): cover the scheduler module only, asserting external behavior. Target-date computation around the cutoff and across timezone boundaries (server-UTC vs Asia/Singapore disagreeing on "today"); rendering with and without dropped items; generation happy path with a fake injected LLM. Do not test the Telegram handler.
Keep the prompt in one obvious place in the module — it will be tuned after real use.
Acceptance criteria
Blocked by