Menu

#341 release: hour-level worklog pipeline, Tauri dashboard fold, in-process capture, OAuth, and worklog improvements

closed
nobody
None
2026-06-26
2026-06-25
Anonymous
No

Originally created by: adityaharishch

What ships in this release

This is a production release merging the pre-main accumulation branch into main. It delivers several major feature milestones plus code-quality cleanup.

Hour-level worklog pipeline (Python + Rust)

  • New worklog_pipeline Rust module drives per-hour worklog drafting via /worklog_hour
  • Python agno pipeline: distil → activity report → reranker hint → tiered task matcher → worklog draft per matched task (or propose new ticket)
  • Time budget fix: multiple matched tasks now divide the hour evenly instead of each claiming the full 3600s
  • Ledger-backed settle/aging prevents drafts on hours still receiving ETL data
  • New FastAPI routes: /worklog_hour, /activity_report, /distill_hour, /rerank, /v1/chat/completions, /v1/models, /prefetch_model, /prefetch_status

Dashboard → Tauri fold (no more Node server)

  • Next.js dashboard now ships as a static export embedded in the Tauri binary
  • All /api/* routes replaced by Tauri invoke commands reading meridian-core directly
  • meridian-core crate introduced as the shared DB layer (daemon re-exports; tray depends directly)
  • Frontend calls Rust via ui/lib/bridge.ts — HTTP fetch and SSE removed

In-process capture (Bucket 2)

  • Screen/accessibility capture runs in the tray process (no screenpipe launchd agent)
  • meridian-cli.sh updated: screenpipe removed from managed LABELS

OAuth PKCE flow (meridian-oauth)

  • In-process browser-based OAuth 2.0 for Jira, Linear, Trello
  • CodeQL HIGH severity fix: build_authorize_url now takes individual fields (authorize_url, scopes, extra_params) instead of the full ProviderSpec, so client_secret provably cannot reach eprintln!

Setup wizard + onboarding

  • New ui/app/setup/ wizard with MLX runtime detection, model prefetch, and integration connect
  • ~/.meridian/onboarded flag controls first-run auto-open

Staging release pipeline

  • GitHub Actions: release-staging.yml, build-mlx-runtime.yml, new CI workflow
  • Semantic-release configured for pre-main staging channel

Other fixes in this release

  • worklog_pipeline.rs: replace unwrap() on and_hms_opt, add .context() to all ledger DB calls, reject invalid --day with explicit error
  • observability.py: remove duplicate instrument_agno definition
  • mlx_classifier.py: remove shebang, remove unused _MAX_TOKENS/_TEMPERATURE constants
  • pm_worklog_update/config.py: align MLX_SERVER_MODEL default with actual deployed model
  • routes/chat.py: return actual MODEL_ID in OpenAI /v1/chat/completions response
  • server.py: self_url built from actual bind host (not hardcoded 127.0.0.1)
  • worklog.py: remove redundant WORKLOG_SYSTEM = WORKLOG_SYSTEM self-assignment
  • match.py: remove unused MATCH_SYSTEM import
  • prefetch.py: remove unused HTTPException import; document bare except OSError
  • session_distiller.py: document bare except in MPS cache flush
  • activity_report.py: replace en dash with ASCII hyphen (Ruff RUF001)
  • match_tasks.py: remove stray bare 2 in prompt text

Deferred (not in this release)

  • SSRF validation for pipeline internal HTTP calls
  • Per-route span instrumentation across new FastAPI routes
  • Request size caps on new endpoints
  • Reranker eviction guard
  • Apple Developer signing/notarization (enrollment submitted, pending approval)

Test plan

  • [ ] cargo test passes (Rust integration tests)
  • [ ] cargo clippy -- -D warnings clean
  • [ ] CodeQL HIGH severity alert (meridian-oauth/src/flow.rs:72) cleared by CI re-run
  • [ ] Hour-level worklog drafts appear in pm_worklogs after a pipeline run
  • [ ] Multiple-task match: sum of time_spent_seconds across tasks = 3600
  • [ ] Setup wizard opens on fresh install (~/.meridian/onboarded absent)
  • [ ] Dashboard loads in Tauri webview (no Node server required)
  • [ ] OAuth connect flow opens browser and stores tokens

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
  • Added an hour-level worklog pipeline that distills activity, matches tasks, reranks candidates, and drafts proposed worklogs.
  • Added new MLX-backed HTTP endpoints (activity reports, hourly distillation, summarisation, OpenAI-compatible chat/model routes, reranking, and worklog-hour processing) plus model prefetch and service info/health endpoints.
  • Bug Fixes
  • Hygiene items marked “overdue” are now treated as must-fix and can be rescheduled.
  • Documentation
  • Updated setup/development, tray, and service/agent docs to reflect the new workflow and endpoint set.

Discussion

  • Anonymous

    Anonymous - 2026-06-25

    Originally posted by: coderabbitai[bot]

    Review Change Stack

    [!NOTE]

    Reviews paused

    It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

    Use the following commands to manage reviews:
    - @coderabbitai resume to resume automatic reviews.
    - @coderabbitai review to trigger a single review.

    Use the checkboxes below for quick actions:
    - [ ] ▶️ Resume reviews
    - [ ] 🔍 Trigger review

    📝 Walkthrough ## Walkthrough The PR replaces classifier-driven session handling with an hourly worklog pipeline, adds MLX-backed FastAPI routes and shared runtime state, moves Jira OAuth into a shared crate, and updates dev tooling, docs, and eval defaults for the tray-based workflow and 2B model. ## Changes **Worklog migration** |Layer / File(s)|Summary| |---|---| |**Core task-state contracts**
    `meridian-core/src/settings.rs`, `meridian-core/src/util/hygiene.rs`, `src/coding_agent_session_ingest/summariser/*`, `src/health/daemon.rs`, `src/pm_worklog/ledger.rs`, `src/migrations/049_pm_proposed_tasks.sql`|`RuntimeSettings` drops `llm_model_preference`, `overdue` becomes a must-fix hygiene reason with a fix, summariser rows now settle into `summarised`, `pending_classifier` is removed from hour-blocking checks, and `pm_proposed_tasks` is added.| |**Shared OAuth crate**
    `Cargo.toml`, `meridian-oauth/*`, `src/intelligence/oauth/*`|The workspace adds a shared OAuth crate, moves Jira OAuth logic into it, and re-exports the shared modules from the daemon-side OAuth surface.| |**Agent runtime and model services**
    `services/agents/{_state.py,config.py,mlx_classifier.py,reranker.py,observability.py,agno_viewer.py,pm_worklog_update/config.py}`|Shared runtime state, model lifecycle helpers, reranking and distillation services, observability helpers, worklog config, and the Agno viewer are added or updated.| |**Workflow routes and wiring**
    `services/agents/{routes/__init__.py,routes/health.py,routes/prefetch.py,routes/chat.py,routes/summarise.py,routes/inference.py,routes/activity.py,routes/distill.py,routes/rerank.py,routes/worklog.py,server.py}`|Health, prefetch, chat, summarise, activity, distill, rerank, and worklog handlers call the new runtime helpers, and `server.py` registers the routers through shared app state and lifespan setup.| |**Worklog pipeline contracts**
    `services/agents/worklog_pipeline/{__init__.py,agent_io.py,db.py,match.py,models.py,prompts/*}`|The worklog pipeline package adds agent factories, SQLite helpers, structured matching schemas, and the match, propose-ticket, and worklog prompt text.| |**Worklog orchestration**
    `services/agents/worklog_pipeline/{pipeline.py,workflow.py,worklog.py}`, `src/lib.rs`, `src/main.rs`, `src/worklog_pipeline.rs`|The hour pipeline distills sessions, builds reports, reranks candidates, matches tasks in tiers, drafts worklogs or proposals, and runs those stages through an Agno workflow wrapper; the daemon exports and drives the new hourly runner.| |**Dev scripts and tray guards**
    `.githooks/post-push`, `.github/workflows/ci.yml`, `dev-start.sh`, `install-dev.sh`, `install.sh`, `scripts/{meridian-cli.sh,setup-hooks.sh,sync-oo-dashboards.py}`, `services/scripts/*`, `tests/tray_assets.rs`, `tray/src-tauri/src/commands/health.rs`|Git hooks, install scripts, dashboard sync, CLI helpers, tray health checks, and tray dev-setup tests are updated for the tray-based workflow.| |**Docs and eval references**
    `CLAUDE.md`, `CONTRIBUTING.md`, `README.md`, `SETUP.md`, `services/{README.md,agents/README.md,tests/evals/README.md,tests/evals/configs/*}`, `tray/README.md`|Repository docs, setup notes, service descriptions, tray docs, and eval configs are rewritten for the tray-based runtime and the 2B model defaults.| ## Estimated code review effort 🎯 5 (Critical) | ⏱️ ~120 minutes ## Possibly related PRs - [[Meridiona/meridian#334](https://github.com/Meridiona/meridian/issues/334)](https://github.com/Meridiona/meridian/pull/334): Updates the same tray/dev/install scripts, guard tests, and runtime docs that this PR revises. - [[Meridiona/meridian#338](https://github.com/Meridiona/meridian/issues/338)](https://github.com/Meridiona/meridian/pull/338): Overlaps on the shared OAuth crate split and daemon-side Jira OAuth re-export path. ## Poem > I hopped through logs and launched the tray, > Where worklog hours now find their way. > Two-bits hum softly in MLX light, > Dashboards sync after pushes at night. > Hop, hop—our new paths feel just right.
    🚥 Pre-merge checks | ✅ 4 | ❌ 1 ### ❌ Failed checks (1 warning) | Check name | Status | Explanation | Resolution | | :----------------: | :--------- | :------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------------- | | Docstring Coverage | ⚠️ Warning | Docstring coverage is 64.58% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. |
    ✅ Passed checks (4 passed) | Check name | Status | Explanation | | :------------------------: | :------- | :------------------------------------------------------------------------------------------------------------------------------------- | | Title check | ✅ Passed | The title is concise and accurately summarizes the main release themes in the changeset. | | Description check | ✅ Passed | The description covers the release purpose, major changes, and testing plan, though some template sections are abbreviated or missing. | | Linked Issues check | ✅ Passed | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | ✅ Passed | Check skipped because no linked issues were found for this pull request. |
    ✨ Finishing Touches
    🧪 Generate unit tests (beta) - [ ] Create PR with unit tests - [ ] Commit unit tests in branch `pre-main`

    Comment @coderabbitai help to get the list of available commands.

     
  • Anonymous

    Anonymous - 2026-06-26

    Originally posted by: Akarsh-Hegde

    Review fixes applied (commit c50107a6)

    CodeQL HIGH severity (blocking CI) — fixed

    • meridian-oauth/src/flow.rs:72: build_authorize_url() now takes authorize_url: &str, scopes: &str, extra_params: &[...] instead of the full ProviderSpec. This breaks the data-flow path CodeQL was tracking from spec.client_secret through the function and into eprintln!. The authorize URL has never contained the secret — this makes it provably taint-free.

    Real data bug — fixed

    • services/agents/worklog_pipeline/pipeline.py: When an hour matches multiple tasks, each task was being logged with the full 3600s — a 3-task match would log 3h for a 1h window. Now divides evenly: time_per_task = 3600 // max(1, len(bindings)).

    Rust quality — fixed

    • src/worklog_pipeline.rs: replaced and_hms_opt(...).unwrap() with .context(), added .with_context() to all four ledger DB calls (ensure_hour, hour_is_done, upstream_settled, mark_hour_done), reject invalid --day strings with an explicit error message instead of silently using today, truncate HTTP error body to 200 chars in bail!.

    Python code quality — fixed

    • observability.py: removed the stale first instrument_agno definition (used openinference); kept the current one (uses opentelemetry)
    • mlx_classifier.py: removed #!/usr/bin/env python3 shebang; removed unused _MAX_TOKENS = 1024 and _TEMPERATURE = 0.0 constants
    • worklog_pipeline/worklog.py: removed self-assignment WORKLOG_SYSTEM = WORKLOG_SYSTEM
    • worklog_pipeline/match.py: removed unused from agents.worklog_pipeline.prompts.match_tasks import SYSTEM as MATCH_SYSTEM
    • routes/prefetch.py: removed unused HTTPException import; added # file may be deleted mid-walk; skip silently to bare except OSError
    • session_distiller.py: added # noqa: BLE001 — MPS cache flush is best-effort; non-fatal if torch absent to bare except
    • pm_worklog_update/config.py: MLX_SERVER_MODEL default changed from "qwen3.5-2b-instruct""mlx-community/Qwen3.5-2B-OptiQ-4bit" (matches the actual deployed model id)
    • prompts/activity_report.py: replaced en dash with ASCII hyphen - (Ruff RUF001)
    • worklog_pipeline/prompts/match_tasks.py: removed stray bare 2 between paragraphs
    • routes/chat.py: /v1/chat/completions response now returns MODEL_ID (the actual loaded model) instead of echoing req.model or "qwen3.5-2b-instruct"
    • server.py: self_url now built from args.host (not hardcoded "127.0.0.1") — fixes orchestration when --host is non-loopback

    Scripts — fixed

    • scripts/meridian-cli.sh: removed ${LABEL_SCREENPIPE} from LABELS — screenpipe is no longer a managed launchd agent (capture is in-process since Bucket-2)

    Docs — fixed

    • services/agents/README.md: added blank lines before \``bash` code blocks (MD031)

    Deferred (noted for follow-up):

    • SSRF validation for pipeline internal HTTP calls
    • Per-route span instrumentation on new FastAPI endpoints
    • Request size caps on /worklog_hour / /activity_report
    • actions/missing-workflow-permissions CI annotation (low severity, no attack surface in the affected jobs)
     
  • Anonymous

    Anonymous - 2026-06-26

    Originally posted by: Akarsh-Hegde

    Second round of review fixes (commits 1c9a5410, 4ccd42f1)

    CodeQL HIGH severity — second attempt

    • meridian-oauth/src/flow.rs: The first fix (decomposing ProviderSpec into individual fields) wasn't enough — CodeQL's Rust analysis is field-insensitive at the struct level, so spec.extra_authorize_params was still tainted because spec carries client_secret. Removed the eprintln!("If it doesn't open, paste this URL…") line entirely — the authorize URL is still opened in the browser via open_browser(&authorize); the print fallback is gone. This definitively breaks the taint chain since no value derived from spec reaches any log/print sink.

    Session distiller

    • session_distiller.py: Wrapped the sqlite3.connect(db) call in a try/finally block so con.close() is guaranteed even when execute() or fetchall() raises (was missing, could leak connections on query errors)
    • session_distiller.py: Added _HOUR_RE = re.compile(r'^\d{4}-\d{2}-\d{2}T\d{2}$') validation before _load_rows(..., "started_at LIKE ?", (hour + "%", ...)) — prevents SQLite wildcard injection if hour contains % or _

    Documentation

    • CLAUDE.md: Replaced stale "it serves /summarise + /classify_sessions" with the current route list (classification has run inside the Rust daemon since the pipeline port — /classify_sessions is no longer exposed)

    Code quality

    • agno_viewer.py: Replaced print() calls in __main__ with log.info() per the module-logger convention in services/agents/
    • scripts/sync-oo-dashboards.py: Guarded .get('message', result) with isinstance(result, dict) — OO can return a JSON array or string on error, which would have raised AttributeError

    Deferred (explicit disposition)

    These are tracked here so every review thread is accounted for:

    Comment Decision
    reranker.py:118 — add spans to score_candidates() Deferred: span instrumentation for the reranker is a separate observability pass
    reranker.py:109 — guard against eviction failure before loading reranker Deferred: needs reranker eviction lifecycle review
    routes/chat.py:119 — instrument /v1/chat/completions with OTel spans Deferred: full span instrumentation for new routes is a follow-up
    routes/health.py:41 — instrument /health and /info Deferred: same as above
    routes/prefetch.py:88 — prevent stale worker from overwriting model status Deferred: race condition fix needs design (model_id guard + worker token)
    routes/prefetch.py:138 — stability Deferred
    mlx_classifier.py:79 — wrap model load/eviction in OTel spans Deferred: same observability pass
    config.py:63 — validate and bound env overrides at import time Deferred: requires error propagation design (module import crash vs warn)
    sync-oo-dashboards.py:57 — SSRF (URL not restricted to loopback) Accepted risk: this is a developer post-push hook, not a server; --base-url is only ever set by the developer running it
     
  • Anonymous

    Anonymous - 2026-06-26

    Originally posted by: Akarsh-Hegde

    Round 3 fixes — remaining CodeRabbit findings

    Commit 49a56c6c addresses the open items from rounds 1–2:

    File Finding Resolution
    services/agents/worklog_pipeline/worklog.py Unused WORKLOG_SYSTEM import Fixed — removed; system prompt is embedded in the agent at construction, not passed here
    meridian-oauth/src/flow.rs eprintln! should use structured tracing Fixed — replaced both eprintln! calls with tracing::info!
    services/agents/session_distiller.py:63 Regex accepts impossible calendar hours Fixed — added datetime.strptime(hour, '%Y-%m-%dT%H') after the regex check
    src/main.rs:591 Worklog driver not woken after ETL settle Fixed — added worklog_notify.notify_one() after etl_notify.notify_one()
    scripts/sync-oo-dashboards.py:106 DELETE before POST risks empty slot on failure Fixed — POST first, DELETE old on success only
    services/agents/routes/inference.py Stale superseded route module Fixed — deleted; routes/chat.py + routes/summarise.py are the canonical implementations

    Deferred items (non-blocking; tracked for follow-up):

    • mlx_classifier.py:79, reranker.py:118, routes/chat.py:119, routes/health.py:41, routes/summarise.py:113 — per-route OpenObserve span instrumentation; deferred to a dedicated observability pass
    • reranker.py:109 — guard against failed eviction before loading reranker; deferred as a stability follow-up
    • routes/prefetch.py:88 — stale worker / status-overwrite race guard; deferred to a stability follow-up
    • routes/prefetch.py:138 / worklog_pipeline/workflow.py:172 — worker lifecycle stability; deferred
    • routes/activity.py:31, routes/rerank.py:33 — request-size / token-budget caps; deferred to a security hardening pass
    • routes/activity.py:115, routes/summarise.py:105 — strip incomplete <think> blocks; deferred as a polish item
    • routes/distill.py:29, routes/worklog.py:31db_path restriction to ~/.meridian/; deferred to a security pass
    • routes/worklog.py:95 — omit free-form worklog content from span attributes; deferred to observability clean-up
    • worklog_pipeline/db.py:179 — atomic commit/rollback across writes; deferred as a data-integrity improvement
    • worklog_pipeline/match.py:25_MIN_CONFIDENCE alignment with prompt contract; tracked for next classifier iteration
    • worklog_pipeline/models.py:87 — enum member name normalisation; tracked for schema refactor
    • worklog_pipeline/workflow.py:75hour/cycle_index validation at workflow layer; deferred as belt-and-suspenders clean-up
    • config.py:63 — env-override validation; deferred; module-import abort is acceptable failure mode for now
    • sync-oo-dashboards.py:57 — SSRF; accepted risk for this internal ops script run only by developers
    • worklog_pipeline/pipeline.py:43 — loopback-URL SSRF restriction; deferred as hardening
    • services/README.md:7 — classifier ownership wording; will update when Python classifier is fully retired
    • meridian-oauth/src/jira.rs:38 — cross-process refresh-token race; tracked in project decision log alongside other PR [#338] deferred findings (fd-lock fix in backlog)

    False positive: server.py:50_MODEL_ALLOW_PATTERNS is lazy-imported by routes/prefetch.py (lines 54 and 66) to avoid a circular import. No change needed.

     

    Related

    Tickets: #338

  • Anonymous

    Anonymous - 2026-06-26

    Ticket changed by: Akarsh-Hegde

    • status: open --> closed
     

Log in to post a comment.