Imported from shawnoster/aya (
AGENTS.md). Install upstream withnpx skills add shawnoster/aya. Copyright stays with the author.
aya — Agent Guide
aya is a CLI tool that AI agents call to schedule reminders, sync context between machines, and integrate with Claude Code sessions. Agents never import aya as a library — they invoke it via shell commands.
Quick Reference
Scheduling
# One-shot reminder
aya schedule remind -m "Check the PR" --due "in 1 hour"
# Watch a GitHub PR (default polls every 5 min for PRs)
aya schedule watch github-pr owner/repo#123 -m "PR approved" --remove-when merged_or_closed
# Watch a Jira ticket
aya schedule watch jira-ticket CSD-225 -m "Ticket status changed"
# Recurring session cron (fires during active sessions only)
aya schedule recurring -m "health-break" -c "*/20 * * * *" \
-p "Stand up, stretch, hydrate." --idle-back-off 10m
# Record user activity (resets idle timer)
aya schedule activity
# Check what's pending for this session
aya schedule pending --format json
# List active items
aya schedule list
# Dismiss or snooze
aya schedule dismiss <id-prefix>
aya schedule snooze <id-prefix> --until "in 1 hour"
Dispatch / Relay
# Send context to another machine (encrypted by default on public relays)
aya send --as alice --to bob \
--intent "context sync" --files path/to/file.md
# Send a conversation seed (request for research/action)
aya send --as alice --to bob --seed \
--intent "investigate caching" \
--opener "Can you trace the auth flow and find where sessions drop?"
# Send plaintext (debug or private relay only)
aya send --as alice --to bob --no-encrypt --intent "test"
# Check inbox
aya inbox --as alice
# Receive and ingest trusted packets (decrypts transparently)
aya receive --as alice --auto-ingest --quiet
# Fully non-interactive receive — ingest everything without prompting (trusted or not)
aya receive --as alice --auto-ingest --yes --quiet
# Set up recurring relay poll (persists across sessions)
aya schedule recurring -m "relay-poll" -c "*/10 * * * *" \
-p "Run: aya receive --as alice --auto-ingest --skip-untrusted --quiet. If any packets were ingested, surface their content to the user."
New machine? See the "One-prompt setup" section in
README.mdfor a single prompt that installs aya, pairs instances, wires hooks, and registers relay polling.
Identity
# First-time setup — label can be anything (name, machine role, hostname)
aya init --label alice
# Pair with another machine (initiator)
aya pair --peer bob --as alice
# On the other machine (joiner)
aya pair --code WORD-WORD-1234 --peer alice --as bob
# Check status
aya status
--asis the local identity,--labelnames a new one ataya init, and--peernames a remote machine. See README for how the primary instance resolves when--asis omitted.
Plugin Skills
/aya manages identity, pairing, health and updates. /relay sends and
receives packets. Both work in any project once the plugin is loaded — see
README for the --plugin-dir setup.
After editing skill files, run /reload-plugins to pick up changes live.
Session Cron Mechanics
aya persists recurring schedules. Claude Code fires them during sessions. The bridge:
aya schedule recurringstores the cron in~/.aya/scheduler.json- At session start, the
aya hook cronscommand reads pending crons - It outputs
hookSpecificOutputJSON telling Claude Code to callCronCreate - Claude Code's native cron system handles the timing from there
Filtering happens at hook-time, not at fire-time. Both filters below are evaluated each time aya hook crons runs (SessionStart, then again after every tool call via PostToolUse), so a cron that's suppressed at session start can still register later in the same session if conditions change. Once registered with Claude Code's cron engine, the cron fires on schedule regardless of aya's current idle/window state.
Idle back-off (--idle-back-off 10m): suppresses registration when the last aya schedule activity is older than the threshold. The PreToolUse hook calls aya schedule activity on every tool use, so an active session won't be considered idle. After being idle, the next tool boundary refreshes activity and the next PostToolUse hook crons registers any previously-suppressed crons.
Work hours (--only-during 08:00-18:00): suppresses registration when the current time is outside the window. Same evaluation cadence as idle — a cron registered at 5:30pm with a 08:00-18:00 window will keep firing after 6pm because Claude Code's cron engine doesn't know about the window. For strict end-of-window stops, embed the check inside the cron's prompt with aya schedule is-idle or a time gate.
Watch Providers
| Provider | Target | Condition | Notes |
|---|---|---|---|
github-pr |
owner/repo#123 |
approved_or_merged (default), merged, new_comments |
Uses gh CLI. new_comments fires when the total count of general PR comments (issue comments) or inline review comments increases since the last poll — does not fire on the first poll. --remove-when merged_or_closed auto-cleans. |
jira-query |
Jira Query Language (JQL) string | new_results |
Requires ATLASSIAN_EMAIL, ATLASSIAN_API_TOKEN, ATLASSIAN_SERVER_URL env vars. |
jira-ticket |
CSD-225 |
status_changed |
Same Jira env vars. |
ci-checks |
owner/repo#123 |
checks_failed (default), checks_complete |
Uses gh pr checks, which signals state through its exit code and a bucket field rather than a conclusion. Polls every minute by default. |
relay-inbox |
instance label, or default for the primary |
new_packets |
Polls the relay as that identity, the same as aya receive with no --as. An empty stored config means the primary instance. Polls every 2 minutes. |
Packet Types
Content packets (default) carry knowledge — the receiver integrates it.
Seed packets (--seed) carry questions — the receiver investigates and reports back. Use --opener for the opening prompt.
Conflict strategies: last_write_wins (default), surface_to_user, append, skip_if_newer.
Data Layout
All aya data lives under ~/.aya/:
~/.aya/
profile.json # Identity, keypairs, trusted keys, relay list
config.json # Workflow config
scheduler.json # Reminders, watches, recurring crons — written lazily by
# the first `aya schedule` command; absence is normal
alerts.json # Unseen alerts from watchers
activity.json # Last activity timestamp (idle tracking)
ledger.json # Packet ledgers: ingested, sent, dropped (7-day TTL).
# Split from profile.json so polling does not rewrite
# the keystore. `aya sent` reads the sent log here.
sent_cache.json # Idempotency cache, keyed by SHA-256 of the
# `--idempotency-key` value, 24-hour TTL. Only written
# when a key is passed; nothing reads it but the
# duplicate-send check.
packets/ # Packet bodies, one <ulid>.json per packet, mode 0600.
# Holds both received packets and your own sent ones,
# so `aya read` works on either.
Claude Code Integration
Quick setup
aya schedule install # installs crontab + Claude Code hooks + OpenCode plugin
aya schedule install --dry-run # preview without changing anything
This installs the system crontab entry for background polling, all required
Claude Code hooks in ~/.claude/settings.json, and the OpenCode plugin at
~/.config/opencode/plugins/aya-reminders.js. Run it once per machine.
To remove everything: aya schedule uninstall.
OpenCode Integration
aya ships an OpenCode plugin (opencode-plugin/aya-reminders.js) that
proactively surfaces due reminders and unseen alerts inside OpenCode sessions.
How it works
The plugin hooks into OpenCode's session.idle event (fires when you stop
typing). On each idle tick it calls aya schedule pending --format json,
and if anything is due it:
- Shows a
tui.toast.shownotification in the TUI status bar for each item - Injects a summary into
tui.prompt.appendso the agent sees it on your next message
A 15-second debounce prevents hammering aya on every brief pause.
Install
aya schedule install copies the plugin automatically. To install manually:
cp opencode-plugin/aya-reminders.js ~/.config/opencode/plugins/
Or add it to your opencode.json by path:
{
"plugin": ["/path/to/aya/opencode-plugin/aya-reminders.js"]
}
Hooks installed
aya schedule install writes a fixed canonical hook block into
~/.claude/settings.json. Order within each event matters and is preserved:
SessionStart (run in order, top-to-bottom):
| # | Command | Purpose |
|---|---|---|
| 1 | aya schedule activity |
Reset the idle timer first so subsequent SessionStart hooks see a fresh activity timestamp |
| 2 | aya hook crons --reset |
Clear the per-session registered-crons tracker, then emit CronCreate instructions for every active session cron passing idle/work-hours filters |
| 3 | aya receive --quiet --auto-ingest (async) |
Ingest packets from trusted senders in the background |
| 4 | aya schedule pending --format text |
Surface due reminders and alerts into session context |
PreToolUse:
| Command | Purpose |
|---|---|
aya schedule activity (async) |
Refresh the idle timer on every tool use |
PostToolUse:
| Matcher | Command | Purpose |
|---|---|---|
| (any) | aya hook crons --event PostToolUse (async) |
Re-evaluate idle/work-hours filters and register any session crons newly eligible since the last hook run. This is what makes mid-session aya schedule recurring calls actually fire. |
Bash |
aya hook watch (asyncRewake) |
Poll all due scheduler watches; if any condition changed, emit asyncRewake so the session wakes after the user's reply |
Critical: don't reorder the SessionStart hooks. activity must run before hook crons or the very first get_session_crons() call sees the stale timestamp from the prior session and falsely suppresses idle-back-off crons.
Common Patterns
After user says "remind me":
aya schedule remind -m "Review the deploy" --due "tomorrow 9am"
After opening a PR:
aya schedule watch github-pr owner/repo#456 -m "PR review" --remove-when merged_or_closed
Sending context to another machine:
aya send --as alice --to bob --seed \
--intent "research request" \
--opener "What logging do we have for the payment flow?"
Checking scheduler health:
aya schedule status
Important Notes
- All
--format jsonoutput usesconsole.out()to avoid Rich wrapping — safe to pipe. - Item IDs support prefix matching:
aya schedule dismiss 5dc6works if unambiguous. aya schedule tick --quietis the system cron entry point (*/5 * * * *), installed viaaya schedule install.- Packets expire after 7 days by default.
- Trust is explicit — only paired/trusted DIDs are accepted.
Troubleshooting
A recurring cron isn't firing.
- Confirm it's registered:
aya schedule list --type recurring— status should beactive. - Confirm whether it was suppressed at session registration:
aya schedule pending --format jsonand inspectsuppressed_cronsfor reasons such asoutside work hours (...)orsession idle (...). - If suppressed for idleness, check the activity timestamp:
cat ~/.aya/activity.json | jq .last_activity_at. A new tool call will refresh it; the next PostToolUseaya hook cronswill then re-evaluate and register the cron. - Confirm the SessionStart hook order in
~/.claude/settings.jsonrunsaya schedule activitybeforeaya hook crons --reset. If reordered, the first filter sees the prior session's stale timestamp. - If
--only-during 08:00-18:00is set and the time is outside that window at session start, the cron is suppressed at registration. Once registered, Claude Code's cron engine fires it regardless of the window — embedaya schedule is-idleor a time gate inside the cron's prompt for hard end-of-window stops.
A watch doesn't seem to be polling.
aya schedule list --type watchshould show it active.- Confirm the system crontab entry exists:
crontab -l | grep "aya schedule tick". If missing, runaya schedule install. - Watches fire from the system cron every 5 min by default — they're independent of session activity.
- Provider-specific deps:
github-prneedsghCLI logged in;jira-*needsATLASSIAN_EMAIL,ATLASSIAN_API_TOKEN,ATLASSIAN_SERVER_URLin the cron environment (system cron has minimal env — set them in the crontab entry or wrap the call in a script that sources them). - For visibility, append
>> ~/.aya/scheduler.log 2>&1to the cron line and tail it.
aya receive returns nothing but the peer says they sent something.
- The peer's packet may not have reached the relay you're polling — confirm both ends share at least one relay:
aya relay list. - The packet may be encrypted to a different DID. Run
aya inbox --format json(raw) to see what arrived; if it's there but not ingested, it's likely from an untrusted sender (runaya receiveinteractively without--auto-ingestto inspect). - As of v1.36.2 the
sincecursor is gone — earlier versions could "lose" packets that arrived during a crashed receive. Upgrade if you're on an older build.