···434434`auth_profiles.*.credential.secret_ref` is also a direct environment variable name (example: `JSONBILL_API_KEY`).
435435436436Key meanings (see `assets/config/config.example.yaml` for the canonical list):
437437-- Core: `llm.provider` selects the backend. Most providers use `llm.endpoint`/`llm.api_key`/`llm.model`. Optional defaults `llm.temperature`, `llm.reasoning_effort`, and `llm.reasoning_budget_tokens` are forwarded to `uniai` only when set. Azure uses `llm.azure.deployment` for deployment name, while endpoint/key are still read from `llm.endpoint` and `llm.api_key`. Bedrock uses `llm.bedrock.*`. `llm.tools_emulation_mode` controls tool-call emulation for models without native tool calling (`off|fallback|force`). `llm.profiles` defines named profile overrides, and `llm.routes` routes semantic purposes such as `main_loop`, `addressing`, `heartbeat`, and `plan_create`.
437437+- Core: `llm.provider` selects the backend. Most providers use `llm.endpoint`/`llm.api_key`/`llm.model`. Optional defaults `llm.temperature`, `llm.reasoning_effort`, and `llm.reasoning_budget_tokens` are forwarded to `uniai` only when set. Azure uses `llm.azure.deployment` for deployment name, while endpoint/key are still read from `llm.endpoint` and `llm.api_key`. Bedrock uses `llm.bedrock.*`. `llm.tools_emulation_mode` controls tool-call emulation for models without native tool calling (`off|fallback|force`). `llm.profiles` defines named profile overrides, and `llm.routes` routes semantic purposes such as `main_loop`, `addressing`, `heartbeat`, `plan_create`, and `memory_draft`.
438438- LLM secret refs: secret-like fields in `llm` and `llm.profiles` accept sibling `*_ref` keys. The value of `*_ref` is the environment variable name, not the secret itself. If both plaintext and `*_ref` are set, `*_ref` wins. If `*_ref` points to a missing or empty env var, startup fails fast. Example:
439439 ```yaml
440440 llm:
···193193Notes:
194194195195- Runtime-level memory integration is wired in Telegram, Slack, and Heartbeat.
196196-- Telegram currently uses a legacy direct memory adapter (`internal/channelruntime/telegram/runtime_task.go`).
197197-- Slack and Heartbeat use shared orchestrator wiring (`internal/memoryruntime/*`) via:
196196+- Telegram, Slack, and Heartbeat all use shared orchestrator wiring (`internal/memoryruntime/*`) via:
197197+ - `internal/channelruntime/telegram/runtime.go`
198198+ - `internal/channelruntime/telegram/runtime_task.go`
198199 - `internal/channelruntime/slack/runtime.go`
199200 - `internal/channelruntime/slack/runtime_task.go`
200201 - `internal/channelruntime/heartbeat/run.go`
+4-2
docs/feat/feat_20260228_memory_refactor.md
···11# Memory Implementation Plan (WAL-first, Minimal)
2233+Note: this plan doc is historical. The current event shape and projector behavior are documented in `docs/feat/memory-project-time-draft.md` and `docs/memory.md`.
44+35## 1. Principles
4657- `memory/log/*.jsonl` is the single source of truth.
···4244 - `protocol`
4345 - `task_text`
4446 - `final_output`
4545- - `draft_summary_items`
4646- - `draft_promote`
4747+ - `source_history`
4848+ - `session_context`
4749- [x] Define id rules:
4850 - `event_id` unique per event.
4951 - `task_run_id` links events to one run.
+57
docs/feat/memory-project-time-draft.md
···11+# Memory Project-Time Draft
22+33+## Goal
44+55+Move memory drafting from channel task execution into the memory projection stage.
66+77+The intended pipeline is:
88+99+1. `channel runtime` writes a raw memory event into the journal/log.
1010+2. `memory projector` reads raw events and resolves a draft.
1111+3. `memory projector` writes projected short-term and long-term memory artifacts.
1212+1313+This replaces the old shape:
1414+1515+1. `channel runtime` called LLM to build `SessionDraft`
1616+2. `channel runtime` wrote drafted event into the journal
1717+3. `projector` only merged precomputed draft
1818+1919+## Why
2020+2121+- The memory journal should be a project-level raw source of truth, not a place that already contains channel-local interpretation.
2222+- LLM draft generation is part of projection, not ingestion.
2323+- Channel runtimes should only normalize source data and append raw events.
2424+- Projection can now use current projected state (`existing short-term content`) when resolving new drafts.
2525+2626+## Event Shape
2727+2828+New events are written with raw fields:
2929+3030+- `task_text`
3131+- `final_output`
3232+- `source_history`
3333+- `session_context`
3434+3535+Projector behavior:
3636+3737+- projector asks its `DraftResolver` to derive a draft from raw event data
3838+- old journal compatibility is intentionally dropped; replay expects the raw event shape
3939+4040+## Draft Resolver
4141+4242+`memoryruntime.NewDraftResolver(...)` owns the project-time draft strategy.
4343+4444+- If an LLM client is configured and the event has enough source history, it calls `memory.draft`.
4545+- Otherwise it falls back to a simple deterministic summary derived from `final_output`.
4646+4747+This keeps the draft decision inside memory/projection, not inside channel task execution.
4848+4949+## Channel Responsibilities
5050+5151+Channels now only append raw events:
5252+5353+- Telegram: appends source history plus session context.
5454+- Slack: appends source history plus session context.
5555+- Heartbeat: appends raw summary output and minimal session context.
5656+5757+No channel task path should call `BuildLLMDraft` directly anymore.
+6-6
docs/memory.md
···316316- `participants` (array, multi-party aware, may be empty)
317317- `task_text`
318318- `final_output`
319319-- `draft_summary_items`
320320-- `draft_promote`
319319+- `source_history`
320320+- `session_context`
321321322322The schema should evolve conservatively.
323323···338338339339```json
340340{
341341- "schema_version": 1,
341341+ "schema_version": 3,
342342 "event_id": "evt_01JY7K9M7T3H2QZ6A9D5V4N8P1",
343343 "task_run_id": "run_01JY7K9B3W8F6M2N4C1R0T9X5Q",
344344 "ts_utc": "2026-02-28T06:15:12Z",
···354354 ],
355355 "task_text": "emmm",
356356 "final_output": "",
357357- "draft_summary_items": [],
358358- "draft_promote": {}
357357+ "source_history": [],
358358+ "session_context": {}
359359}
360360```
361361···430430Projection outputs in one pass:
431431432432- one short-memory file per touched `(day, subject_id)` bucket
433433-- optional long-term update (`memory/index.md`) when event contains `draft_promote`
433433+- optional long-term update (`memory/index.md`) when resolved draft contains `promote`
434434435435When projecting one target file, summary merge uses all existing summary items in that file.
436436