ワンクリックで
ea-scheduling
Meeting-mail scheduling. Kanban ea-sched-*; Calendly fallback.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Meeting-mail scheduling. Kanban ea-sched-*; Calendly fallback.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Search Desktop files via gbrain; mail use joshu-mail.
Find/search mail; local cache first, deep server Gmail.
jMail desktop UI workflows — compose drafts, thread navigation, voice fast paths.
Daily handoff — prep yesterday carryover, interactive morning review, finalize today's time block.
Triage mail to Projects. Not drips—use ea-project-kanban.
Drips, pipelines, HITL: Kanban project boards, not todo.md.
| name | ea-scheduling |
| description | Meeting-mail scheduling. Kanban ea-sched-*; Calendly fallback. |
| metadata | {"hermes":{"category":"executive-assistant","version":"4.22.0"}} |
Meeting negotiation on board ea-scheduling. Ingest no longer opens ea-sched-ingress — mail ingress workers file the email first, then spawn meeting tasks here via scheduling_* MCP.
| Board | kind: | Job |
|---|---|---|
ea-scheduling | meeting | Check live owner Google availability → book on owner Google Calendar (Composio) → kanban_block / kanban_complete |
ea-sched-ingress | ingress | Legacy — no new ingest tasks. Existing cards: match/create on ea-scheduling → kanban_complete |
Meeting workers never book from mail ingress alone — ingress filing + scheduling_create_meeting_task first.
kanban_show()
kind: meeting → Meeting. kind: ingress on ea-sched-ingress → Legacy ingress only.
When the owner asks in jChat (not a Kanban worker): "what meetings need follow-up?", "which threads are blocked?", "did we send yet?", "what's waiting on replies?" — you are answering scheduling status, not generic recall.
skill_view("ea-scheduling") if not already loaded.scheduling_list_meeting_tasks — use block_reason and recent_comments on each task; do not infer from body alone.source_paths and verify the mail thread (see Study the thread before sending).When ea-mail-ingress filing is done and the ingress task has scheduling_eligible: true:
scheduling_list_meeting_tasks — match open/blocked meeting on ea-scheduling by thread_id / subject.scheduling_handoff_meeting_task with neutral summary of this mail.scheduling_create_meeting_task — always pass threadId + provider from the ingress body (Joshu dedupes: returns action: existing_thread if an open meeting already exists for that thread → then scheduling_handoff_meeting_task instead).nylas_send_message without a meeting task — see Owner self-introduction handoff. Study the thread first — Study the thread before sending.Hard stop: If ingress has agent_authorized: false or scheduling_eligible: false — do not create meeting tasks, do not send mail. File-only mail is not your job on that card.
HITL defer at ingress: If the mail ingress worker chose path C (ea-playbook scheduling decision gate), there is no meeting task for you on this mail — owner review notes are in Projects/<slug>/. Do not create one retroactively unless the owner explicitly asks in jChat.
Call from ea-mail-ingress worker — Hermes kanban_create cannot cross to ea-scheduling; use scheduling_* MCP only.
Only for existing ea-sched-ingress cards — no new ingest tasks.
kanban_createHermes pins ingress workers to ea-sched-ingress. Worker tools (kanban_create, kanban_list, kanban_comment on other ids) cannot create or list on ea-scheduling.
CLI --board ea-scheduling from an ingress worker still creates on ingress (verified on patrick).
Use mcp_joshu_connectors_scheduling_* tools — Joshu writes to ea-scheduling via hermes-kanban-bridge.py.
kanban_show() — read source_path, message_id, ingress_id, profile, workspace_path.
read_file — <workspace_path>/<source_path> (under joshu's files).
scheduling_list_meeting_tasks — open meetings on ea-scheduling (includes blocked).
Match on subject, participants, time hints — not thread_id alone. Prefer match, not a duplicate create.
Match → scheduling_handoff_meeting_task — neutral summary of this mail only:
{
"taskId": "<matched meeting task_id>",
"sourcePath": "<source_path from ingress body>",
"messageId": "<message_id from ingress body>",
"from": "info@joshu.me",
"summary": "info@joshu.me replied: offered Thursday noon PT for in-person meet."
}
Do not judge whether waiting is over — the meeting worker decides.
Then kanban_complete with metadata={"action":"matched","matched_meeting_task":"<id>"}.
scheduling_create_meeting_task:{
"messageId": "<message_id from ingress body>",
"sourcePath": "<source_path>",
"subject": "Change tires",
"from": "the owner <owner work email>",
"timezone": "America/Los_Angeles"
}
Response includes task_id on board ea-scheduling.
scheduling_comment_meeting_task to link ingress.
kanban_complete(summary=…, metadata={"action":"new_meeting","new_meeting_task":"<task_id>"}).
kanban_create for meetings (lands on ingress)kanban_list (orchestrator-only)hermes kanban CLI / execute_code / SQLiteterminal / nylas email send (or any shell path to outbound mail — bypasses action guard; use MCP only)nylas_send_message, nylas_list_events, any Nylas calendar write (nylas_create_event is hard-blocked)GMAIL_SEND_* / GMAIL_REPLY_* (use nylas_send_message for outbound mail)scheduling_unblock_meeting_taskmark_scheduling_ingress_processed (legacy JSONL)todo listsJob: check owner availability, book or negotiate.
| Use | Tool | Notes |
|---|---|---|
| Owner busy/free | google_calendar_find_free_slots (Joshu connectors MCP) | Live FreeBusy API — omit items (default: primary + owner personal Gmail) or pass explicit calendar ids. Use calendars.combined.free for slot picking (union of busy across all queried calendars). Respects Google Show as free (transparent) events. |
| Event details (conflict explanation) | google_calendar_list_events | Titles + blocksAvailability / transparency only — never treat every event title as busy. |
| Agent ledger (read-only) | nylas_list_events | Historical holds only — do not create on Nylas (hard-blocked) |
| Stale fallback | gbrain / connectors/calendar/google/ mirrors | Only if live Composio fails — mirrors lag; owner may have edited calendar since sync |
Owner can delete or move events on their real Google calendar. Never infer owner availability from Nylas alone, from agent calendar mirrors, or from event titles on list_events.
Wrong calendar scope: items: ["primary"] alone checks only the work calendar (owner work email). Owner meetings often live on a personal Gmail calendar — FreeBusy returns busy: [] on primary while the personal calendar is blocked. Omit items or include personal Gmail; always schedule from calendars.combined.free, not primary.free alone.
Transparent events: Google events marked Show as free (transparency: transparent, blocksAvailability: false) — e.g. some recurring Asteme blocks — do not appear in FreeBusy busy[]. A visible calendar title does not mean the owner is unavailable. Only google_calendar_find_free_slots (or Composio GOOGLECALENDAR_FIND_FREE_SLOTS with the same args) determines bookable time.
External attendees (not connected on this box): no calendar read — email + kanban_block.
nylas_get_profile — timezone, primaryWorkEmail, personalEmail, working hours (default: 9 AM - 7 PM PT).google_calendar_find_free_slots — authoritative for busy/free. Prefer Joshu connectors MCP (wraps Composio GOOGLECALENDAR_FIND_FREE_SLOTS). Omit items so Joshu queries primary plus owner personal Gmail calendars (from profile + connected account). Or pass explicit ids, e.g. ["primary", "owner@gmail.com"]. Time window in owner timezone:
date + timezone for one local day, or timeMin / timeMaxtime_min style when using Composio directly: 7 AM local on the first day; time_max midnight local after the last day (exclusive)busy[] / free[] and calendars.combined (union busy — schedule from combined.free). Free intervals filtered to minDurationMinutes (default 30). Pick slots within working hours.google_calendar_list_events same window only to name events on calendar — check blocksAvailability before mentioning a conflict:{ "date": "2026-06-11", "timezone": "America/Los_Angeles" }
Responses include timeAnchor (owner-local now) and per-event localDate / relativeDay (today | tomorrow | yesterday | null). Use those — not UTC server time or Composio time_info — when saying today/tomorrow in summaries.
Wed 6/17) and use relativeDay when present (tomorrow's wrap block, not today's if relativeDay is tomorrow).timeAnchor.ownerLocalDate.nylas_list_events same window — see holds the companion already placed (avoid double-booking ledger).kanban_show() — source_paths, ingress_handoff, state, timezone, calendar_event_id.
Verify task status against real data — never answer from memory alone. The task body may say "agent needs to send times" even though you already sent them in a prior session, cron run, or different thread. Before reporting a task as "not yet done," read every source_paths entry and check the thread for your sent replies (look for agent mailbox messages in Nylas threads, or agent from in Gmail threads). Also check whether a nylas_send_message was logged in the thread's message_ids. If multiple threads exist for the same contact, read both — don't assume they're the same conversation.
read_file on every source_paths entry (and new paths from ingress_handoff).
Do not pick recipients from the task title, subject line, or your memory of the thread. Read the mail carefully — message by message — before any nylas_send_message.
Read the full thread in order — every message, not just the latest snippet. If the mirror truncates headers or bodies, fetch live metadata (nylas_get_message, or Composio GMAIL_FETCH_MESSAGE_BY_MESSAGE_ID with format: metadata) until you have complete To / CC / From for each message that matters.
Find the owner's latest outbound (any mailbox they use — primaryWorkEmail, personal Gmail, etc.). Who did the owner put in To:? That is usually who expects the next action. CC: are observers (recruiters, coordinators, the agent mailbox) — not necessarily who you should address unless the owner's prose says otherwise.
Map roles, don't guess from labels. Recruiter ≠ scheduler ≠ hiring manager. The person who started the thread or appears most often is not automatically the right to for your reply. Example failure mode: owner replies to the scheduler with the recruiter only CC'd — emailing the recruiter as to misses the person actually booking the meeting.
When the owner delegates to you ("copying [companion] to coordinate", "please suggest times") — your job is to continue their addressing: reply to whoever they addressed, CC who they CC'd (owner addresses + thread participants as appropriate), use replyToMessageId on the message you're continuing. Subject must be the parent message subject exactly (only Re:/Fwd: prefix differences allowed). Do not append availability, names, or task-title decorations — that forks a new Gmail/Google thread. On reply_subject_mismatch, retry with expectedSubject from the error.
Before send, state recipients explicitly in your working summary (to / cc) and match them to what you read. Owner Telegram approval shows To/CC — if they don't match your reading of the thread, fix before the owner approves.
Re-check sent mail on this thread (Nylas + owner Gmail mirrors) so you don't duplicate outreach or report "not sent" when a prior session already emailed the wrong person.
After ingress handoff: you decide:
google_calendar_find_free_slots for that window → if free, bookkanban_block (no book)nylas_send_message, then kanban_blockBook (slot confirmed + owner free on live Google): mcp_composio_GOOGLECALENDAR_CREATE_EVENT on the owner's connected Google account — include owner primaryWorkEmail + attendees, local start_datetime + timezone + duration fields. Then nylas_send_message confirmation → kanban_comment → kanban_complete.
Negotiate when vague/conflict: study the thread, email proposal, kanban_block.
nylas_send_messageOutbound mail hits owner-channel approval (Slack or Telegram) when action guard is enabled. Joshu may block up to 30 minutes waiting for the owner; Hermes MCP tool calls often timeout around 120s first.
Always pass kanbanTaskId (this meeting task id, e.g. t_…) and preferably threadId on nylas_send_message. Joshu rewrites this task's block_reason after approve/deny/timeout so status does not stay on "awaiting owner approval" after mail delivers (or after a denied/failed gate).
| Outcome | What it means | Your action |
|---|---|---|
Timeout / TimeoutError on nylas_send_message while guard is on | Approval still pending — not MCP down | kanban_block(reason="awaiting owner approval") — do not retry send from another task or board. Joshu will rewrite the reason after the owner decides. |
503 / action_guard_unavailable | Owner channel delivery failed or guard broken | kanban_block(reason="action-guard-unavailable: …") — not MCP down |
ok: true with messageId starting blocked- | Owner denied or approval timed out at Joshu | No mail sent — do not treat as success; block or complete without re-send |
ok: true with real UUID messageId | Mail sent | kanban_block waiting on reply (or book if confirmed). Prefer reason like awaiting reply: … — Joshu may already have rewritten it. |
Before claiming "MCP down": run connectors_status. A timeout on send alone is not connectors failure.
Only ea-scheduling meeting workers negotiate calendar mail on a thread. project-* board workers must not call nylas_send_message for meeting scheduling — see ea-playbook.
If connectors_status, scheduling_*, or google_calendar_* fail with HTML 404, connection refused, or explicit "Joshu API unreachable":
kanban_block(reason="connectors-mcp-down: …") — do not kanban_complete.terminal / nylas email send / curl.When working a kind: meeting card, do not book or send if you have any doubt that negotiation is still appropriate:
| Signal | Action |
|---|---|
| Thread shows meeting already booked (Calendly, confirmations, event on owner calendar) | kanban_comment + update project journal → kanban_complete with scheduling_closed: true — no outreach |
| Counterparty sent status-only mail ("confirmed", "see you then") | Same — close meeting task; do not re-offer slots |
| Ambiguous who to address, or sensitive thread where wrong mail would hurt | kanban_block(reason="owner review: scheduling judgment") — append project journal_* section ## Scheduling — owner review needed (what you read, why you stopped, suggested owner action) |
| Calendar check inconclusive | Block for owner review — never guess with find_free_slots + send |
Owner clears blocked meeting tasks in jChat or morning review. ea-morning-review surfaces owner_decisions_pending projects and todo rows Waiting on: owner review.
Never nylas_create_event — Joshu MCP policy blocks Nylas calendar writes.
Example (GOOGLECALENDAR_CREATE_EVENT via Composio MCP):
{
"summary": "Change tires",
"start_datetime": "2026-06-09T09:00:00",
"timezone": "America/Los_Angeles",
"event_duration_hour": 1,
"event_duration_minutes": 0,
"attendees": ["owner@example.com", "shop@example.com"]
}
| Action | Tool |
|---|---|
| Owner availability (live) | google_calendar_find_free_slots (Joshu connectors MCP) |
| Event titles / transparency | google_calendar_list_events |
| Agent ledger (read) | nylas_list_events / nylas_get_event |
| Create on owner calendar | mcp_composio_GOOGLECALENDAR_CREATE_EVENT |
| Reschedule | mcp_composio_GOOGLECALENDAR_PATCH_EVENT (no deletes — policy blocks *_DELETE_*) |
nylas_send_message — always pass sourcePath from meeting source_paths, replyToMessageId on the message you are continuing, and the exact parent subject (no decorations) | |
| Profile | nylas_get_profile / nylas_update_profile |
joshu-mail (read/search only).
The owner sometimes sends batch emails to multiple people (investors, partners) with the agent CC'd, and explicitly writes something like "[Companion], please introduce yourself, and share my availability." When this happens:
Find the email in the agent Nylas inbox — sent from an owner address with the agent CC'd. Use nylas_get_message with the message_id from the Nylas thread mirror if the body is truncated.
Read the owner's exact instruction in the body (e.g. "please introduce yourself and share my availability — 30 min is fine"). Follow that instruction literally — see Study the thread before sending for who belongs in To vs CC.
Check live owner calendar — google_calendar_find_free_slots (omit items or include personal Gmail; multi-day window, owner timezone). Schedule from calendars.combined.free. Filter free intervals to at least 30 min within working hours. Offer 2-3 specific time windows across different days.
Reply to the thread — nylas_send_message with sourcePath from the meeting task source_paths, kanbanTaskId = this task id, optional threadId, replyToMessageId on the message you are continuing, and the exact parent subject from the thread mirror (no decorations). To: whoever the owner addressed; CC: owner (so they see the thread) plus anyone they CC'd. Introduce yourself as the owner's companion and offer the slots you found. Agent sends hit action guard (owner channel approval) when enabled — kanban_block until send succeeds; Joshu rewrites block_reason after approve/deny when kanbanTaskId was passed.
No kanban task is needed for this pattern — the owner's instruction is in the email body, not in a scheduling ingress workflow. Reply, wait for the counterparty, then proceed with standard meeting booking.
User-initiated multi-step work (drip campaigns, research pipelines, vendor chases, approval-heavy sends) belongs on a project-<slug> board via skill ea-project-kanban. Do not create triage cards on ea-sched-* boards for those workflows. This skill is only for meeting-mail scheduling negotiation.
When you've offered slots and the other party replies with their own Calendly link (common for investors), you have two paths:
Calendly has a REST API for programmatic booking — no browser needed.
Important caveat: This API requires the host's Calendly credentials (Personal Access Token or OAuth). When a counterparty sends their Calendly link, you generally cannot use this path because you don't have their account credentials. This path is viable when the owner is the Calendly host.
Prerequisites:
Booking flow:
GET https://api.calendly.com/event_types — discover event type UUIDGET https://api.calendly.com/event_type_available_times?event_type={uuid}&start_time=...&end_time=... — query slotsPOST https://api.calendly.com/invitees — book the slotMCP integration: github.com/mkimelblat/calendly-mcp-server (Python, MIT). Box-only skill calendly-agent-integration may exist on learning repos for setup notes.
When the counterparty sent their Calendly link and you don't have API access to their account:
Failure mode observed: Even with Camoufox (anti-detection browser), Calendly often blocks the final "Schedule Event" submit: "This booking cannot be completed. For security reasons, we are not able to finalize this booking from your current session." Do not retry repeatedly — the block is deterministic per-session.
Email fallback (do this instead):
nylas_send_message (reply-to-thread with the latest message_id):Hi [Name],
I tried booking the [day] at [time] [TZ] slot through your Calendly link, but the booking platform isn't accepting my end. Could you send over a calendar invite directly for:
**[Day, Month Date, Year]**
**[Start time – End time] [TZ]**
**[Meeting format, e.g. FaceTime/Zoom/Phone]**
Looking forward to connecting!
Best,
[Companion name]
GOOGLECALENDAR_CREATE_EVENT placeholder on owner calendar ("Awaiting [name]'s invite").See also references/investor-response-classification.md (Calendly security blocks) in ea-playbook.