Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions PERF.md
Original file line number Diff line number Diff line change
Expand Up @@ -1570,6 +1570,14 @@ measures **52,713** bytes and the lean **44,935**, and both waivers fall to sit
exactly on the measurement: the full cap is **52,713** (4,713 over its target)
and the lean cap **44,935** (13,435 over).

**`automation` learns `change`, and pays for it (2026-10-14).** The op, the
sentence in the tool's description that says when to use it and the page's
"one that exists is CHANGED, never proposed again" were taken out of what the
description already said twice — a watch's look and condition, which the schema
says beside their own fields — and out of the `id` field's list of the ops that
take one. Both arms come out **22** bytes lighter: the full cap is **52,691**
(4,691 over) and the lean cap **44,913** (13,413 over).

## Following through on a completion claim

A turn may decline handoff once per request when its own continuation says no
Expand Down
11 changes: 11 additions & 0 deletions docs/changes/unreleased/1829-automation-change-op.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
kind: added
title: the conversation can change an automation in place, on a save this change? card
pr: 1829
surface: [chat, engine, docs]
invalidates:
- "The `automation` tool had no way to change an existing automation, so \"move my reminder to 5pm\" proposed a second one beside the first, often a finished one. It has a `change` op: an id and only what changes, saved through `Store.Update`, which wakes a finished one-time reminder given a new moment."
- "The manual said the chat had no verb that changes an automation in place, and that changing one was `e` or a new proposal that replaces it. Asking in the conversation changes it in place, and `e` on `/automations` still does."
- "`save this change?` was a card only a typed `/automations edit` raised. The model's `change` raises the same card; any card whose automation already has an id is a change, with only `1 Save` and `0 Don't save`."
- "The prefix waivers were 4,713 bytes on the fixed arm and 13,435 on the lean. They are 4,691 and 13,413: `change` was paid for out of the tool description's repeat of the watch fields."
---
14 changes: 9 additions & 5 deletions docs/design/automations/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,15 @@ codeaf is open`), because the model judges every look; with no known price for
the judging model it shows nothing.

**Three ways in, one card.** Said in a conversation (the model calls the
`automation` tool); typed with exact syntax (`/automations add …`, no model in
between); or edited on the list (`e` puts the automation's own
`/automations edit …` line in the box). All three end on a card, and nothing is
saved before the person says yes on it. A typed line's card is the surface's
own (`save this automation?`, `save this change?`), because no model asked.
`automation` tool — `propose` for a new one, `change` with its id for one that
exists); typed with exact syntax (`/automations add …`, no model in between); or
edited on the list (`e` puts the automation's own `/automations edit …` line in
the box). All three end on a card, and nothing is saved before the person says
yes on it. A typed add's card is the surface's own (`save this automation?`),
because no model asked. A change's card is `save this change?` from either door —
the model's `change` or a typed `edit` — with only `1 Save` and `0 Don't save`:
an automation with an id on a card is a change to it, and session's
`AutomationHead` and `AutomationOptions` decide that once for both.

