Agentic Tasks — Message Intake
Reads incoming messages from messaging tools addressed to the current user and auto-converts them into Notion tasks. read-only: Does not send any messages. Only creates tasks.
Cowork Scheduled Task Setup
To run automatically every morning via Cowork:
- Cowork → Scheduled Tasks → New
- Trigger: Daily / 09:00 (user's timezone)
- Prompt:
Run the ingesting-messages skill
Step 0: Preparation
Provider Detection + Identity Resolve
- Load
${CLAUDE_PLUGIN_ROOT}/skills/detecting-provider/SKILL.md→active_provider. Skip if set. - Load
${CLAUDE_PLUGIN_ROOT}/skills/resolving-identity/SKILL.md:- Obtain
current_user(for message filtering). - Obtain
org_members(for Category C: identifying assignees).
- Obtain
Messaging MCP Auto-Detection
Inspect available MCP tools and determine which messaging tool to use:
| Tool group | Service |
|---|---|
slack-* tools exist |
Slack |
teams-* or ms-teams-* tools exist |
Microsoft Teams |
discord-* tools exist |
Discord |
- Multiple detected: Use AskUserQuestion to ask "Which messaging service would you like to use?"
- None detected: Stop and inform "No messaging MCP is configured. Please set up a Slack/Teams/Discord MCP."
Message Intake Log Preparation
The Intake Log is a Notion database (Intake Log) that tracks which messages have already been processed.
- Read the config page and check for
intakeLogDatabaseId. - If
intakeLogDatabaseIdis missing or the database does not exist:- Create a new database named "Intake Log" under the Agentic Tasks parent page using
notion-create-database. - Schema:
Property Notion Type Description Message ID title Message unique ID (e.g. Slack: channel_id:ts)Tool Name select slack/teams/discordProcessed At date Processing timestamp - Write the new database ID back to the config page as
intakeLogDatabaseId.
- Create a new database named "Intake Log" under the Agentic Tasks parent page using
- Load
processed_message_ids: queryintakeLogDatabaseIdvianotion-searchand collect all existing Message ID values. - FIFO cleanup: If the Intake Log has more than 1000 entries, delete the oldest records (by Processed At) until the count is at or below 1000.
Step 1: Fetch Unprocessed Messages
Use the detected Messaging MCP to retrieve all messages from the past 24 hours addressed to current_user via a multi-query strategy:
1a. Search Intent (platform-agnostic)
Retrieve every message from the past 24 hours that is directed at or contextually relevant to current_user:
- DMs: Direct messages sent to self
- Channel mentions: Messages in channels/groups that @-mention
current_user - Thread participant replies: New replies in threads where
current_userhas participated (started or replied), even if no @-mention is present
1b. Slack Query Example
- Query 1 (DMs): Search with
to:me - Query 2 (Channel mentions): Search for messages containing
<@USER_ID>(thecurrent_user's Slack user ID). Exclude own messages. Search scope must include both public and private channels the user is a member of. If the MCP tool has a channel-type filter, ensureprivate/mpim/imtypes are included alongsidepublic_channel. - Query 3 (Thread participant replies):
- From Query 1, Query 2, and a
from:mesearch (past 24h), collect allthread_tsvalues of threadscurrent_userparticipates in - Fetch replies for each thread
- Exclude own messages and already-processed messages
- If the MCP does not support thread-level queries, skip Query 3 and note it in the summary
- From Query 1, Query 2, and a
1c. Common Filters (applied after merge)
id ∉ processed_message_ids- If bot message (has
bot_idor bot-relatedsubtype): keep only if it @-mentionscurrent_user; discard otherwise - Not sent by self
1d. Deduplication
Merge all query results and deduplicate by message unique ID (Slack: channel_id:ts).
1e. Platform Notes
- Teams / Discord: Translate to equivalent APIs. The intent (DMs + mentions + thread participant replies) is the same.
- Thread queries unsupported: Skip Query 3 and add
(thread check: skipped — MCP does not support thread queries)to the summary.
Step 2: Classify Messages (3 Categories)
Classify each message into one of 3 categories:
| Category | Criteria | Action |
|---|---|---|
| A: Hearing Needed | Insufficient info, question format, ambiguous request, seeking approval | Main task (Status=Blocked) + Blocker task (Status=Ready, executor=human, Assignees=requester) |
| B: Self-Action | AI-processable implementation, research, documentation, clear work request | Task (Status=Ready, executor=cowork or claude-code, Assignees=self) |
| C: Delegate | Clearly intended for another team member (name explicitly mentioned, etc.) | Task (Status=Backlog, executor=human, Assignees=assignee) |
When classification is unclear: Treat as Category A (safe default).
Classification Heuristics and Examples
Category A (Hearing Needed) — default when uncertain:
- Question format: "Can you …?", "What's the status of …?"
- Approval requests: "Review and approve this"
- References context the AI does not have: "about that thing we discussed yesterday"
- Example:
"Hey, can you look at the design doc and let me know if the approach works?"→ A (which document? what feedback criteria?)
Category B (Self-Action) — clear and actionable:
- Specific work request: "Write unit tests for the auth module"
- Research / summary: "Compile the Q3 metrics report"
- Implementation request with sufficient context to start
- Example:
"Please update the README to include the new API endpoints"→ B
Category C (Delegate) — explicitly addressed to another member:
- Names another member: "Ask @alice to …"
- Current user is CC; the action owner is someone else
- Example:
"@you FYI — @bob needs to update his deployment script"→ C (action owner is Bob)
Decision rule: If torn between B and A → choose A. If torn between C and A → choose A. A is always the safe default.
Step 2.5: Enrich Task Details (Category B/C)
Before creating tasks, enrich Category B and C messages with additional details via AskUserQuestion.
Category B (Self-Action) — ask:
- Acceptance Criteria: What are the completion conditions?
- Working Directory: Which repository / directory to work in?
- Execution Plan: Any specific approach or constraints?
- Context: Additional background information?
Category C (Delegate) — ask:
- Acceptance Criteria: What is the expected deliverable?
- Context: Background info for the assignee (or their agent)
- Due Date: Any deadline?
How to ask:
- If there are multiple B/C messages, batch them into a single
AskUserQuestioncall (do not ask per-message) - If the user replies "as-is" or equivalent, proceed with only the information from the original message
- Incorporate answers into the task fields when creating tasks in Step 3
Step 3: Bulk Task Creation
Create tasks directly via notion-create-pages for each message (do not go through the managing-tasks skill).
Pre-Creation Dedup Check
Before creating each task:
- Generate
source_message_idfrom the message unique ID (Slack:channel_id:ts) - Query the Tasks DB via
notion-searchfor existing tasks with the sameSource Message ID - If a matching task exists: skip the message and count it as
Skipped (already exists as task) - If no match: include
Source Message IDin the created task's fields
Common Fields
| Field | Value |
|---|---|
| Title | From @{sender}: {message summary (50 chars max)} |
| Description | Full original message + append Source: {tool_name} DM from @{sender} at {datetime} |
| Tags | ["ingesting-messages"] |
| Context | Received via {tool_name} on {date} |
| Issuer | [current_user] |
Category-Specific Fields
Category A (Hearing Needed):
- Create the blocker task first:
- Title:
[Hearing] Confirm with {requester_name}: {question summary} - Status:
Ready - Executor: If messaging MCP available (e.g. Slack tools) →
cowork(agent can send follow-up messages). If no messaging MCP →human(requires manual contact). - Assignees:
[requester](Load${CLAUDE_PLUGIN_ROOT}/skills/looking-up-members/SKILL.mdto resolve). If requester cannot be identified:[current_user](fallback, not empty). Record "Original sender: {sender}" in Context. - Acceptance Criteria:
"Confirm with {requester_name} about {topic_summary}. Record response in Agent Output. Update Status to Done when confirmed." - Execution Plan:
"1. Contact {requester_name} via {tool_name}\n2. Ask about: {question_summary}\n3. Record response in Agent Output\n4. Update Status to Done"
- Title:
- Create the main task:
- Status:
Blocked - Blocked By:
[blocker_task_id] - Executor: Determine from message content — if the required action (after hearing) is clearly code/research/docs, infer executor (claude-code or cowork based on execution_environment). If unclear or requires human judgment →
human(default, re-evaluated in daily routine Step 2.5 when unblocked). - Assignees:
[current_user] - Acceptance Criteria: Derive from message content. Fallback:
"[DRAFT — update after hearing] Determine required action from {requester_name}'s response and complete it."
- Status:
Category B (Self-Action):
- Status:
Ready - Executor: Determine from environment and context:
execution_environment = "cowork": Default for AI-executed tasks iscoworkexecution_environment = "claude-code": Code work →claude-code, external integrations →cowork
- Assignees:
[current_user] - Working Directory: Empty (user sets later)
Category C (Delegate):
- Status:
Backlog - Executor:
human(always fixed to human when assigned to others) - Assignees: Load
${CLAUDE_PLUGIN_ROOT}/skills/looking-up-members/SKILL.mdto resolve assignee- If assignee cannot be identified:
[current_user](fallback, not empty). Record "Expected assignee: {name or hint}" in Context
- If assignee cannot be identified:
- Working Directory: Empty (other person's filesystem unknown)
- Branch: Empty (other person's git environment unknown)
Step 4: Log Update + View Server Push
- For each processed message, create a record in the Intake Log DB via
notion-create-pages:- Message ID: the message unique ID (e.g.
channel_id:ts) - Tool Name: the messaging tool name (e.g.
slack) - Processed At: current timestamp
- Message ID: the message unique ID (e.g.
- If the Intake Log DB exceeds 1000 entries, delete the oldest records (by Processed At) to bring it back to 1000.
- Push data to view server:
# Silently skip if server is not running
curl -s http://localhost:3456/api/health -o /dev/null 2>/dev/null && \
curl -s -X POST http://localhost:3456/api/data \
-H "Content-Type: application/json" -d '<tasks_json>' -o /dev/null 2>/dev/null || true
Step 5: Summary Output
[Message Intake Complete] via {tool_name}
Processed: N / Skipped (already processed): K / Skipped (already exists as task): J
A (Hearing Needed): X → Blocked tasks + Blocker tasks created
B (Self-Action): Y → Ready tasks created
C (Delegate): Z → Backlog tasks created
Language
Always respond in the user's language.