Omniscio documentation
Browse all documentation
  1. Getting Started13
  2. Sessions & Agents115
  3. Inbox & Notifications59
  4. Projects & Tasks95
  5. Automation & Scheduling75
  6. Knowledge & Memory26
  7. AI Features60
  8. Integrations100
  9. Plugins & Marketplace33
  10. Cloud & Teams56
  11. Settings & Customization58
  12. Account & Billing28
  13. Troubleshooting84
  14. CLI & API Reference22
  15. Legal & Policies4
  16. Uncategorised22

Drip (queue-and-trickle inbox feeder)

Drip holds mixed content — notes, links, files, whole folders — in named queues and releases items into your inbox on a cadence you set, so a sixty-link reading list arrives a few at a time instead of all at once.

What it is

Drip is a queue-and-trickle inbox feeder. You stash mixed content — short text notes, links you want to revisit, files you want to remember to look at, folders you want walked for new files — into named drips, and Drip releases items into your inbox on a cadence you control. One drip = one named queue + one cadence (e.g. "Weekend reading — Saturdays at 9am") + the pending pile of items waiting to be released. The metaphor is a slow drip rather than a firehose: instead of dumping a 60-link reading list into your inbox at once, you let three a week trickle in.

Drip is on by default under Settings → Features (flag dripEnabled). When enabled, a "Drip" virtual project appears in the Omniscio sidebar group with the standard amc-builtin treatment — sidebar row, virtual project pane, settings search hit at Settings → Features → Enable Drip.

dripEnabled is a full kill switch. Switching Drip off hides the sidebar row and every Drip surface, and it also stops the release scanner: the 60-second tick stands down, so nothing further lands in your inbox. Your drips and their pending items are untouched — switching it back on resumes exactly where they left off, with no restart. This is separate from Global Pause, which halts releasing while leaving the feature and its surfaces in place, so use the feature toggle to put Drip away entirely and Global Pause to take a break from it.

A drip has three statuses: active (the cron fires on cadence, releasing itemsPerRelease items per tick from the head of the pending queue), paused (cron is suspended; the user explicitly stopped it, but pending items survive so the drip can be resumed without losing state), and archived (the scanner has released the last pending item and auto-transitioned the drip to archived — distinguishes "you stopped me" from "I'm out of things to give you"). archived is NOT strictly terminal: adding a releasable item back into an auto-archived drip wakes it to active so the item releases (reactivateArchivedDripOnAdd), whereas a user-paused drip is never auto-woken — see the drip add-path contract. Pausing a single released item via the standard inbox snooze can optionally bump a pendingSkipCount (per-drip "skip the next release" knob via the skipNextOnSnooze toggle) so the user can buy breathing room without pausing the whole drip.

Items are typed via a discriminator: text (free-form prose / markdown lives in textContent), link (URL lives in textContent with best-effort og: metadata in linkTitle / linkDescription / linkImageUrl), file (the file lives on disk under userData/drip/<dripId>/ with original-name preserved in fileName and the on-disk path in filePath), book-pages (a single releasable unit — one PDF page, one EPUB chapter, or one ~400-word run — of a drip_book_sources row, referenced by FK with book_unit_start / book_unit_end selecting the slice), completion (a synthetic terminal item the scanner emits exactly once when a drip drains, so the user sees a "queue empty" inbox card instead of the drip silently going quiet), and divider (an inert visual separator with an optional short label in textContent — "Week 2", "Phase 2", "Done in 2026" — that the user drops anywhere in the pending queue to group items; the scanner NEVER releases a divider, so it lives in the management view as a permanent layout aid and never reaches the inbox).

A drip can also have folder sources — watched directories on disk that the folder scanner walks on a slow cadence. New files in a watched folder are copied into the drip's userData/drip/<dripId>/ storage (so the original file can move or be deleted later without breaking the drip) and queued as file items with sourceKind: 'folder'. The drip_folder_seen_files bookkeeping table tracks (filePath, mtime, fileSize) so the scanner doesn't re-queue unchanged files on every walk.

