---
name: agentcal
description: Read and update AgentCal project calendars. Use in a project containing .agentcal/project.json when the human discusses dates, deadlines, milestones, releases, schedules, plans, priorities, recent history, or what happens next; before time-sensitive planning; at the start and end of a scheduled or automated project run; and when the human asks to create, edit, delete, or verify calendar events, calendars, or layers. If the project is not bound to AgentCal, use only when the human explicitly asks to set it up.
---

# AgentCal — core operations

AgentCal is a calendar API for agents. You hold a token, calendars hold events,
and humans watch the result in a browser. Your job is to keep the calendar
true; the human's trust depends on you following the rules below exactly.

## 0. Resolve config — before anything else

1. Read `.agentcal/project.json` in the repo root. It gives you:
   - `api_base` — every endpoint below is relative to it (it already ends in `/v1`)
   - `project.id` / `project.slug` — the project calendar
   - `layers` — the calendars already attached to this project, by key
2. Read your token from `.agentcal/credentials` (gitignored), falling back to
   `~/.agentcal/credentials`. Send it as `Authorization: Bearer <token>`.
3. Confirm identity and environment before writing:

   ```
   GET {api_base}/me
   ```

   Confirm that the response includes the expected project calendar id and a
   role that permits the intended operation. Do not search the repository,
   logs, or shell history for another token. If the credential is missing or
   lacks access, follow `{app_base}/grants.md` and ask for the minimum access
   needed; do not mint a replacement project calendar.

   **Environment check.** If `api_base` points at `localhost` or a host
   different from the calendar URLs the human uses, you may be talking to a
   development instance. Verify the host and agent identity internally. If
   there is any doubt about which instance the human means, tell them which
   host is selected and ask before writing. A write to the wrong instance
   looks successful and is invisible to the human.

After reading `project.json`, derive `app_base` by removing the trailing
`/v1` from `api_base`.

No `.agentcal/project.json` in the repo? Do not guess an API host or create
anything. If the human explicitly asked to set up AgentCal, fetch
`https://youragentcal.com/start.md` (or the deployment they named) and follow
it. Otherwise say that this project is not bound to AgentCal and continue
without calendar operations.

## Rules that override everything

1. **One fact = one event.** Never copy an event into a second calendar or
   layer. If something matters to both the project and a person, it goes on
   the **project calendar once**. Composed views already show layer events
   together; a mirror renders as a duplicate and then drifts.
2. **Names, slugs, and abbreviations are the human's.** Use exactly what they
   wrote — never shorten, stylize, or transliterate ("Diwan" never becomes
   "D1"). If a slug is taken or reserved, ask; don't silently pick a variant.
3. **Don't create calendars or layers unless the human explicitly asks.**
   Reuse what `project.json` lists. New structure is a human decision.
4. **Verify by reading data back, never by HTTP status.** A missing browser
   page can still return HTML with HTTP 200. After writing, read the exact
   object from the authenticated API and, when public, check the composed
   calendar JSON too (step 8 below).
5. **Only confirmed facts.** No guessed dates, no invented locations. If a
   detail is unknown, leave the field out or ask.
6. **Close the loop.** End by telling the human the public URL and exactly
   what you wrote, so they can see it with their own eyes.
7. **Read before reasoning about time.** Calendar state may be more current
   than the conversation or repository. Refresh it before answering a
   temporal question or starting scheduled work.

## 1. Read a calendar

Composed public read — the calendar plus all its layers, as the human sees it:

```
GET {app_base}/c/<slug>
Accept: application/json
```

Returns `{ calendar, events, layers }`. Project events are in `events`;
layer events are nested under `layers[].events`, and each layer object has
its `key`. A private calendar 404s on this public surface (use authenticated
reads instead); private *layers* of a public calendar are silently omitted
from the response.

Authenticated API reads:

```
GET {api_base}/calendars/<cal_id>/events            # this calendar's own events
GET {api_base}/calendars/<cal_id>/events/<evt_id>   # one event
GET {api_base}/calendars/<cal_id>/layers            # readable attached layers
```

Send the bearer token on authenticated reads so private calendars remain
readable. To assemble complete project context, read the project events and
layer list, then read each relevant layer calendar by id. For a public or
unlisted calendar, read its full `purpose` from the composed calendar JSON or
`{app_base}/c/<slug>/purpose.md` before deciding what belongs where.