**The typed grammar** is a word and one value per clause, a value with a space
in it quoted (`internal/automation`'s `command.go`):
Expand Down
9 changes: 7 additions & 2 deletions internal/automation/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ func (s *Store) Update(a Automation) (Automation, error) {
if lookChanged(current.Look, a.Look) {
a.Seen, a.Memo = "", ""
}
if scheduleChanged(current.Schedule, a.Schedule) {
if ScheduleChanged(current.Schedule, a.Schedule) {
if a.Schedule.Repeats() && a.Schedule.Interval() > 0 {
a.Schedule.Anchor = now
}
Expand Down Expand Up @@ -997,7 +997,12 @@ func lookChanged(a, b *Look) bool {
return a.Command != b.Command || a.Files != b.Files || a.Tool != b.Tool || string(a.Args) != string(b.Args) || a.Condition != b.Condition
}

func scheduleChanged(a, b Schedule) bool {
// ScheduleChanged reports whether b is a different schedule from a: another
// moment, another rhythm or another zone. It is the ONE reading of that, which
// [Store.Update] reschedules on and a change's card works its next run out by
// (internal/session's automation_tool.go), so the card cannot promise a next run
// the save does not give.
func ScheduleChanged(a, b Schedule) bool {
return !a.At.Equal(b.At) || strings.TrimSpace(a.Every) != strings.TrimSpace(b.Every) || a.Zone != b.Zone
}

Expand Down
45 changes: 28 additions & 17 deletions internal/manual/chat/automations.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,28 +252,37 @@ catches up for the paused time; a one-time reminder resumed after its moment run
late. A finished one offers no pause. The foot names the row's verbs:
`enter its history · → verbs: run now, pause, edit, delete, open where it was asked · tab next place · esc`.

## Change an automation — edit its time, what it does or where it runs

Press `→` then `e` on its row in `/automations`, and the automation's own line lands in the
## Change an automation — move my reminder to 5pm, reschedule it, edit its time, what it does or where it runs

**Ask me.** "Move my water reminder to 5pm", "make the weekly update Tuesdays", "have the
CI watch look every 30 minutes": I find it with the `automation` tool's `list` and change
it in place with its `change` op, sending only what changes. Its card is headed
`save this change?` and shows the automation as it would be, with `next run` where a new
one says `first run`: `1 Save` says
`The automation runs this way from its next time.`, `0 Don't save` says `Nothing is saved.`,
and a correction typed under it goes back to me. Saved, it leaves one dim line —
`drink water · changed` — and it keeps its id, its history, where it runs and the
conversation its news arrives in. No second automation is left beside the old one.

**Or press `→` then `e`** on its row in `/automations`, and its own line lands in the
message box, exactly as it would have to be typed:

```
/automations edit 3f2a9c1e5b7d4a60 title "weekly update" zone America/Toronto every "0 9 * * 1" do "draft the weekly update from the git log" folder /home/you/code/app
```

Change what you want and press enter. Nothing is saved yet: a card headed
`save this change?` shows the automation as it would be, and `1 Save` saves it —
`saved weekly update · next Mon 12 Oct at 09:00`. Any clause you leave out stays as it
was. A new schedule is worked out again from now, and a finished one-time automation given
a new moment wakes again. A watch whose look or condition changed forgets what it had
seen, so its next yes speaks.
Change what you want and press enter: the same `save this change?` card, and `1 Save` says
`saved weekly update · next Mon 12 Oct at 09:00`. Any clause you leave out stays as it was.

A watch that looks through a tool is written without its look — a tool's arguments are not
something anybody types on one line — and the edit leaves that look as it was.
Either way, a new schedule is worked out again from now, and **a finished one-time reminder
given a new moment wakes again**. A new rhythm keeps the zone the old one was read in. A
watch whose look or condition changed forgets what it had seen, so its next yes speaks. A
paused automation stays paused until it is resumed. A change cannot turn a watch back into
a plain reminder: delete it and ask for the reminder.

**I have no verb that changes an existing automation in place.** In a conversation I can
pause, resume, run, delete, list it and read its history; changing one is `e`, or a new
proposal that replaces it.
A watch that looks through a tool is written without its look in the typed line — a
tool's arguments are not something anybody types on one line — and the edit leaves that
look as it was.

## History — every run, read a run's whole result, why did I get this reminder, and open where it was asked

Expand Down Expand Up @@ -598,11 +607,13 @@ along, and switching one off.

You never name a tool. When what you say is a reminder, scheduled work or a watch, I call
the **`automation`** tool to propose it, and its card is what you see. The same tool
**changes** one that exists — "move my reminder to 5pm" — on a `save this change?` card,
**lists** the automations with their ids, **pauses**, **resumes**, **deletes** and **runs**
one now, and reads one's **history** — so "pause the CI watch", "delete the weekly update"
and "what did the nightly bump do on Tuesday" work in any conversation. A change made that
way needs no card and leaves one dim line in the conversation — `ci on main · paused`,
`weekly update · deleted` — while making a new one always goes through its card.
and "what did the nightly bump do on Tuesday" work in any conversation. A pause, resume,
delete or run made that way needs no card and leaves one dim line in the conversation —
`ci on main · paused`, `weekly update · deleted` — while making a new one or changing one
always goes through its card.

A run of scheduled work ends by calling **`automation_report`** with `done` or `incomplete`
and a short summary: that summary is the line you read, and a run that never calls it is
Expand Down
9 changes: 4 additions & 5 deletions internal/manual/chat/keys.md
Original file line number Diff line number Diff line change
Expand Up @@ -2621,17 +2621,16 @@ verbs, and their letters work only while that strip is drawn:
A verb that cannot work on the row is not drawn: `s` only while a run is in hand and `r`
only while none is, no `pause` on a finished one, no `o` with no conversation behind it. A
click on a row opens it; the wheel walks. Home's `automations` panel has none of these
keys — its rows open this place, and `ctrl+e`, `ctrl+x` and `alt+e` do nothing to an
automation.
keys — its rows open this place — and `ctrl+e`, `ctrl+x` and `alt+e` do nothing to one.

**On an automation's card** — `wants to remind you`, `wants to schedule work`,
`wants to watch for something` — `1` Save · `2` Save and run it now (`2` Save and check it
now on a watch; no `2` on a reminder or on work kept in a separate worktree) · `0` Don't
save · `o` Change…, which asks for your correction in words · `esc` later, which puts the
card off until `alt+y` raises it. A card raised by a typed `/automations add` or `edit` —
`save this automation?`, `save this change?` — has only `1` and `0`. In home's `ask here`
pane the same keys answer it while the pane has the keyboard, and `esc` there hands the
keyboard back to the list instead.
`save this automation?`, `save this change?` — has only `1` and `0`; a change I raise
has `1`, `0` and `o`. In home's `ask here` pane the same keys answer it while the pane has
the keyboard, and `esc` there hands it back to the list.

*Automations* says what each verb and each answer does.

Expand Down
7 changes: 4 additions & 3 deletions internal/manual/chat/questions.md
Original file line number Diff line number Diff line change
Expand Up @@ -1307,7 +1307,8 @@ card — the title, when it first runs, what it says or does, where work runs an
for anything but a reminder, what one run may take and spend (*Automations*,
*The card*) — and its question goes on the block above your box. The first line
says the kind: `wants to remind you`, `wants to schedule work` or
`wants to watch for something`. The line under it is
`wants to watch for something` — or `save this change?` when I change one that exists,
with only `1 Save` and `0 Don't save`. The line under it is
`nothing is saved until you say so`.

The answers are `1 Save`; `2 Save and run it now`, or `2 Save and check it now` on
Expand Down Expand Up @@ -1439,8 +1440,8 @@ It depends on what was proposed, and the card says which beside `1 Save`:
(on a watch, `Saves it, then takes one look now, here, while you watch.`), and
`0 Don't save` says `Nothing is saved and nothing runs.` On the card a typed
`/automations add` raises, `1 Save` says
`It runs on this schedule while codeaf is open.`; on an edit's,
`The automation runs this way from its next time.`
`It runs on this schedule while codeaf is open.`; on a change's — typed with `edit`, or
asked of me in a conversation — `The automation runs this way from its next time.`

Saving is not forever: pausing or deleting one later is `/automations`, or asking me
(*Automations*). A rule such as "always use tabs" is not an automation — it has no
Expand Down
6 changes: 6 additions & 0 deletions internal/manual/chat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2585,6 +2585,12 @@ func TestTheChatManualAnswersTheQuestionsPeopleAsk(t *testing.T) {
{"I pressed esc on a reminder card and it did not say no", "automations"},
{"where did 2 change when or where go on the standing card", "automations"},
{"how do I change the time on a standing card", "automations"},
// The wave that gave `automation` its `change` op. Before it the chat
// could only propose a second automation beside the first, and the page
// said so; these are the sentences a person says to the one they have.
{"move my reminder to 5pm", "automations"},
{"can you reschedule my water reminder", "automations"},
{"why is my old reminder still there after I asked you to change it", "automations"},
// The wave that gave the engine on the far machine its own ambient
// side. This is what a person asks before they rely on it — and what an
// attached window keeps running there now is that machine's automations.
Expand Down
Loading
Loading