Uh oh!
There was an error while loading. Please reload this page.
feat(sync): add Composio Todoist memory-sync pipeline - #137
Conversation
Add TodoistSyncPipeline for the Composio `todoist` toolkit, modeled on the document-shaped Linear/Google Calendar pipelines: single list action, content taken directly from the task payload with no secondary fetch. - Verified Composio action slug: TODOIST_GET_ALL_TASKS. - Unpaginated single-fetch: Todoist active-tasks returns a plain array with no page token, so max_pages defaults to 1 and next is always None. - Stable upsert key `todoist:<id>`; client-side dedup on id + created_at sort cursor for incremental behavior (server_side_depth false). - Documents carry taint external_sync via the shared document helper. Refs tinyhumansai#95
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
🚧 Files skipped from review as they are similar to previous changes (4)
📝 WalkthroughWalkthroughAdds an incremental ChangesTodoist synchronization
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant SyncFramework
participant TodoistSyncPipeline
participant ComposioClient
SyncFramework->>TodoistSyncPipeline: tick()
TodoistSyncPipeline->>ComposioClient: Execute TODOIST_GET_ALL_TASKS
ComposioClient-->>TodoistSyncPipeline: Return active tasks
TodoistSyncPipeline->>TodoistSyncPipeline: Deduplicate by ID and payload fingerprint
TodoistSyncPipeline-->>SyncFramework: Return SkillDocument records
Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
|
| Filename | Overview |
|---|---|
| src/memory/sync/composio/providers/todoist.rs | New TodoistSyncPipeline implementing IncrementalSource; payload-fingerprint dedup is sound, sort_cursor deliberately returns None, extract_page correctly handles both bare-array and object-wrapped responses after the client strips the outer data envelope. |
| tests/composio_sync_mock.rs | Three new integration tests cover task ingestion, bare-array response handling, idempotency (second tick is a no-op), and edit re-ingestion via fingerprint change — all exercising the key behaviors of the pipeline. |
| src/memory/sync/composio/mod.rs | Re-exports TodoistSyncPipeline; additive, no side effects. |
| src/memory/sync/composio/providers/mod.rs | Registers the todoist sub-module and re-exports TodoistSyncPipeline alongside other providers; change is mechanical and consistent with existing entries. |
| src/memory/sync/mod.rs | Adds TodoistSyncPipeline to the crate-level public re-export list; one-line additive change. |
Sequence Diagram
sequenceDiagram
participant Caller
participant TodoistSyncPipeline
participant Orchestrator as run_incremental_sync
participant Composio as Composio API
participant State as SyncState (persisted)
Caller->>TodoistSyncPipeline: tick(config, context)
TodoistSyncPipeline->>Orchestrator: run_incremental_sync(self, client, ...)
Orchestrator->>State: load synced_ids + cursor
Orchestrator->>Composio: TODOIST_GET_ALL_TASKS (no args)
Composio-->>Orchestrator: "{ successful:true, data: {tasks:[...]} }"
Orchestrator->>TodoistSyncPipeline: extract_page(data)
TodoistSyncPipeline-->>Orchestrator: "PageFetch { items:[...], next:None }"
loop each task
Orchestrator->>TodoistSyncPipeline: "dedup_key(item) → id@FNV(canonical)"
alt key already in synced_ids
Orchestrator-->>Orchestrator: skip
else new or edited task
Orchestrator->>TodoistSyncPipeline: document(item) → SkillDocument
Orchestrator->>State: mark_synced(key)
Orchestrator->>Caller: store document
end
end
Note over Orchestrator: sort_cursor=None → no cursor boundary check
Note over Orchestrator: stop_on_empty_pending=true → stop if all skipped
Orchestrator->>State: save synced_ids (no cursor advance)
Reviews (4): Last reviewed commit: "Merge main into feat/composio-todoist-dr..." | Re-trigger Greptile
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Address Greptile review on tinyhumansai#137. - Todoist tasks carry no modification timestamp, so keying dedup on the immutable `created_at` meant an edited task (content/due/project change) was never re-ingested. Key `dedup_key` on a payload fingerprint instead, and return `sort_cursor: None` — using `created_at` there would trip the orchestrator's cursor-boundary short-circuit and halt the scan on an edited task created before the persisted cursor. Freshness is now handled entirely by the fingerprint; `document_id` stays the stable `todoist:<id>`. - Remove the `page_size` struct field: it was written by `new`/`with_limits` but never read (Todoist active-tasks is unpaginated). `with_limits` keeps the sibling signature but the page-size argument is inert. - Add a mock test proving an edited task re-ingests without any timestamp change.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/memory/sync/composio/providers/todoist.rs (1)
35-49: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDocument the public constructors.
Add Rustdoc for
TodoistSyncPipeline::newandTodoistSyncPipeline::with_limits. Document that Todoist is unpaginated and that_page_sizehas no effect.As per coding guidelines, “Document public APIs, module contracts, and non-obvious behavior thoroughly.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/memory/sync/composio/providers/todoist.rs` around lines 35 - 49, Add Rustdoc comments to the public constructors TodoistSyncPipeline::new and TodoistSyncPipeline::with_limits. Describe their initialization/configuration behavior, explicitly note that Todoist active tasks are unpaginated, and document that the _page_size parameter is accepted for signature parity but has no effect.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/memory/sync/composio/providers/todoist.rs`:
- Around line 150-157: Update the document construction in the Todoist item
conversion flow to populate SkillDocument.content with the task’s text content
rather than the pretty-serialized item.raw JSON. Keep the existing document
metadata and raw payload unchanged, and remove the now-unneeded serialization
used solely for content.
- Around line 162-170: Replace DefaultHasher in payload_fingerprint with a
specified stable digest computed from canonical JSON bytes. Recursively
canonicalize serde_json::Value objects by sorting their keys before
serialization, then hash the resulting bytes with the chosen stable digest so
persisted dedup_key values remain consistent across toolchain and
feature-resolution changes.
In `@tests/composio_sync_mock.rs`:
- Around line 405-415: Extend the sync test around the captured documents to
assert that the first document’s content is exactly “Write report”, not
JSON-formatted task data. Add a separate mock response case where Composio’s
data value is the task array itself rather than an object under data.tasks, and
verify the pipeline handles that bare-array response contract.
---
Nitpick comments:
In `@src/memory/sync/composio/providers/todoist.rs`:
- Around line 35-49: Add Rustdoc comments to the public constructors
TodoistSyncPipeline::new and TodoistSyncPipeline::with_limits. Describe their
initialization/configuration behavior, explicitly note that Todoist active tasks
are unpaginated, and document that the _page_size parameter is accepted for
signature parity but has no effect.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 8ef1fc1f-0e08-491b-9fd5-d5d305f09fc7
📒 Files selected for processing (5)
src/memory/sync/composio/mod.rssrc/memory/sync/composio/providers/mod.rssrc/memory/sync/composio/providers/todoist.rssrc/memory/sync/mod.rstests/composio_sync_mock.rs
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Address CodeRabbit review round 2 on tinyhumansai#137. - payload_fingerprint: replace DefaultHasher (unspecified, unstable across Rust releases) with FNV-1a over a canonically-serialized payload (object keys sorted recursively). The dedup key is persisted in SyncState, so an unstable hash would silently re-ingest every task on a toolchain bump. - document(): store the task `content` (+ optional `description`) as the document body instead of pretty-printed JSON, so retrieval embeds task text. - extract_page(): handle the bare `data: [...]` array shape (already unwrapped by the client) in addition to the `tasks`/`items` wrappers. - Tests: assert document content is the task text, and add a bare-array case.
Resolve conflicts from Composio Google Calendar/Drive (tinyhumansai#134) and Docs/Sheets (tinyhumansai#135) landing alongside the Todoist sync pipeline: - union the pipeline re-exports in sync/composio/mod.rs and sync/mod.rs - union the test imports in tests/composio_sync_mock.rs All 19 composio_sync_mock tests pass (3 Todoist + 4 Google incl.).
Uh oh!
There was an error while loading. Please reload this page.
Summary
Adds
TodoistSyncPipelinefor the Composiotodoisttoolkit, document-shaped. Previously advertised but unsyncable (#106).TODOIST_GET_ALL_TASKS(verified againstcatalogs_productivity.rs). Todoist's active-tasks endpoint returns a plain array and is unpaginated, so this is an honest single-fetch — no invented page-token params;extract_pagehandles both thedata.taskswrapper and a baredataarray. Task text comes fromcontent; stable upsert keytodoist:<id>,taint = external_sync, content-free logging.Branch name mentions Dropbox, but Dropbox is deferred: the curated catalog exposes no clean
list_folder-style enumeration action, so shipping it would require guessing an action schema. Only Todoist is included here — no Dropbox stub was committed.Pipeline body only (step 1 of 2); openhuman wiring closes the issue end to end.
API Or Behavior Changes
One new public
SyncPipelinetype exported frommemory::sync. Additive.Tests
cargo fmt --checkcargo clippy --all-targets -- -D warningscargo build --all-targetscargo test(all-features green; new mock test asserts task ingestion fromcontent, stabledocument_id, and idempotent re-sync via global dedup)Documentation
Module/item docs, including a note that
page_sizeis unused (Todoist active-tasks is unpaginated). No external docs needed.Part of #95 · tracker #106
Summary by CodeRabbit
New Features
Bug Fixes
Tests