Skip to content

feat(calendar): add advanced event search - #218

Open
dtump wants to merge 9 commits into
nextcloud:mainfrom
dtump:feat/advanced-calendar-search
Open

feat(calendar): add advanced event search#218
dtump wants to merge 9 commits into
nextcloud:mainfrom
dtump:feat/advanced-calendar-search

Conversation

@dtump

@dtump dtump commented Jul 30, 2026

Copy link
Copy Markdown

Closes #216.

I built this because I wanted Assistant to answer calendar questions more reliably, for example:

  • What is on my calendar next week?
  • Is there a yearly recurring event for an annual appointment?
  • Do I have anything scheduled around an upcoming holiday?

This adds a separate, read-only Calendar: Advanced Search category. It searches a required, bounded date range across the current user’s event calendars, expands recurring events and exceptions, supports grouped content matching, and reports incomplete or truncated results explicitly.

I kept it separate from calendar.py because the implementation is substantial and it reads full event resources inside Context Agent for recurrence and content matching. The separate category lets admins enable or disable that access independently. Only bounded selected fields are returned to the LLM; descriptions can be matched locally but are not returned. I am happy to adjust the structure if maintainers prefer another approach.

Validated against Context Agent 2.7.0 and current main, with 55 synthetic tests in a separate development harness, the upstream Ruff, Black and isort configuration, and a live Nextcloud test. Because this repository currently has no Python unit-test setup, I did not introduce one solely for this PR. I hope that is okay.

The implementation and test hardening received material AI assistance. I did functional testing myself.

Screenshot because the proof is in the pudding:
Screenshot_20260730_142846

@dtump
dtump force-pushed the feat/advanced-calendar-search branch from eef9769 to 51d53c9 Compare July 30, 2026 12:42
Signed-off-by: Dick Tump <dick@tump.me>
Assisted-by: Codex:gpt-5.6-sol
@dtump
dtump force-pushed the feat/advanced-calendar-search branch from 51d53c9 to e3510a0 Compare July 30, 2026 12:56
@marcelklehr

Copy link
Copy Markdown
Member

Hi @dtump
Thank you for this contribution!

Undeclared direct dependencies

calendar_search.py imports recurring_ical_events, icalendar, and lxml, but none are in pyproject.toml. They resolve today only transitively via nc-py-api[calendar] → caldav 3.2.1, which declares icalendar >6.0.0, lxml *, recurring-ical-events >=2.0.0. That means a caldav release dropping or bumping any of them silently breaks this tool with an ImportError at module load — and because tools.py calls spec.loader.exec_module() inside the tool-loading loop with no try/except, an import failure here takes down loading of all tool categories, not just this one.

Please add explicit constraints to tool.poetry.dependencies and regenerate poetry.lock.

expand_and_filter_events

expand_and_filter_events is called synchronously and can take a lot of time depending on the amount of items it handles. Perhaps wrap the parse/expand phase in asyncio.to_thread?

Per-calendar REPORTs seem to be fully sequential

Can we parallelize these using asyncio so we can get data faster?

UNTIL in recurrences

It seems that UNTIL clauses are ignored in occurrence estimation. This should probably be fixed.

Subscribed calendars?

Does this also work with subscribed calendars?

return "floating"
if start.utcoffset() == timedelta(0):
return "UTC"
return str(start.tzinfo)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

When the expanded occurrence has no TZID param surviving expansion, this falls through to str(tzinfo). With icalendar 6.x's default zoneinfo provider that yields "Europe/Berlin" (fine), but a non-zoneinfo tz provider or a custom VTIMEZONE can yield an opaque repr. Guarding on getattr(tzinfo, "key", None) and omitting the field otherwise would be safer than emitting something the model may quote back to the user.

result_limit = _validate_result_limit(limit)
validated_names = _validate_calendar_names(calendar_names)
groups = _validate_text_term_groups(text_term_groups)
return SearchBounds(start=start, end=end), validated_names, groups, result_limit

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this function raises a value error, rather than sticking to the convention you established in the other functions to return errors. It might make sense to change this?

MAX_XML_BYTES = 10 * 1024 * 1024
MAX_ICALENDAR_BYTES = 512 * 1024
RECURRENCE_UNIT_SECONDS = {
"SECONDLY": 1,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

please use tabs :)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I noticed that the W rule set is enabled in ruff (I use ruff in my projects as well), but W191 actually flags tabs (so that means you shouldn't use them). I'm happy to change this of course, but I wanted to double check with you. Or should the ruff config be fixed as well?

And I think Black is used as well. I'm not very familiar with Black, but when I ran Black it also tried to change tabs back to spaces.

@dtump

dtump commented Aug 3, 2026

Copy link
Copy Markdown
Author

Thanks for the detailed response. I'll check it out and come back with an updated PR. The performance remarks are good ones: I'm using Nextcloud only personally for a few users, so I don't run into performance issues easily, but I can imagine that it's different when you have a work calendar with 10+ daily items.

Will give an update when I'm finished, will pick them up one by one (with some AI help 😄)

dtump added 7 commits August 3, 2026 17:01
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Dick Tump <dick@tump.me>
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Dick Tump <dick@tump.me>
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Dick Tump <dick@tump.me>
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Dick Tump <dick@tump.me>
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Dick Tump <dick@tump.me>
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Dick Tump <dick@tump.me>
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Dick Tump <dick@tump.me>
@dtump

dtump commented Aug 5, 2026

Copy link
Copy Markdown
Author

@marcelklehr I addressed all review points. My only open question is the indentation: Ruff selects the W rules, including W191, which flags tabs, while Black formats indentation using spaces

Few notes:

  1. Subscribed calendars were previously unsupported, but are now implemented and successfully live-tested with a public WebCal feed
  2. Input-validation errors now follow the structured-result convention
  3. I did final functional regression testing, everything looks OK

Other than the indentation question, it’s ready for another review

@janepie

janepie commented Aug 8, 2026

Copy link
Copy Markdown
Member

Hey @dtump, this is a lot of code and not easy to grasp. Can you please explain in your own words what you are doing here and add some comments in the code?

Assisted-by: Codex:gpt-5.6-terra
Signed-off-by: Dick Tump <dick@tump.me>
@dtump

dtump commented Aug 8, 2026

Copy link
Copy Markdown
Author

Hi @janepie
I wanted to extend the current calendar functionality with a more advanced search, so it could answer more questions about the events in my calendar.

What the code does:

  1. Validated and bounds the requested date range, calendars, search terms and result limit based on LLM's tool call
  2. Discovers the current user's event calendars through CalDAV including subscribed calendars
  3. Requests events from the selected calendars (within the selected date range)
  4. Parses iCal data: handles recurring events, recurring exceptions, cancellations
  5. Searches summary, description, location and categories locally (locally as in inside the Context Agent instance)
  6. Returns only the selected fields together with explicit truncation and failure details (if any)
  7. Uses limits, background processing and bounded concurrency to avoid excessive or blocking work

I'm not a professional developer, although I do have a lot of Perl and Python experience from my 25+ years as a Linux sysadmin. So I defined the problems I had and made a plan with AI (gpt-5.6-sol), which then produced the implementation. I then reviewed it myself, as well as functional testing against my own Nextcloud instance (which is used in 'production' in my family) and I addressed review feedback together with AI (and functionally tested again).

I added a few comments to make the flow clearer, I hope that helps. If there are specific areas that need explanation or changes, please let me know.

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.

Add MCP tool to list calendar events by date range

3 participants