A folder can also be imported once rather than watched. The Add-folder form has a One-time import checkbox: with it ticked, submitting walks the directory a single time, copies every matching file into the drip's userData/drip/<dripId>/ storage, and queues them as ordinary manual file items — but no watched-source row is persisted and no drip_folder_seen_files ledger is written, so the folder is never scanned again. The IPC channel is DRIP_FOLDER_IMPORT_ONCE (the one-shot sibling of DRIP_FOLDER_SOURCE_ADD); its handler returns an { added, errors, error } result so a per-file copy failure is reported without aborting the whole import. Use a one-time import for "queue this folder's current contents and forget it"; use a watched source for "keep feeding me new files as they land here."

A drip can also have book sources — PDFs and EPUBs that the drip releases one unit at a time rather than as a single file item. The drip_book_sources row carries the on-disk path under userData/drip/<dripId>/books/<sourceId>/, the kind (pdf = one page per release, epub-chapter = one spine item per release, epub-word = one ~400-word chunk per release across the concatenated spine), and total_units (page count / spine length / chunk count, computed once at add time via the drip-book-extractor-service). Each scheduled release inserts a book-pages-typed drip_item whose book_source_id points at the source and whose book_unit_start / book_unit_end window selects the slice to extract at release time — the full book never sits in memory between cadence ticks. Magic-byte sniff (%PDF- for PDFs, ZIP-with-mimetype-first for EPUBs) gates the kind so a .pdf extension over an .exe body can't reach the extractor; failed extractions surface as a typed success: false reason in the IPC envelope, the file is GC'd by the Phase 4 sweep.

The half of this page that carries the mechanics — the tables and services a drip is built from, the CLI surface that drives it, and what the inbox does with a released item — is Drip (part 2).

Where to find it