The current authenticated API exposes only `purpose_preview` through
`GET {api_base}/me`, not the full purpose of a private calendar. If a private
calendar's full purpose is unavailable, say so and use only the preview plus
the human's explicit instructions. Do not pretend the full contract was read.

Before reading a date-relative request ("what's next week?"), check the
calendar's timezone in the read response — dates and times are calendar-local.

## 2. Read temporal context

Refresh AgentCal before answering questions about dates, deadlines,
milestones, releases, recent history, priorities, schedules, or what happens
next. Also refresh at the start of a scheduled or automated run.

Build a compact briefing rather than pasting the whole calendar into chat:

1. Read the project purpose and relevant layer purposes when those documents
   are available; otherwise carry forward the private-purpose limitation.
2. Read recent dated history, the nearest future events, active recurrences,
   and relevant dateless `someday` milestones.
3. Match names, notes, categories, and layer scopes against terms from the
   human's request by listing events and filtering locally — there is no
   search endpoint.
4. Distinguish confirmed project facts from planned agent work and activity
   logs. Do not present a scheduled agent action as a completed project fact.
5. Cite the event name, date or recurrence, calendar/layer, and public URL
   when calendar context materially affects the answer.

For a broad session-start read, keep only the most recent meaningful history,
the nearest upcoming commitments, active recurring work, and important
`someday` items. For a specific temporal question, widen the range or inspect
matching events as needed.

## 3. Create an event

```
POST {api_base}/calendars/<cal_id>/events
Authorization: Bearer <token>
Content-Type: application/json
Idempotency-Key: <descriptive-deterministic-key>
```

Every POST requires an `Idempotency-Key` header. Make it deterministic and
descriptive — `<project>-<event>-<date>-v1` — so a retry can't double-write.
Reusing the same key with a different body is rejected; change the key when
the intended request body changes.

Fields (anything else is rejected — put extra detail in `notes`):

| field        | rules                                                            |
|--------------|------------------------------------------------------------------|
| `name`       | required, ≤200 chars — short and scannable; detail goes in notes |
| `date`       | `YYYY-MM-DD`, one-off events                                     |
| `recur`      | recurring events; read `{app_base}/events.md` before using       |
| `start_time` | `HH:MM` 24h, calendar-local; absent = all-day                    |
| `end_time`   | `HH:MM`, requires `start_time`, must be after it                 |
| `notes`      | ≤4000 chars, markdown                                            |
| `category`   | one line, ≤40 chars, lowercased on save                          |
| `art`        | ≤8000 chars; leave unset unless the human asks for card artwork  |

- `date` and `recur` are mutually exclusive. **Neither** is valid too: a
  dateless event is a "someday" milestone that lives on the timeline until it
  earns a date.
- Categories are semantic tags, not containers. Reuse what the calendar
  already uses (`deadline`, `release`, `milestone`, `decision`, `event`)
  before inventing one — read existing events first.
- Notes convention — start with metadata lines, then a markdown body:

  ```
  location: 42 Paris
  link: https://…
  people: Diwan

  ## Why this matters
  …
  ```

**Which calendar?** Project deadline, release, or milestone → the project
calendar. Something only one person or agent cares about → that layer's
calendar (its id is in `project.json` under `layers`). Both → project
calendar once (rule 1).

## 4. Update or delete an event

```
PATCH  {api_base}/calendars/<cal_id>/events/<evt_id>   # partial; same fields
DELETE {api_base}/calendars/<cal_id>/events/<evt_id>
```

PATCH only what changed. JSON `null` clears `date`, `recur`, `notes`,
`category`, or `art`; `{"start_time": null}` clears both times. Sending a
new `date` clears an existing recurrence and sending `recur` clears an
existing date. Prefer PATCHing the existing event over delete-and-recreate —
the event id is what humans and other agents link to.

## 5. Scheduled and automated runs

When a Codex scheduled task, Claude Code automation, CI agent, or other runner
starts inside a project with `.agentcal/project.json`:

1. Read the project purpose and current temporal context before doing work.
2. Perform only the scheduled task's authorized work.
3. At completion, record one truthful outcome event on this agent's existing
   log layer. Include the objective, scheduled time, actual result, status,
   relevant artifact links, and runner/run id when available in `notes`.
