Skip to content

Quill meetings integration + after-hours digest processing#27

Merged
agr77one merged 1 commit into
mainfrom
feat/quill-meetings
Jun 18, 2026
Merged

Quill meetings integration + after-hours digest processing#27
agr77one merged 1 commit into
mainfrom
feat/quill-meetings

Conversation

@agr77one

Copy link
Copy Markdown
Owner

Connect the local Quill note app over MCP to search meetings and ask about them on the local model — with an after-hours scheduler that pre-computes digests so daytime reads are instant.

Why

Asking the NPU about a full meeting transcript costs real prefill: a POC measured ~17 s to first token for a ~7k-token transcript (~25–35 s total). So instead of paying that at read time, a daemon scheduler runs during a configurable idle window (default 17:00–21:00, only when idle) and caches a digest (summary / goals / action items) per meeting in data/meeting_digests.jsonl. Reads are then instant; ad-hoc "Ask about this meeting" grounds on the cached digest.

What's new

  • ffp_quill — minimal stdlib MCP-over-HTTP client (initialize → tools/call) + parsing of Quill's meeting/transcript/minutes output. Loopback-only.
  • ffp_meetings — digest cache (upsert/list/get), batch worker (pages undigested meetings, idempotent, max-per-run), pure scheduler gate (should_run_batch: enabled + window + idle), and GetLastInputInfo idle detection.
  • Daemon — a scheduler thread (no-op unless meetings.enabled + in window) + 8 actions (quill_status, quill_search_meetings, meeting_digest_get, meeting_digests_list, meeting_process, meeting_batch_run, meeting_batch_status, meeting_ask). 59 → 69 actions. The meeting actions self-lock + write a separate file, so a long batch never holds the global config write-lock.
  • Config — a whitelisted meetings block (off by default, opt-in; mcp_url validated loopback).
  • Dashboard — a Meetings tab (search → cached digest / "Process now" / "Ask about this meeting") and a Config card for all settings + "Run batch now".

Validation

  • Live: ffp_quill.status + search_meetings against real Quill; process_meeting against real Quill + NPU produced an accurate digest in ~33 s on a full transcript.
  • ruff clean · 305 pytest passed (+27: scheduler window/idle, digest store, batch skips-cached/max-per-run, ask-prefers-cached, MCP parsing, config-patch whitelist incl. loopback rejection) · node --check app.js.

Off by default — enable it in Config → Meetings & after-hours processing.

🤖 Generated with Claude Code

Connect the local Quill note app over MCP to search meetings and ask about
them on the local model. Asking about a full transcript costs ~15-17s of
prefill on the NPU, so a daemon scheduler pre-computes a digest (summary /
goals / action items) per meeting during a configurable idle window
(default 17:00-21:00, idle-gated) and caches it in data/meeting_digests.jsonl
— daytime reads are then instant.

New modules: ffp_quill (stdlib MCP-over-HTTP client + parsing) and
ffp_meetings (digest store, batch worker, pure scheduler gate, GetLastInputInfo
idle detection). New config block `meetings` (off by default, opt-in;
mcp_url validated loopback-only). New dashboard Meetings tab (search /
cached digest / Process now / Ask) + a Config card with a "Run batch now"
button. Daemon gains a scheduler thread + 8 actions (quill_status,
quill_search_meetings, meeting_digest_get, meeting_digests_list,
meeting_process, meeting_batch_run, meeting_batch_status, meeting_ask);
59 -> 69 actions. The meeting actions self-lock and write a separate cache
file, so a long batch never blocks the global config write-lock.

Tests: test_ffp_meetings (scheduler window/idle, digest store upsert, batch
skips-cached + max-per-run, ask prefers cached digest), test_ffp_quill
(parsing), meetings config-patch whitelist incl. loopback rejection; daemon
action-count 69. Validated live: search + process_meeting against real
Quill + NPU (33s full-transcript digest, accurate).

Gates: ruff clean, 305 pytest passed, node --check app.js.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@agr77one agr77one merged commit 6e7f5a9 into main Jun 18, 2026
5 checks passed
@agr77one agr77one deleted the feat/quill-meetings branch June 18, 2026 12:36

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 28273ec040

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/ffp_daemon.py
def _act_quill_search_meetings(args: dict) -> dict:
import ffp_quill
url = str(_meetings_cfg().get("mcp_url") or ffp_quill.DEFAULT_MCP_URL)
return ffp_quill.search_meetings(str(args.get("query") or ""), int(args.get("limit") or 12), url=url)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Gate Quill searches on the opt-in flag

When meetings.enabled is false, opening the Meetings tab still calls searchMeetings(), which reaches this action and queries Quill with the configured/default URL. That violates the off-by-default opt-in behavior and can expose meeting titles/participants even though the integration is disabled; the Quill data actions should refuse unless meetings.enabled is true.

Useful? React with 👍 / 👎.

Comment thread scripts/ffp_meetings.py
Comment on lines +217 to +219
if not content:
content = ffp_quill.get_transcript(meeting_id, client=client)
used = "transcript" if content else used

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor the minutes-only source setting

If the user selects source: "minutes" from the Config UI (Minutes only), meetings without minutes still fall through here and fetch the full transcript. That silently defeats the privacy/performance reason for the setting and can feed a full transcript to the model during batch processing; only auto should fall back to transcript.

Useful? React with 👍 / 👎.

Comment thread scripts/ffp_meetings.py
Comment on lines +346 to +347
if not c.connect():
return {"ok": False, "error": "Quill MCP server is not reachable"}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use cached digests before connecting to Quill

For an already-processed meeting, meeting_ask should be able to answer from meeting_digests.jsonl, but this early connect() makes it fail whenever Quill is closed or temporarily unavailable. Since the cache is the advertised fast path, check get_digest() first and only connect to Quill when falling back to live minutes/transcript content.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants