Originally created by: Akarsh-Hegde
Summary
This PR lands the KAN-97 epic: dynamic LLM selection based on machine specs for the task-classification agent, along with a runtime settings UI, a refactored task-linker pipeline, and end-to-end distributed tracing.
What's in this PR
Dynamic LLM selection (services/agents/llm_selector.py)
- Detects available compute at startup: MLX on Apple Silicon, LM Studio with a loaded model, or falls back to the configured remote endpoint (e.g. Anthropic Claude).
- Manages a long-running
mlx_lm.server child process when no external server is found; respects LLM_BUDGET_PCT to leave headroom for macOS.
- Auto-unloads the managed MLX server when an external server is detected mid-run, and waits for the old process to fully exit before launching a replacement.
- Comprehensive structured logging + tracing spans (
llm.model, llm.runtime, llm.is_local) on every selection decision.
Runtime settings UI (ui/app/settings/, ui/components/views/SettingsView.tsx)
- New
/settings SPA view embedded in the dashboard (no page reload).
- Backed by
~/.meridian/settings.json via a new GET /api/settings + POST /api/settings route.
- Radix UI primitives (Switch, Select, NumberStepper) styled to Apple HIG feel.
- NumberStepper allows free typing without clamping mid-edit; clamps only on blur/enter.
Task linker refactor (src/intelligence/task_linker/)
- Merged
ticket_links into app_sessions (migration 018) — eliminates the join for every UI query.
- Split monolithic
db.rs into db.rs (reads) + db_write.rs (writes).
- Added startup preflight check that fast-fails on a missing classification stack.
- Reduced daemon batch to 1 session per tick to prevent blocking the poll loop.
- Hermes reasoning text now stored alongside the ticket link.
Intelligence pipeline simplification (services/agents/)
- Removed stages 1/2/3 (rules + embeddings + agent tiebreak); replaced with a single hermes
AIAgent call per session — simpler, more accurate, same latency budget.
- Renamed
agent_tiebreaker → task_classifier_agent throughout.
- Consolidated DB connections (
services/agents/db/) into typed sub-modules (sessions, context, dispatch, agent_runs, jira_updates).
- Backfill binaries (
src/bin/backfill_session_categories.rs, backfill_task_classification.rs) for historical data.
Observability (services/agents/observability.py, Rust intelligence/)
- End-to-end distributed tracing into OpenObserve via OTLP.
llm.model, llm.runtime, llm.is_local attributes on every span and log record.
Tests & docs
- Replaced stub smoke tests with real hermes integration tests for the task linker.
- New test suites:
test_parser.py, test_run_task_linker.py, test_llm_selector.py.
- Rewrote
services/README.md and services/agents/README.md to match the actual codebase.
- Added
scripts/setup-services.sh for co-dev onboarding.
chore(ui): added next-env.d.ts to .gitignore — auto-generated by Next.js on each build.
Migrations
| # |
File |
Change |
| 016 |
016_category_settler_cursor.sql |
Category settler cursor |
| 017 |
017_ticket_links_reasoning.sql |
Reasoning column on ticket_links |
| 018 |
018_merge_ticket_links.sql |
Merge ticket_links into app_sessions |
| 019 |
019_pm_tasks_hierarchy.sql |
PM task hierarchy |
| 020 |
020_session_text_source.sql |
Session text source tracking |
Test plan
- [ ]
cargo test passes (integration tests cover ETL, category settler, task linker smoke)
- [ ]
cargo clippy -- -D warnings clean
- [ ]
cd ui && npm run build succeeds
- [ ] Python tests:
cd services && pytest (test_llm_selector, test_parser, test_run_task_linker)
- [ ] Settings UI: open
/settings, toggle a switch, verify ~/.meridian/settings.json persists
- [ ] LLM selector: run
python -m agents.tagger --session <ID> and confirm model selection log lines
- [ ] Backfill: run
cargo run --bin backfill_task_classification against a populated DB
🤖 Generated with Claude Code
Ticket changed by: adityaharishch