- name
- task-inbox-fill
- description
- Use when Justin asks to 'fill my inbox', 'sync my tasks', 'what am I missing in Todoist', or wants open actions from external sources (Slack, Gmail, Calendar, Obsidian daily notes, Linear) surfaced into his Todoist Inbox — without duplicating what's already there. Also detects potential calendar events and surfaces them as Google Calendar event candidates to schedule directly on his behalf. Accepts an optional lookback window (default 48h).
- platforms
- ["linux","macos"]
# 📥 Todoist Inbox Fill — ⚠️ DEPRECATED ⚠️
> [!warning] **DEPRECATION NOTICE: Transitioned to Obsidian TaskNotes (June 23, 2026)**
> Justin has stopped using Todoist entirely and transitioned his task management to **Obsidian TaskNotes**.
> - This skill is legacy/deprecated and should not be run for Todoist.
> - Any future automated task ingestion or "inbox fill" sweeps should target the Obsidian TaskNotes system (creating new task notes in `TaskNotes/Tasks` or checklists in daily notes for the EIIRP sweep), following the conventions in `/home/justin.guest/Developer/obsidian-vault/TaskNotes/Setup.md`.
Scan external sources for open actions Justin owns and needs to do. Deduplicate against what already exists in Todoist. Surface candidates to Justin for confirm/edit. Batch-add confirmed tasks to Inbox.
**This is not a sync.** It's a one-way capture pass. It does not complete, reschedule, or remove tasks. It only adds what's genuinely missing.
---
## Step 1 — Pre-flight
Determine the lookback window. If Justin specified one (e.g. "last 24 hours", "go back 3 days"), use it. Otherwise default to **48 hours**.
Compute dates and snapshot the current Todoist state. Do this in-context (not in a subagent).
```bash
TODAY=$(date +%F) # e.g. 2026-05-21
TOMORROW=$(date -d "$TODAY + 1 day" +%F)
TODAY_SLASH=$(date +%Y/%m/%d)
TOMORROW_SLASH=$(date -d "$TODAY + 1 day" +%Y/%m/%d)
WEEK_FROM_NOW=$(date -d "$TODAY + 7 days" +%F)
# Lookback window (default 48h = 2 days)
LOOKBACK_HOURS=48 # override if Justin specified something else
LOOKBACK_START=$(date -d "$TODAY - ${LOOKBACK_HOURS} hours" +%F)
LOOKBACK_START_SLASH=$(date -d "$TODAY - ${LOOKBACK_HOURS} hours" +%Y/%m/%d)
```
Pass `LOOKBACK_HOURS`, `LOOKBACK_START`, `LOOKBACK_START_SLASH`, and `LOOKBACK_ISO` (ISO 8601 datetime for Linear, e.g. `2026-05-21T00:00:00.000Z`) to subagents so they scope their searches to the right window.
Compute `LOOKBACK_ISO`:
```bash
LOOKBACK_ISO=$(date -u -d "${LOOKBACK_HOURS} hours ago" +%Y-%m-%dT%H:%M:%S.000Z)
```
Then snapshot **all open tasks** in Todoist so you can deduplicate later. Call both:
1. `find-tasks-by-date(startDate="today", daysCount=7, overdueOption="include-overdue", limit=100)`
2. `find-tasks(filter="no date", limit=100)`
Merge results and keep them in-context. You'll diff against this snapshot in Step 3. This two-call method is robust and completely avoids HTTP 400 errors caused by complex Todoist search query syntax.
---
## Step 2 — Gather candidates (parallel subagents)
Spawn one subagent per source in a **single batch**. Each subagent returns a compact bullet list of *open actions* — things Justin needs to do. Not FYIs. Not things others owe him (unless he needs to follow up). Not decisions already made.
Pass `TODAY`, `TOMORROW`, `TODAY_SLASH`, `TOMORROW_SLASH`, `WEEK_FROM_NOW`, `LOOKBACK_HOURS`, `LOOKBACK_START`, and `LOOKBACK_START_SLASH` into each subagent as verbatim substituted strings.
**Budget for every subagent: ≤8 tool calls. Return partial results and stop if budget is exhausted.**
Sources to cover in two/three batches (concurrency cap is 3):
- **Batch 1:** Slack, Gmail, Obsidian daily notes
- **Batch 2:** Calendar, Linear, Granola meeting notes
Sources: **Slack, Gmail, Obsidian daily notes, Calendar, Linear, Granola.**
**Do not pipe command output into a language interpreter** (no `... | python3 -c "..."`, no `... | bash`, no `... | node -e "..."`). The security scanner flags `cmd | python3` etc. as `pipe_to_interpreter` (HIGH) regardless of intent and will halt your run for approval. If you need to post-process JSON, use `jq` (installed). If you need real Python, write a short script to a tempfile and run it as `python3 /tmp/foo.py` — the file boundary is what satisfies the scanner.
---
### Subagent A — Slack (SignLab)
- **Toolsets:** `["terminal"]`
- **Skill:** `slack`
- **Goal:** Find open actions for Justin in Slack today. Budget: 8 tool calls.
- **Context:**
> Extract open actions from Slack for Justin. **Only surface messages where Justin has saved the message or set a Slack reminder** — these are the clearest signal of intent to act.
>
> Run one search:
> 1. `python3 ${HERMES_HOME:-$HOME/.hermes}/skills/social-media/slack/scripts/slack.py search '(has:reminder OR is:saved) after:<LOOKBACK_START>' --limit 50`
>
> `is:saved` matches messages Justin has explicitly added to his "Saved items" (formerly starred messages) list. `has:reminder` matches reminders. Together, these represent "saved Slack reminders/messages" Justin means to act on.
>
> Note: the word "reminder" appearing in message *text* (e.g. "Reminder: meeting at 3pm") can occasionally leak in via `has:reminder`. Drop these if they're clearly not reminder-flagged or saved — use context and channel to judge.
>
> **Command safety:** Do NOT pipe `slack.py` output into a language interpreter (`| python3 -c`, `| bash`, `| node -e`). The security scanner blocks these as `pipe_to_interpreter` (HIGH) and your run will halt for approval. If you need to inspect or reshape the JSON, use `jq`. Example:
> ```bash
> python3 ${HERMES_HOME:-$HOME/.hermes}/skills/social-media/slack/scripts/slack.py search '(has:reminder OR is:saved) after:<LOOKBACK_START>' --limit 50 \
> | jq -r '.[] | "\(.channel_name) | \(.username) | \(.permalink) | \(.text[:200])"'
> ```
> | jq -r '.[] | "\\(.channel_name) | \\(.username) | \\(.permalink) | \\(.text[:200])"'
> ```
> For non-trivial Python, write to a tempfile and run as `python3 /tmp/foo.py` — the file boundary is what satisfies the scanner.
>
> Format each as a candidate task:
> `- [Slack] <concise action> | context: <#channel or @person, brief what/why> | url: <permalink>`
>
> End with `Total: N candidates`.
>
> Budget: 8 tool calls. Return what you have and stop if budget exhausted.
---
### Subagent B — Gmail (work + personal-main)
- **Toolsets:** `["terminal"]`
- **Skill:** `google-workspace`
- **Goal:** Find emails where Justin owes a reply or has an explicit action item. Budget: 8 tool calls.
- **Context:**
> Use the wrapper at `${HERMES_HOME:-$HOME/.hermes}/skills/productivity/google-workspace/scripts/gws_multi.py`. Read-only — do NOT attempt sends.
>
> Run exactly two searches (no personal-junk unless Justin explicitly asked):
> 1. `gws_multi.py --account work gmail search 'after:<LOOKBACK_START_SLASH> before:<TOMORROW_SLASH> in:inbox -label:sent' --max 50`
> 2. `gws_multi.py --account personal-main gmail search 'after:<LOOKBACK_START_SLASH> before:<TOMORROW_SLASH> in:inbox -label:sent' --max 50`
>
> The `in:inbox` filter is critical — it excludes archived threads. **Do not surface tasks from archived emails.** If a thread has been archived, Justin has already processed it and it is not an open action.
>
> From results (subject, from, snippet only — do NOT fetch full bodies), extract:
> - Emails from humans that contain a direct question or request for Justin.
> - Emails Justin sent that contain a commitment or follow-up he still needs to do.
> - Threads where he's last-replied-to but hasn't responded.
>
> For each result, include the message ID or thread ID if available in the search output (needed to construct a Gmail link). Gmail deep links follow the pattern `https://mail.google.com/mail/u/0/#Inbox/<threadId>` — include this if you can derive it.
>
> Skip: automated notifications, CI/build alerts, shipping/delivery, marketing, newsletters, receipts, calendar invites, App Store Connect issue alerts, Todoist onboarding, Readwise, Substack.
>
> **Command safety:** Do NOT pipe `gws_multi.py` output into a language interpreter (`| python3 -c`, `| bash`, `| node -e`). The scanner blocks these. Use `jq` for JSON, or write a Python helper to `/tmp/foo.py` and run as `python3 /tmp/foo.py`.
>
> Format each candidate task:
> `- [Email/<account>] <concise action> | from: <sender> | subject: <subject>`
>
> End with `Total: N candidates (work: X, personal-main: Y)`.
>
> Budget: 8 tool calls. Return what you have and stop.
---
### Subagent G — Granola meeting notes (Next Steps for Justin)
- **Toolsets:** `["terminal", "file"]`
- **Skill:** `obsidian`
- **Goal:** Find Next Steps assigned to Justin in recent meeting notes. Budget: 8 tool calls.
- **Context:**
> Vault path: resolve from env `OBSIDIAN_VAULT_PATH` (fallback: `~/Documents/Obsidian Vault`). Meeting notes live under `<vault>/Inputs/Meetings/`. Each note filename starts with the meeting date (`YYYY-MM-DD`).
>
> Scan the `<vault>/Inputs/Meetings/` directory for `.md` files whose date prefix falls within the lookback window (`<LOOKBACK_START>` to `<TODAY>`).
>
> For each such meeting note:
> 1. Read the file.
> 2. Find any section with "Next Steps" or "Priorities" in its heading (e.g. `### Next Steps` or `### Priorities and Next Steps`).
> 3. Extract only lines that begin with `- Justin:` (case-insensitive) under that section. These are the action items assigned to Justin.
> 4. Skip all other lines (other people's action items, bullets under other sections).
>
> **Command safety:** Use `search_files` and `read_file` from the file toolset — do NOT use `grep` piped to `python3 -c` or similar (scanner blocks `pipe_to_interpreter`). Use `jq` if reshaping JSON.
>
> Format each candidate task:
> `- [Granola/<YYYY-MM-DD>] <action text (strip the "Justin: " prefix)> | meeting: <note title> | file: <relative path>`
>
> End with `Total: N candidates across M meeting notes`.
>
> Budget: 8 tool calls. Return what you have and stop.
---
### Subagent C — Obsidian daily notes (lookback window)
- **Toolsets:** `["terminal", "file"]`
- **Skill:** `obsidian`
- **Goal:** Find uncaptured open actions in recent daily notes. Budget: 8 tool calls.
- **Context:**
> Read `OBSIDIAN_VAULT_PATH` from env (fallback: `~/Documents/Obsidian Vault`). Daily note filename format: `YYYY-MM-DD DayName.md`. Current notes live in the vault root; older ones in `Daily Notes/`.
>
> **Capture convention:** Raw `- [ ]` checkboxes in daily notes are the intended quick-capture format for tasks. Some of these may already have been converted to TaskNotes (in-vault task files) by the vault hygiene EIIRP sweep — converted tasks appear as wikilinks in the form `[[TaskNotes/Tasks/slug-timestamp]]` rather than as raw checkboxes.
>
> For each of the last N days matching the lookback window (starting today, <TODAY>, going back to <LOOKBACK_START>), find and read the daily note if it exists.
>
> From each note, extract **only**:
> - Unchecked task items: lines matching `- [ ]` that are not marked done.
> - Lines containing explicit first-person commitment phrases: "I need to", "I need to remember", "I have to", "I should", "I must", "don't forget", "follow up on", "remind me".
>
> Do NOT infer tasks from section headings, bullet points under "Open Questions" or "Blockers", or general observations. Only literal `- [ ]` items and lines with the above explicit keywords qualify.
>
> Skip:
> - `- [x]` lines (completed)
> - Headings, pure notes, observations, decisions, highlights
> - Any line that is already a TaskNote wikilink (contains `[[TaskNotes/Tasks/`) — these are already captured in-vault and do not need a separate Todoist task unless Justin explicitly indicates otherwise
>
> Raw `- [ ]` lines that remain unconverted are still valid Todoist candidates — surface them as normal.
>
> **Command safety:** Use the `file` toolset and `search_files` to read notes — do NOT pipe shell output into `python3 -c` / `bash` / `node -e` (scanner blocks `pipe_to_interpreter`).
>
> Format each candidate task:
> `- [Obsidian/<date>] <action text> | note: <YYYY-MM-DD DayName.md>`
>
> End with `Total: N candidates across M notes`.
>
> Budget: 8 tool calls. Return what you have and stop.
---
### Subagent H — Obsidian Vault Notes (New & Modified)
- **Toolsets:** `["terminal"]`
- **Skill:** `obsidian`
- **Goal:** Find open actions, commitments, and suggest follow-up/development tasks for new and modified notes in the vault. Budget: 5 tool calls.
- **Context:**
> Run the specialized scanning script:
> ```bash
> python3 ~/.hermes/scripts/fetch_vault_notes_candidates.py --lookback-hours <LOOKBACK_HOURS> --json
> ```
> (Replace `<LOOKBACK_HOURS>` with the lookback hours value, e.g. `48` or whatever is set for this run).
>
> This script scans all non-daily, non-Granola notes in the vault that were created or modified since the previous run. It extracts unchecked tasks (`- [ ]`), commitment statements, and suggests follow-up/development tasks for newly created notes.
>
> **Task naming rules:**
> Format each candidate task exactly as returned by the script:
> - Tasks/Commitments: `- [Obsidian] <description> | context: <context>`
> - Development Suggestions: `- [Obsidian] <description> | context: <context>`
>
> End with `Total: N candidates`.
>
> Budget: 5 tool calls. Return what you have and stop.
---
### Subagent — Linear
- **Toolsets:** `["terminal"]`
- **Skill:** `linear`
- **Goal:** Find Linear issues assigned to Justin (To Do / In Progress) and new Triage issues from within the lookback window. Budget: 8 tool calls.
- **Context:**
> Use `curl` against `https://api.linear.app/graphql` with `Authorization: $LINEAR_API_KEY`.
>
GitHubで見る