| name | aeon |
| description | Set up and run an Aeon agent instance — get started from scratch, pick which skills to turn on or install more from packs, reschedule or change what runs, edit what an existing skill does, fix a skill that isn't firing, set the STRATEGY.md north star and soul/ voice, turn a Claude Code chat into a scheduled Aeon skill, and mine past Claude Code conversations for recurring work worth automating as a skill. Use when the user mentions Aeon, aeon.yml, an Aeon skill / instance / routine / pack, asks to schedule, enable, edit, or debug an agent that runs on a cron, or asks what of their repeated/manual work Aeon could take over. |
Aeon
Aeon is an agent that runs on the user's own GitHub repo via Actions. A skill is a Markdown file (skills/<name>/SKILL.md); aeon.yml says which ones run and when.
Pick the mode they're asking for:
| |
|---|
| 1 · Start | No instance yet, or set one up from scratch |
| 2 · Reschedule | Change times, cadence, or what a skill focuses on |
| 3 · Unblock | "It didn't run" / "nothing happened" |
| 4 · Chat → skill | Turn what we just did into a scheduled skill |
| 5 · Edit a skill | Change what an existing skill does |
| 6 · What to turn on | Pick skills, browse packs, install more |
| 7 · Strategy & voice | STRATEGY.md and soul/ — the north star and the tone |
| 8 · Mine history → skill | "What of my repeated work could Aeon do for me?" — surface it from past Claude Code chats |
Preflight (every mode)
-
Find the repo: current dir → gh repo set-default → ask. Clone it if it isn't local.
-
Confirm gh points at THEIR instance, before any command that writes.
gh repo view --json nameWithOwner -q .nameWithOwner
If that prints aeonfun/aeon and they aren't working on upstream itself, stop and run gh repo set-default <owner>/<repo>. gh prefers an upstream remote over origin when no default is pinned, and every Aeon write (auth, secrets set, skills run, config pushes) is a gh -R <resolved> call — so it will cheerfully put their API keys on the upstream repo and dispatch runs there. It looks like success: no error, a real run id, and the skill just never fires on their instance.
-
gh auth status — everything routes through gh. If it fails, tell them to run gh auth login and stop.
-
Use the ./aeon CLI for all config writes. It preserves comments in aeon.yml and validates. Never hand-edit the YAML — with one exception: the CLI cannot create an entry for a brand-new skill (see Mode 4 step 4).
Don't trust "disabled" for a skill you just created. The read path lists skills from disk and defaults a missing aeon.yml entry to enabled: false, so "not configured" and "disabled" look identical. One command tells them apart:
comm -23 <(ls skills/*/SKILL.md | cut -d/ -f2 | sort) \
<(grep -oE '^ [a-z0-9-]+:' aeon.yml | tr -d ' :' | sort)
Anything it prints is on disk but unconfigured. Orientation — what's installed, what's on, and where everything lives: references/layout.md.
Setting any key or token: read references/secrets.md — it has every secret and repo variable with the exact page to get it from. Always set secrets with ./aeon secrets set NAME --stdin, never as a command argument.
Mode 1 — Start on Aeon
Goal: one real notification in their phone, fast. Do not configure a schedule first.
-
Get a repo. Ask public or private before you run anything — it changes the command, and switching later means moving the repo.
Public (recommend this): Actions minutes are free, and upstream skill updates arrive with one command.
gh repo fork aeonfun/aeon --clone && cd aeon
gh repo set-default <owner>/aeon
Private: a fork of a public repo is always public, so a private instance is a mirror, not a fork.
gh repo create <name> --private
git clone --bare https://github.com/aeonfun/aeon.git
git -C aeon.git push --mirror https://github.com/<owner>/<name>.git
rm -rf aeon.git && git clone https://github.com/<owner>/<name>.git && cd <name>
git remote add upstream https://github.com/aeonfun/aeon.git
gh repo set-default <owner>/<name>
Say both costs out loud before they pick private: Actions minutes bill against the account quota (2,000/mo on Free — scheduled skills burn it), and updates come from git fetch upstream && git merge upstream/main instead of gh repo sync.
Pin the default repo before any other command — both paths. Both end up with an upstream remote (gh repo fork --clone adds one for you), and with no default pinned gh prefers upstream over origin. Everything in Aeon routes through gh -R $(gh repo view …), so an unpinned checkout silently writes secrets to and dispatches runs against aeonfun/aeon instead of their instance — with no error, because the commands genuinely succeed on the wrong repo. Verify:
gh repo view --json nameWithOwner -q .nameWithOwner
Everything after this step is identical either way.
-
Auth a model. At least one is required. Fastest is ./aeon auth --oauth (Claude Pro/Max, opens a browser), or ./aeon auth --key <key>, which detects the provider from the key prefix — sk-ant-oat (OAuth), sk-or- (OpenRouter), bk_ (Bankr), inf_ (Surplus), xai- (Grok); anything else lands in ANTHROPIC_API_KEY.
UsePod and Venice keys have no prefix and are undetectable, so a bare --key files them as a plain Anthropic key and the run fails later with a confusing auth error. They must be named:
./aeon auth --key <token> --provider usepod
--dry-run prints the resolved method=… → secret … without calling gh or claude — worth running whenever the provider is in doubt.
Don't assume they have a Claude subscription: nine providers work, including OpenRouter, Grok, GLM, and crypto-settled gateways. See "Providers and harnesses".
-
Wire one channel. Telegram is the fastest: create a bot with @BotFather, then ./aeon secrets set TELEGRAM_BOT_TOKEN --stdin and TELEGRAM_CHAT_ID. Skip Discord/Slack/email for now — one channel is enough to prove it works.
-
Run one skill now. Pick it with Mode 6 — ask what they want handled, propose one — then ./aeon skills run <name>. Wait for it, then ./aeon runs logs <id>. They should get a Telegram message.
-
Only then, schedule it. ./aeon skills enable <name> and set a time (see Mode 2).
Good first skills: digest (topic briefing), github-monitor (their repos), heartbeat (already on by default, reports only when something needs attention).
Mode 2 — Reschedule / change the routine
Show them their day as a timeline in their own timezone, not a config file:
07:00 digest "solana"
09:00 pr-review your repos
18:00 heartbeat health check
Build it from ./aeon skills ls --enabled --json. (--enabled matters: plain ls prints a SCHEDULE column for disabled skills too — that's their aeon.yml entry, not proof anything fires.) No CLI, or want the raw file? references/layout.md has grep-only equivalents. Then take plain-language edits and apply them:
| They say | You do |
|---|
| "move the digest to 7am" | ./aeon skills schedule digest "0 6 * * *" |
| "weekdays only" | ... "0 6 * * 1-5" |
| "too noisy, twice a week" | ... "0 6 * * 1,4" |
| "stop the crypto one" | ./aeon skills disable token-movers |
| "make it about rust instead" | ./aeon skills set digest --var rust |
Rules:
-
All cron in aeon.yml is UTC. Convert from their timezone, and say so: "7am Paris = 0 6 * * * UTC (5am in summer — want it pinned to local time?" There is no local-time option, so if DST matters, tell them which half of the year is off by an hour.
-
Confirm back the next 3 fire times in their timezone after any change.
-
--dry-run first on anything ambiguous, show the diff, then apply.
-
Changes need a push to take effect. The CLI does it; confirm it landed.
-
Then check the value came out quoted — one grep, every time:
grep '^ <skill>:' aeon.yml
The scheduler only reads schedule: "…" with double quotes. The CLI writes a new key unquoted, so an entry that had no schedule: yet comes back as schedule: 0 12 * * * and the skill is skipped forever. Details below.
Skills with schedule: workflow_dispatch are on-demand only — they never fire on cron. reactive ones fire on conditions, not time.
Mode 3 — Unblock
"It didn't run." Check in this order and stop at the first hit:
-
Is it on? ./aeon skills ls --enabled — is it listed?
-
Duplicate key? node scripts/validate-config.js. A repeated skill name in aeon.yml silently shadows the first one. Common after hand-edits.
-
Is it even cron? workflow_dispatch and reactive never fire on a schedule.
-
Are Actions disabled? gh api repos/{owner}/{repo}/actions/permissions. GitHub auto-disables scheduled workflows after 60 days of repo inactivity — this silently kills forks and nothing in Aeon surfaces it. Re-enable in repo Settings.
-
Is the schedule quoted? grep '^ <skill>:' aeon.yml — the value must be schedule: "0 12 * * *", with double quotes.
schedule: "0 12 * * *" ✅ fires
schedule: 0 12 * * * ❌ never fires, no error anywhere
scheduler.yml matches schedules with the bash regex schedule: *"([^"]+)". An unquoted value doesn't match, $SCHED is empty, and the match loop hits [ -z "$SCHED" ] && continue — skipped silently, every tick, forever.
How it gets that way: the CLI edits aeon.yml through a YAML document model that preserves an existing quoted node but writes a newly added key in plain style. So ./aeon skills schedule <name> "0 12 * * *" is safe on an entry that already had a quoted schedule:, and quietly breaks one that didn't. Same for a first-time --var.
Nothing else detects this. The file is valid YAML, validate-config.js reports CLEAN, and ./aeon skills ls --enabled lists the skill with its schedule — because they all parse YAML properly and only the scheduler uses a regex. Fix by adding the quotes by hand.
-
Did it run and fail? ./aeon runs ls then ./aeon runs logs <id>. A failed skill retries after a 30-minute cooldown.
Three more, if the above are clean:
-
It ran against the wrong repo. The giveaway is a command that reported success with a run id, but ./aeon runs ls on their instance shows nothing. gh prefers upstream over origin when no default is pinned, so an unpinned checkout sends every write to aeonfun/aeon.
gh repo view --json nameWithOwner -q .nameWithOwner
gh repo set-default <owner>/<repo>
Then clean up what landed upstream — re-running against the right repo does not undo it. Any key set while mispointed is now a secret on someone else's repo:
gh secret list -R aeonfun/aeon
Rotate it at the provider first, always — it sat on a repo whose collaborators can land a workflow that reads it. Then re-set it on their instance with ./aeon secrets set NAME --stdin.
Don't blind-delete it. gh secret list shows only last-updated, so it cannot tell you whether the upstream repo already had that secret and the misfire overwrote it. Ask before removing:
- Upstream never had it →
gh secret delete <NAME> -R <upstream>.
- Upstream had its own → deleting breaks their scheduled runs. The owner must re-set upstream's own value; the overwrite is not reversible from here.
If the delete 403s, they never had write access — nothing was ever written, and the earlier command failed while only looking fine.
-
Missing secret. Skills declare keys in requires:. Check them against ./aeon secrets ls --set. A missing optional key (KEY?) means it degrades quietly, not that it breaks.
-
"No MCP tools available." On the Claude harness a single unresolved ${VAR} in .mcp.json disables every MCP server for that run, not just the broken one (::warning::.mcp.json references secret(s) not set: … Skipping MCP this run.). Grok degrades per-server instead. If an OAuth server broke a run after working, suspect a rotated refresh token that couldn't be saved — references/mcp.md.
-
It ran but sent nothing. That's usually correct. Aeon's convention is silence on no signal — a clean run sends nothing rather than an empty report.
Note: GitHub only delivers ~10% of */5 cron ticks, so the scheduler catches up missed slots for up to 12 hours. A skill firing 40 minutes late is normal.
Mode 4 — Turn this chat into a skill
They just did something in Claude Code and want it to happen on a schedule.
-
Write the skill file. skills/<name>/SKILL.md — frontmatter, then the prompt. Derive it from what actually happened in the session:
- the prompt body = what they asked for, plus the steps that worked
mode: = read-only unless it needs to commit or open PRs
requires: = any API key the work hit (KEY? if it can degrade without it)
category: = one of core evolution basics dev crypto productivity
- if they liked the output, paste a trimmed sample into the body as the format spec
-
Fix the three things that break unattended runs:
- Nobody's there. Any point where you asked them a question has to become a default or a rule.
- Stay silent on nothing. Add an explicit "if there's nothing worth reporting, log and exit without notifying." Otherwise it gets muted in a week.
- Don't repeat yesterday. Add "check the last 3 days of
memory/logs/ and skip anything already reported."
-
Check it can actually run there. No local filesystem, no logged-in tools. If the session read their home directory or used a local MCP server, say so plainly — that part won't work unattended unless it's wired as a repo secret / .mcp.json. Wiring an MCP server for unattended use (dashboard Connect, OAuth refresh, the rotating-token PAT): references/mcp.md.
-
Add the aeon.yml entry yourself. A new skill on disk has no entry, and ./aeon skills enable|schedule will not create one — they only flip entries that already exist, and report no change — already in that state, which is false. Add it by hand, disabled, before the fallback heartbeat: line:
my-skill: { enabled: false, schedule: "0 12 * * *" }
Include the quoted schedule: even though it's disabled — the quotes are load-bearing. Writing a bare { enabled: false } and letting ./aeon skills schedule add the key later produces an unquoted value the scheduler cannot read, and the skill never fires (Mode 3, check 5). Seeding a quoted node here means every later CLI edit preserves the quotes.
Match the inline { … } form the other 61 entries use, on one line. aeon.yml:367 reads per-skill model:/harness: overrides with a single-line grep, so an entry split across lines takes the global default instead.
This is the one sanctioned exception to "never hand-edit the YAML". Validate after: node scripts/validate-config.js — but note it only checks structure, and will not catch an unquoted value.
-
Regenerate BOTH catalogs, then ship it as a PR. A new skill trips three CI gates. Run them locally — nothing blocks a merge on red, main is unprotected and has no rulesets, so an unrun gate just fails after the fact:
bash scripts/check-skill-categories.sh
bin/generate-skills-json
bin/generate-packs-json
generate-packs-json is the one everyone forgets: catalog/skills.json is itself a trigger path for ci-packs-json, so committing the skills catalog without the pack catalog goes red on a workflow you never touched. Commit both files.
Full gate list, triggers, and the ci-tests / ci-apps commands: references/ci.md.
-
Run it once (./aeon skills run <name>), show them the output, then schedule it via Mode 2.
Skill file shape
---
name: my-skill
description: One line — what it does and what it sends.
metadata:
title: My Skill
mode: read-only
category: basics
var: ""
tags:
- content
requires:
- SOME_API_KEY?
---
Today is ${today}. <the prompt — plain instructions, including judgment calls>
1. <the procedure — 43 of 77 skills lead with this>
<curl / WebFetch / `./secretcurl` / `gh api` — how this skill fetches>