How to use it

  1. Turn it on. Settings → Features → Enable Drip is on by default; turning it off hides the sidebar row and silences the scanner.
  2. Open the Drip virtual project in the sidebar. The first launch shows an empty state with + New drip.
  3. Create a drip. Pick a name, set a cadence (progressive-disclosure listbox — one input box, a list of presets beneath it that filters live as you type, exactly like the Snooze palette), set itemsPerRelease (default 1), toggle skipNextOnSnooze (default off). Typing e highlights every preset starting with "Every…"; typing every fri narrows to Friday entries; typing a full plain-English cadence the parser understands ("every weekday at 9am") shows a single Custom row at the bottom of the list with the resolved cron beside it; pasting a 5-field cron expression like 0 9 * * 1-5 is auto-detected (no separate "Raw cron" mode). Press ↑/↓ + Enter to pick from the list. Nothing turns red until you actually hit Save with something the parser can't resolve — the old eager "Couldn't read a recurrence pattern" toast that fired on the first keystroke is gone. The form also exposes two optional Start-session defaults: a Session prompt preset (leads the opening prompt whenever you launch a Claude Code session from one of this drip's inbox items) and a Default repo (which repo that session opens in — leave it at Claude (default) to decide at launch instead). Both are optional; a blank prompt or the Claude (default) repo stores no saved default.
  4. Add items. Paste text, drop a link, attach a file, or add a folder source. The add-item zone carries three buttons beside the text box — Attach files, Bulk add, and Divider. Bulk add opens a modal with an editable textarea: paste or type a list and a split preview re-parses live as you edit (debounced ~250 ms), so you can fix a URL split across two lines or delete a blank row before committing — then Split the content into separate queue items or Add as single entry. The same modal opens automatically when you paste something long into the add box (titled "Bulk paste detected" in that case, "Bulk add" when opened from the button). Divider inserts an inert labelled separator ("Week 2", "Phase 2") to group the queue visually — dividers reorder by drag and can be deleted, but are never released into the inbox. When adding a folder source, the One-time import checkbox flips the form from "watch this folder" to "copy its current files once and stop" (the submit button relabels to Import once). The drip's management pane shows the pending queue with reordering, plus four segmented tabs — Pending (default), Released (audit trail of items already trickled), Archived (items the user archived from the inbox; each row has a Restore button that puts it back into the inbox unread), and All (everything regardless of state).
  5. Receive on cadence. Each release fires a DRIP_ITEM_RELEASED push, the item appears in the inbox as a drip integration row (header shows DRIP with a cyan droplet icon, sourced from the DRIP_PROJECT_ID branch in src/renderer/src/features/dashboard/inbox-helpers.ts), and the standard inbox snooze / archive actions work on it. Clicking a row opens it in the Drip inbox viewer on the right: text items render as Markdown through the same shared alert text body every inbox alert uses (the chat-message renderer), link items render a clickable og: preview card (title / description / hero image, with a graceful image-fallback) via LinkPreviewCard, image file items render the picture inline (the bytes are lazy-loaded from userData/drip/ as a data URL via the read-only DRIP_ITEM_FILE_READ channel and shown through ReservedImage in src/renderer/src/features/drip/DripInlineImage.tsx, falling back to the pill PLUS an honest "could not be loaded — it may have been removed" note on a read error, e.g. the backing file was deleted), HTML file items (.html/.htm) render LIVE in a sandboxed preview — styling and JavaScript executing — when Render HTML files live is on (see "Live HTML preview" below), video / audio / PDF file items now play or preview INLINE (streamed over the drip-media:// scheme — see "Inline media & openable files" below), any OTHER file item shows an openable card (Open in the OS default app / Reveal in folder on desktop, or a download link on mobile) rather than a dead name / mime / size pill, and the synthetic "queue drained" item shows a success message. Every releasable item's viewer also carries a Start session button (a MessageSquarePlus icon beside Archive): clicking it — or pressing N while the item is open — opens a small dialog that pre-fills an opening prompt composed from the item's context (led by the drip's saved preset, if any) and a target repo (the drip's saved default if it still exists and is spawnable, otherwise the Claude repo), both editable; confirming spawns a Claude Code session already focused on that item. It is the shared StartSessionButton (the same one every agent alert carries), so the bare-N shortcut appears on the button's hover tooltip — Omniscio's universal way of showing a control's key, never an always-visible badge. The button is hidden on the synthetic "queue drained" card and disabled — with a repo-mention tooltip — if you have no spawnable repo (N does nothing then either). The viewer's footer action bar carries an "Edit drip" button (shown for every card except the drained "queue empty" sentinel) that opens that drip's editor — the same name / cadence / repeat-forever / session-prompt / repo CadenceModal the Drips panel uses — right over the inbox, so a released item is one click from editing its schedule and every setting. The editor opened this way also gains a confirm-gated Delete drip button (footer, left of Cancel/Save): confirming deletes the whole drip and all its inbox cards, then advances the inbox to the next item — the inbox is now a full path to manage the drip, not just view it (it was previously the only Drip surface with no way back to the drip). The header drip name is also a link to the same editor (a secondary affordance alongside the footer's Edit button). The one exception is the synthetic "queue drained" card, whose name + pencil are absent (an auto-archived drip has nothing useful to edit); and a drip that can't be loaded (e.g. it was deleted out from under the item) reports a toast rather than a dead click. The name-link lives in the header title slot and the Edit button in the footer action bar (locked by inbox-alert-contract I8/I18), so the edit affordance never crowds Snooze / Archive and a phone never strands a lone pencil floating in the header's stacked actions row (where the snooze + archive pins leave it) below the title. Deleting a drip prunes its cards from the inbox slice instantly (independent of the debounced DRIP_CHANGED refresh, so it also clears on mobile); the whole-drip delete is deleteDrip in drip-store.ts (returns a boolean, rolls both slices back on failure). The DRIP_ARCHIVED push fires once when the queue drains and the drip auto-transitions to archived.
  6. Archive and restore inbox items. Archiving a drip inbox row — pressing E, middle-clicking the sidebar row on desktop, the detail viewer's Archive button, or the mobile swipe — calls drip:item-archive and, from any of those surfaces, arms a Ctrl+Z undo labelled Archived "<drip name>", then navigates to the next inbox row via navigateAfterInboxDripItemDismiss. The undo is registered inside the store's archiveInboxItem (not the individual archive surfaces), so no surface can forget it — press Ctrl+Z right after archiving to bring the item back. The archived item disappears from the inbox but is preserved on the row — it shows up in the drip management pane's Archived tab with a Restore button that calls drip:item-archive-undo to unset inbox_archived_at and bring it back to the inbox (releasedAt is preserved; the inbox treats it as freshly-released again). The symmetric archiveInboxItem / unarchiveInboxItem actions (both the store-armed undo and the Restore button) live in drip-store.ts.

How it behaves

Repeat forever (loop)

A drip can be set to repeat forever via the loop flag (the "Repeat forever" toggle in the cadence form; column drips.loop, default 0, migration 20260622132725). A loop drip never archives on drain: instead of consuming its queue once and transitioning to archived when empty, it re-delivers its queued item(s) on every cadence tick, indefinitely — turning a one-shot trickle into a recurring reminder.

Mechanically, a loop drip's queued items are perpetual templates — they stay pending and are never consumed. On each tick the scanner clones the next itemsPerRelease template(s) (round-robin, cursor = totalReleased % templateCount) into fresh released drip_items + inbox alerts, so every delivery is a distinct, archivable card. Dividers are skipped; an empty / divider-only queue simply reschedules (it never archives, and never divides by zero). Both the normal trickle path and the loop path go through one shared releaseDripItemAsAlert helper in drip-scanner-service.ts, so the alert reuses the clone's drip_item id (archive-sync invariant preserved) and the two paths can't drift. The loop path still advances next_run_at to the next cron slot every tick — without that the 60s scanner would re-fire every minute and flood the inbox.

Round-robin cannot pin a specific item to a specific time (which template lands on which tick depends on first-fire parity), so for a time-pinned schedule (e.g. photo A at 9 AM, photo B at 4 PM) use two single-item loop drips, one per slot. Trade-off: clone-on-release grows drip_items + inbox_alert_items by ~1 row/tick (trivial for SQLite; the inbox surface is capped at 200 and excludes archived). Full invariants + the tests that lock them: .claude/memory/contracts/drip-loop-contract.md.

Missing-file resilience. A file item whose backing file under userData/drip/<dripId>/ was deleted out from under the drip (e.g. the 2026-06-24 cross-instance wipe) is NOT released as a silent dead paperclip chip. The shared releaseDripItemAsAlert chokepoint checks existsSync and instead releases a clear "file unavailable — re-add it" text card; for a loop drip it also pauses the drip (status → paused) so it stops firing a blank card every cadence until the user re-adds the file. Already-released dead cards render an honest "could not be loaded" note in the inbox image viewer (above). The deletion itself is prevented at the source — see .claude/memory/postmortems/drip-datadir-cross-instance-deletion-postmortem.md.

DATA_DIR-move portability. A drip file item stores an absolute path in drip_items.file_path, but that directory prefix is advisory: the file always lives at <currentDripRoot>/<dripId>/<leaf>. Every drip-file read resolves the stored path against the current drip root via the one chokepoint readDripItemFile (→ resolveDripItemFilePath, re-derived from dripId + the filename), the scanner's presence check re-bases the same way, and the GC compares orphans by basename — so moving the database / DATA_DIR (e.g. the 2026-07-13 C:→H: move) does NOT make a present file read as missing/unreadable, falsely pause a loop drip, or get the moved files GC-deleted. This mirrors the already-portable drip_book_sources.path (stored relative). The security containment in readDripFile is unchanged; the re-base happens upstream. Locked by the move-portability tests in drip-file-storage, drip-gc, drip-scanner, serve-drip-preview, and the file-read handler; full detail in the postmortem above.

next_run_at lifecycle

The scanner's listActiveDripsReadyToFire selector reads drips.next_run_at and skips any drip where it's NULL — so the column MUST be populated for the drip to ever fire. The CRUD handlers (not the scanner) own the lifecycle:

  • drip:create computes next_run_at via computeNextRunAt(cronExpression, Date.now()) and persists it on the new row.
  • drip:update recomputes when cronExpression changes (from "now", to honor the user's intent that the new cadence starts ticking now).
  • drip:setStatus paused → active recomputes from "now" so a drip paused for days doesn't dump a backlog on resume.
  • drip:setStatus active → paused clears next_run_at to NULL.
  • drip:setStatus active → active (idempotent or other transitions) preserves the existing next_run_at.

The scanner's tick has a defensive self-heal pass: any active drip with next_run_at IS NULL gets one written from computeNextRunAt(cronExpression, Date.now()) and a warning logged. Self-terminating — the per-drip release txn always advances next_run_at or archives the drip, so the warning never loops. This catches drips on a real user's machine that were created during the pre-fix window where the handlers didn't populate next_run_at.

The full contract — including the original 2026-05-17 bug where handlers shipped without next_run_at population, the 2026-05-20 fix, and the regression-lock tests in tests/unit/ipc/drip-handlers-next-run-at.test.ts — is in .claude/memory/postmortems/drip-next-run-at-handler-gap-postmortem.md.

Retention and permanent deletion

Drip runs an hourly janitor — the drip-gc periodic service, started at every app launch by src/main/startup/tasks/1900-drip-gc.ts and not gated behind the Drip feature flag, so it sweeps whether or not you still use Drip. Two independent passes run each hour, plus one bookkeeping prune. The App tells you at delete time that a deleted drip cannot be undone; what no surface has said is what the janitor removes on its own.

Deleting a drip is a 7-day hold, then a permanent purge. Deleting a drip marks it deleted and cascades that mark to its items, folder sources, book sources and released inbox cards. There is no undelete surface in the App — the confirm dialog says so plainly. Once a drip has been deleted for more than 7 days, the janitor permanently deletes every row that references it (drip_folder_seen_files → drip_folder_sources → drip_items → drips) and wipes its entire on-disk folder, userData/drip/<dripId>/ — every file copied in from a watched folder, and any imported PDF/EPUB under books/. Past day 7 the content is gone: there is no trash folder and no recovery from inside the App.

Orphaned files inside a LIVE drip are removed after 24 hours — with no action from you. For every drip that has not been deleted, the janitor lists the files in userData/drip/<dripId>/ and deletes any file that no undeleted item points at whose last-modified time is older than 24 hours. You never deleted that file; the janitor did. The 24-hour floor exists because the folder scanner copies a file in before it writes the item row, so a tick landing mid-copy must not delete the file that was just placed.

Two things are deliberately safe from that sweep. A file referenced by any item that has not been deleted is kept — including an item you archived from the inbox, since archiving is not a delete and leaves the item (and its file) referenced. And the original in a watched folder is never touched: the scanner always copies a file into the drip's own storage rather than moving it, so neither deleting a drip nor the orphan sweep can reach the folder you pointed at.

The watched-folder dedup ledger is pruned after 30 days. drip_folder_seen_files records the files the folder scanner has already queued, so it does not re-queue unchanged files on every walk. A row whose last_seen_at is older than 30 days means that file has left the watched folder, so the row is dropped. This removes bookkeeping only — it never deletes an item or a file.

Trickle tasks from Tasks

Drip is also the engine behind "Trickle to inbox…" in Tasks (gated, in-development). From a project (its open tasks) or a hand-picked set of tasks, that action creates a NEW Drip pre-loaded with those tasks as text items, so they trickle into the inbox a few at a time on a cadence you pick — reusing everything here (cadence, items-per-release, repeat-forever, pause/resume, snooze, the management pane). It's free (a local task ranker, no AI).

Two small additions make a drip "task-aware". Each task item carries a back-reference to its tasks_v2 row (drip_items.source_task_id), and the per-trickle "archive each task once it's sent to my inbox" choice is stored on the drip (drips.archive_source_on_release). At release the scanner consults one seam, drip-task-source.ts: a task that's been finished, deleted, or archived since the snapshot drains silently (no inbox row — it never nags you about done work), and an open task is archived (cascade, reversible) when the trickle opted in. Archive-on-release is forced off for a loop (repeat-forever) drip — a standing reminder must not archive its own source — and a loop clone copies the back-reference so a task finished mid-flight stops re-dripping too. The presence of source_task_id (not a new source_kind) is the "this is a task item" marker, so the two columns are plain additive ones — no table rebuild. Full invariants + the tests that lock them: .claude/memory/contracts/tasks-v2-trickle-contract.md.

For agents

The user-facing half of Drip is on this page. The internals — the five tables and the data model behind a drip, the shared types, the book extractor, and the full CLI surface — are in Drip (part 2).

Related

  • Drip (part 2) — the internals: the five tables and the data model, the shared types, the book extractor, and the full CLI surface.
  • snooze-a-session.md — the snooze and time-parser machinery Drip reuses for cron input.
  • inbox-overview.md — how released Drip rows render alongside other inbox rows.

Last verified 2026-10-02