- 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 coding-agent chat into a scheduled Aeon skill, and mine past coding-agent 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 coding-agent chats |
## Preflight (every mode)
1. Find the repo: current dir → `gh repo set-default` → ask. Clone it if it isn't local.
2. **Confirm `gh` points at THEIR instance, before any command that writes.**
```bash
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.
3. `gh auth status` — everything routes through `gh`. If it fails, tell them to run `gh auth login` and stop.
4. 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:
```bash
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.
1. **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.
```bash
gh repo fork aeonfun/aeon --clone && cd aeon
gh repo set-default <owner>/aeon # REQUIRED — see below
```
**Private**: a fork of a public repo is always public, so a private instance is a mirror, not a fork.
```bash
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> # REQUIRED — see below
```
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:
```bash
gh repo view --json nameWithOwner -q .nameWithOwner # must print THEIR repo
```
Everything after this step is identical either way.
2. **Auth a model.** At least one is required. Fastest is `./aeon auth --harness claude-code` (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:
```bash
./aeon auth --key <token> --provider usepod # same for venice
```
`--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:** ten providers work, including OpenRouter, Grok, GLM, and crypto-settled gateways. See "Providers and harnesses".
3. **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.
4. **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.
5. **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 entry came out right** - one grep, every time:
```bash
grep '^ <skill>:' aeon.yml
```
The CLI writes `schedule`, `var`, `model` and `harness` double-quoted, and the scheduler reads `aeon.yml` with yq, so a quoted or a bare `schedule:` both fire. Quotes still matter on a hand-written per-skill `model:`/`harness:` override (see Harness below). Details in Mode 3, check 5.
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:
1. **Is it on?** `./aeon skills ls --enabled` — is it listed?
2. **Duplicate key?** `node scripts/validate-config.js`. A repeated skill name in `aeon.yml` silently shadows the first one. Common after hand-edits.
3. **Is it even cron?** `workflow_dispatch` and `reactive` never fire on a schedule.
4. **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.
5. **Is the schedule valid cron?** `grep '^ <skill>:' aeon.yml`. The scheduler reads `aeon.yml` with yq (`scripts/parse-aeon-config.sh`), so quotes are optional:
```
schedule: "0 12 * * *" ✅ fires
schedule: 0 12 * * * ✅ fires
schedule: "0 12 * *" ❌ never fires (wrong field count)
```
`scripts/cron-due.sh` treats a wrong field count, `*/0` or an out-of-range value as "not due" and only warns on stderr, so the skill is skipped every tick. `node scripts/validate-config.js` checks the schedule format, so run it after any hand edit. Invalid YAML anywhere in `aeon.yml` fails the whole scheduler tick with an `::error::`, so nothing runs at all.
**Older instances:** a `scheduler.yml` from before the yq parser (no `scripts/parse-aeon-config.sh` in the repo) matches schedules with the bash regex `schedule: *"([^"]+)"`, so there an unquoted value is skipped silently, every tick, forever, and nothing else detects it. Pull upstream, or add the quotes by hand.
6. **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`.
```bash
gh repo view --json nameWithOwner -q .nameWithOwner # if this isn't their repo:
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:
```bash
gh secret list -R aeonfun/aeon # timestamps matching the misfire = theirs
```
**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 this chat and want it to happen on a schedule.
1. **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
2. **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."
3. **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`.
4. **Give it an `aeon.yml` entry.** A new skill on disk has no entry. `./aeon skills enable|schedule <name>` **upserts**: if the entry is missing they create it (inline, quoted `schedule:` defaulting to `"0 12 * * *"`, inserted before the fallback `heartbeat:` line) and then apply the change. To land it present but **disabled**, add it by hand instead, before the `heartbeat:` line:
```yaml
my-skill: { enabled: false, schedule: "0 12 * * *" }
```
**Include the quoted `schedule:` even though it's disabled.** It matches every other entry, and an older instance whose scheduler still uses the bash regex only reads quoted values (Mode 3, check 5). The CLI writes the values it adds double-quoted, so later edits stay consistent.
Match the inline `{ … }` form every other entry uses, on one line. Per-skill `model:`/`harness:` overrides are read through `scripts/skill_entry.sh`, which also follows an entry split across lines, but the value must be double-quoted (see Harness below).
This is the one sanctioned exception to "never hand-edit the YAML". Validate after: `node scripts/validate-config.js`. It checks structure and schedule format, but not whether a `model:`/`harness:` override is quoted.
5. **Regenerate BOTH catalogs, add the eyebrow entry, then ship it as a PR.** A new skill trips four CI gates, and **a red gate blocks the merge**: `main` requires the `gate` check, which `ci-gate` fails whenever any other check on the PR is red. Run them locally first. Commit `SKILL.md` on its own before regenerating (the catalog's `sha`/`updated` are git-derived):
```bash
bash scripts/check-skill-categories.sh # category is one of the six
bin/generate-skills-json # catalog/skills.json
bin/generate-packs-json # catalog/packs.json - NOT optional
eyebrow scan --path . --lockfile /tmp/fresh.json # splice only this skill's entry into eyebrowlock.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. `ci-skill-integrity` also hard-fails any skill with no `eyebrowlock.json` entry; use the eyebrow version pinned in `.github/workflows/ci-skill-integrity.yml` and commit only the new skill's artifact, not a whole-file rescan.
Full gate list, triggers, and the `ci-tests` / `ci-apps` commands: `references/ci.md`.
6. **Run it once** (`./aeon skills run <name>`), show them the output, then schedule it via Mode 2.
### Skill file shape
```yaml
---
GitHubで見る