4. Use an idempotency key derived from the stable task name or id plus its
   scheduled run time, so a retry cannot create a second result.
5. Write to the project calendar only when the run established a verified
   project fact such as a completed release or a newly confirmed deadline.
6. Record failures and no-change outcomes as agent activity, not project
   facts. Never claim success merely because the runner woke up.

Do not create an agent layer automatically when none is listed. Report that
write-back is blocked and ask the human whether to create or attach one.

## 6. Create a calendar or attach a layer

Only when the human explicitly asked (rule 3).

```
POST {api_base}/calendars
Authorization: Bearer <token>
Content-Type: application/json
Idempotency-Key: <descriptive-deterministic-key>

{"name": "Diwan", "timezone": "Europe/Paris", "slug": "diwan"}
```

- `name` required; `timezone` is IANA, defaults to `UTC` — set it, times are
  calendar-local; `slug` optional (the public URL `/c/<slug>`).

Attach it to the project calendar as a layer:

```
POST {api_base}/calendars/<project_cal_id>/layers
Authorization: Bearer <token>
Content-Type: application/json
Idempotency-Key: <descriptive-deterministic-key>

{"calendar_id": "cal_…", "key": "diwan"}
```

- `key` is the layer's role inside this project — 1–40 chars of `a-z0-9-`.
  It's how the layer is filtered and badged in the UI.
- A layer is a whole calendar attached one level deep. Layer = *whose/which
  context* (person, agent, workstream); category = *what kind of event*.
  Don't use one for the other.
- Attaching requires owner access to the project calendar and read access to
  the child. Layers cannot nest and a calendar cannot be attached to itself.
- Detach with `DELETE …/layers/<lyr_id>` — this detaches, it does not delete
  the child calendar or its events.

After creating or attaching, finish the bookkeeping:

1. Add the new calendar to `.agentcal/project.json` under `layers` so future
   agents discover it.
2. Set the new calendar's purpose doc — `PATCH {api_base}/calendars/<cal_id>`
   with `{"purpose": "# …"}` — saying what belongs on it and what doesn't.
3. Read the existing project purpose, add the layer to its `## Layers`
   section without discarding other content, and PATCH the complete result.
   If the full existing purpose cannot be read, do not overwrite it from a
   preview; report that this bookkeeping step remains blocked.

## 7. Calendar settings

```
PATCH {api_base}/calendars/<cal_id>
Authorization: Bearer <token>
Content-Type: application/json

{"name": …, "slug": …, "timezone": …, "purpose": …}
```

Changing the primary slug requires owner access. Confirm with the human because
it changes the canonical URL, then update `.agentcal/project.json`. Old slugs
remain permanent `308` redirects and are never recycled, so existing links do
not break. A calendar can hold at most 10 slugs; switching back to one it
already owns is allowed.

## 8. Verify and report

After any write:

1. Read the exact created or changed object through the authenticated API.
   For an event, use `GET {api_base}/calendars/<cal_id>/events/<evt_id>`.
2. If the project calendar is not private, also read
   `GET {app_base}/c/<slug>` with `Accept: application/json` and check the
   event or layer appears in the composed response. Follow a `308` to the
   current slug. **Do not trust status alone.**
3. For a private project, verify through authenticated calendar, event, and
   layer reads; do not weaken visibility merely to perform public verification.
4. Tell the human what changed, on which calendar or layer, and the canonical
   public URL. Mention any visibility limitation that prevents them opening it.

## Common errors

| symptom                              | cause / fix                                                       |
|--------------------------------------|-------------------------------------------------------------------|
| `401` on a write                     | token doesn't match this `api_base` — re-run the environment check |
| `400 invalid_event` with field list  | unknown field — move it into `notes`                              |
| slug rejected as taken/reserved      | ask the human for the slug they want; don't invent one            |
| event shows twice in the week view   | you mirrored it — delete the copy, keep the project-calendar one  |
| write "succeeded" but human sees 404 | you wrote to a different instance — environment check, then redo  |
| project exists but token is missing  | do not grep for secrets or mint a duplicate; use the grants flow  |
| public JSON omits a layer            | it may be private; verify with authenticated layer/event reads     |
