Skip to content

sdk: who made each step: Author, act, and the host's author map - #474

Merged
foxnne merged 1 commit into
mainfrom
sdk/authors
Oct 16, 2026
Merged

foxnne merged 1 commit into
mainfrom
sdk/authors

Conversation

@foxnne

@foxnne foxnne commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator

Part of fizzyedit/plans#14: plans/SHARED_HISTORY_PLAN.md, step 2, as amended in fizzyedit/plans#21. This is the SDK half; the app half (the frame's input author, tapes acting, activity bubbles naming others, the integration check) follows as a PR stacked on this one.

What changes

The host learns who made each step of a document's history. It keeps this on the channel owners already report steps through (Host.recordStep, #456), not a second one.

  • sdk.Author: the person, an agent, a tape, or nobody known (unattributed, a missed site). Identity is kind, plugin and session; the label ("Claude", "the tour") is display only.
  • Who is acting:
    • act(author) and endAct(outer): the innermost act wins. An outermost act starts a run (one agent call, one tape op).
    • acted() and actAgain(stored): work put off (pixi's call that waits out the person's stroke) runs as the author and run it was asked in.
    • author(): the innermost act, else the frame's input author (setInputAuthor, for the app to set where input is consumed), else unattributed.
  • recordStep:
    • keeps who made every step that isn't the person's, per document, by step id, plus the document's top step;
    • counts steps made with nobody acting (unattributed_steps, which the app's tests will assert stays 0);
    • forwards a step to the recorder only while it records, as before.
    • Working alone, nothing is kept: a document gets an entry the first time someone else acts on it.
  • Queries: stepAuthor, topStep and mayMerge (whether the acting author may fold an edit into a step; it keeps an agent's keystrokes from joining the person's run of typing). forgetDocumentAuthors runs when a document closes, beside releaseDocumentClaims.
  • Step.top: the entry on top after the step, so Undo can name whose step it takes back. An owner that leaves it 0 still gets a sensible top after added and joined.
  • docs/PLUGINS.md:
    • owners report every step and gate only the encoding on recording;
    • an Authors entry: act around commands run on someone's behalf, actAgain for deferred work, mayMerge for runs.

Nothing calls act yet. Owners report steps only while recording until the plan's step 3. So nothing a person sees changes in this PR.

SDK impact

  • None
  • Core-only or additive: reaches plugins at the next SDK release
  • Fingerprint moved: recorded in sdk/src/version.zig (0x78f2a3cc371828ef), sdk_version untouched, PR labelled sdk
  • SDK release: bumps sdk_version, lists the sdk PRs since the last sdk-v* tag

For plugin authors: additive. Step has a new defaulted field top, and the host has new functions. Nothing existing changes shape.

After the release that carries this:

  • agent's agentCall acts as its connection;
  • chat acts around its ACP writes;
  • pixi's waiting calls store acted() and run in actAgain.

Verified

  • macOS:
    • Unit tests in sdk/src/Host.zig and sdk/src/Author.zig:
      • authors and the top through added, person, undone and restarted steps;
      • nesting, runs and actAgain;
      • interning and late labels;
      • mayMerge both ways;
      • the unattributed count.
      • zig build test: 568/569 passed (1 skipped). check-tests-reached passes.
    • Builds: zig build, test-sdk-version, check-web and test-integration. Results are in the comment below once they finish.
    • Store plugins: pixi and agent build against this sdk/.
  • Windows: CI cross-compile.
  • Linux: CI.
  • Web: check-web.

🤖 Generated with Claude Code

The first half of plans/SHARED_HISTORY_PLAN.md's step 2 (as amended in fizzyedit/plans#21):
who made each step of a document's history, kept by the host on the channel owners already
report through (Host.recordStep, fizzy#456).

- sdk.Author: the person, an agent, a tape, or nobody known; identity is kind, plugin and session.
- Host.act / endAct / acted / actAgain / author: who is acting. Innermost wins; an outermost act
  starts a run; work put off acts again as its stored author and run.
- Host.setInputAuthor: the frame's input author, for the app to set where input is consumed.
- Host.recordStep keeps who made every step not the person's, per document, by step id, and the
  document's top; the recorder still hears a step only while recording. Working alone, nothing
  is kept. Host.stepAuthor, topStep, mayMerge; forgetDocumentAuthors when a document closes.
- Step.top: the entry on top after a step, so Undo can name whose step it takes back.

The fingerprint moves. Nothing calls act yet, and owners report steps only while recording until
the plan's step 3, so nothing changes for a person yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@foxnne foxnne added the sdk Moves the plugin boundary's fingerprint; ships at the next SDK release label Oct 16, 2026
@foxnne

foxnne commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator Author

Gates on this head (macOS): check-fmt, zig build, zig build test (568/569, 1 skipped), check-tests-reached, test-sdk-version, check-web, and test-integration (53/53 steps, 554/554 tests) all pass. fizzyedit/pixi and fizzyedit/agent build against this sdk/ (--fork).

@foxnne
foxnne marked this pull request as ready for review October 16, 2026 21:48
@foxnne
foxnne merged commit ef30c0c into main Oct 16, 2026
11 checks passed
@foxnne
foxnne deleted the sdk/authors branch October 16, 2026 22:07
foxnne added a commit that referenced this pull request Oct 16, 2026
#474 landed squashed; this branch carried its original commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
foxnne added a commit that referenced this pull request Oct 16, 2026
Stacked on #474 (the SDK half); review that first. Part of
fizzyedit/plans#14: `plans/SHARED_HISTORY_PLAN.md`, step 2, as amended
in fizzyedit/plans#21.

## What changes

The app now says who is acting, using #474's `Host.act` and
`setInputAuthor`.

- **The frame's input author.**
- `Editor.tick` sets it where input is consumed: around the active
document's tick, native menu actions, the layout's draw and floats, then
around plugin keybinds, the keymap, overlays and the palette.
- It clears it around plugins' `endFrame`. Plugins' `beginFrame` and
event delivery run with none, as the plan says: they act for themselves,
or are nobody's.
- The author is the person's, or a tape's while one drives
(`Demo.author`). The demo player and the live driver stop for a real
press or key before the frame's input is consumed, so a frame a person
takes over is theirs.
- **Tapes act.** A tape's command ops run inside `act` as the tape
(`Demo`'s stage `command`), and a live tape's as whoever it plays for. A
demo's author is its title; a live tape played by nobody in particular
is "a tape".
- **`automation.Api.playAs(bytes, author)`** (service version 6): a live
tape played for an author. An agent's pointer tape (`fizzy_act`) is then
the agent's. Plain `play` leaves the tape its own author. The service
tells the app through a new `OnAuthor` hook, beside its `Other`.
- **Bubbles name agents.** An edit made while an agent acts shows as
"Claude · Draw". The person's own edits stay plain, as the plan has it,
and so does a demo narrating itself. The plan says "others", but
prefixing every bubble of a playing demo with its title cluttered the
demo; that's a judgement call for review.
- **The integration check.** The whole-editor test asserts the tour adds
no unattributed step. Owners report every step only from the plan's step
3, so today it guards the steps reported while recording.

## SDK impact

- [ ] None
- [x] Core-only or additive: reaches plugins at the next SDK release.
The automation service is versioned on its own (`service_version` 5 →
6), so a plugin built for version 5 gets null from `getServiceTyped` on
this host and degrades. The agent plugin is the one consumer: it moves
to `playAs` with its repin.
- [ ] Fingerprint moved
- [ ] SDK release

## Verified

- [x] macOS:
- **Unit test:** an agent-acting edit's activity title is "Claude ·
Draw", the person's "Draw".
- **Gates:** `zig build`, `zig build test`, `check-fmt`.
`test-integration`, `check-web` and `test-sdk-version` results are in a
comment below once they finish.
- [ ] Not tried by hand: a demo's edits attributed while it plays, and
an agent's pointer tape through `playAs`. The agent plugin calls
`playAs` after the SDK release; that PR will show it end to end.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
foxnne added a commit that referenced this pull request Oct 17, 2026
…g joins (#479)

Part of fizzyedit/plans#14: `plans/SHARED_HISTORY_PLAN.md`, step 3
(text's half; pixi's follows in its repo).

## What changes

- **Text reports every step** through `Host.recordStep`, not only while
recording. The host keeps who made each one (#474); working alone, it
keeps nothing. Encoding a step's `effect` still happens only while the
document is recorded, so a document nobody records costs one call per
step.
- **Each step carries `top`:** the last applied entry after it
(`History.lastApplied`), for added, joined, undone and redone alike. The
host knows the top step and its author without asking for the line,
which Undo's "Undo Claude's …" label (step 5) needs.
- **One hand's typing never joins another's run.** `History.end` takes
`may_join`. The editor's edit hook fills it from `Host.mayMerge` on the
step on top. When that step is someone else's, VSCode's grouping rule
doesn't get to fold the new keystroke into it: an agent typing doesn't
extend the person's step, nor the person an agent's.
- **Typing calls `documentEdited`,** as its doc comment always said it
did. A person's typing runs no command, so it still raises no activity
bubble.

## SDK impact

- [x] None. Text is built in, and #474's API is used as is.

## Verified

- [x] macOS:
- **New unit test:** an edit with `may_join = false` starts a step of
its own where VSCode's rule would have joined it. The existing history
tests pass `true` and behave as before.
- **`zig build test`:** 573/574 (1 skipped). `check-tests-reached`
passes.
- **`test-integration`:** 53/53 steps, 556/556 tests. The whole-editor
test's check from #475, that the tour adds no unattributed step, now
covers real steps: the tour's typing and commands are each attributed to
the tape. Two warnings print, and both are on unmodified `main` too (a
demo replay mismatch and a float position).
  - `check-fmt`, `check-web`, `test-sdk-version`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
foxnne added a commit that referenced this pull request Oct 17, 2026
## What changes

`sdk_version` goes from 0.2.28 to 0.2.29. Merging this tags
`sdk-v0.2.29` and publishes its tarball.

**Why now:** authors (#474, #475) need a release before plugins can use
them:
- the agent acts as its connection around each command, and plays its
pointer tapes with `playAs`;
- chat acts around its writes;
- pixi's calls that wait out the person's stroke keep their author
(`plans/SHARED_HISTORY_PLAN.md` step 2's plugin PRs).

## SDK impact

- [ ] None
- [ ] Core-only or additive: reaches plugins at the next SDK release
- [ ] Fingerprint moved: recorded in `sdk/src/version.zig`,
`sdk_version` untouched, PR labelled `sdk`
- [x] SDK release: bumps `sdk_version`, lists the `sdk` PRs since the
last `sdk-v*` tag

**`sdk` PRs since `sdk-v0.2.28`:**
- **#474:** who made each step.
- `sdk.Author`; `Host.act` / `endAct` / `acted` / `actAgain` / `author`
/ `setInputAuthor`.
  - The host keeps a per-document author map filled in `recordStep`.
- `stepAuthor`, `topStep`, `mayMerge`, `forgetDocumentAuthors`, and
`Step.top`.
- The fingerprint moved. Everything is additive (a defaulted field, new
functions).

**Reaching plugins, no fingerprint move:**
- **#475:** the automation service's `playAs(bytes, author)`
(`service_version` 5 → 6). Activity titles name an acting agent ("Claude
· Draw").
- **#468:** `core`: `MenuRowOptions.anchor`, a stable name for a menu
row's button.

## Verified

- [x] macOS:
- On this head, `zig build test-sdk-version` passes, and
`scripts/pack-sdk.sh` packs `fizzy-sdk-v0.2.29.tar.gz`.
- fizzyedit/pixi, fizzyedit/agent and fizzyedit/atlas `main` build
against this `sdk/` (`--fork`) with no changes.
- [ ] Windows:
- [ ] Linux: CI.
- [ ] Web:

## Follow-ups

- The repin automation opens `sdk: repin to fizzy SDK 0.2.29` PRs in the
store plugins whose fingerprint moved.
- The agent has no repin automation: its repin is a hand-made PR,
carrying the agent's `act` around `agentCall` and `playAs` for its
pointer tapes.
- Chat's repin carries its `act` around ACP writes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
foxnne added a commit that referenced this pull request Oct 17, 2026
Part of fizzyedit/plans#14 — `plans/SHARED_HISTORY_PLAN.md` step 5a, as
amended for `plans/CONTROL_SERVER_PLAN.md` (fizzyedit/plans#24).

## What changes

`fizzy.undo` and `fizzy.redo` take two optional arguments:

- **`path`**: the open document to undo, which need not be the active
one. It goes through the owner's `historyLine` and `historyGoTo` (text
and pixi both have them), so nothing is focused and the document in
front is untouched. An owner with no line undoes only the active
document, as before.
- **`expect`**: the step id the caller means, the last change for undo
or the next one for redo. When another step is there instead, the call
refuses and names it: *"step 41 is not the one to undo: that is step 43,
Draw, by the person. The document changed since; fix it forward."* So a
caller never takes back a step made since it looked.

Its answer is `.{ .step, .title, .by }`, where `by` is whose the step
was: "the person", or an author's label (`Host.stepAuthor`, #474).

Keys, menus and the palette run Undo exactly as before (`run`, with its
transport and key repeat). Only a call with arguments takes the new
path.

This is the refusal the shared-history plan wanted from the agent plugin
(fizzyedit/agent#21's `undoesAnother`). As a command, it holds for any
caller, and over the control server it becomes a 409 from `POST
/commands/fizzy.undo` with no agent code.

**One trade-off.** `fizzy.undo`/`redo` are now enabled while any
document is open, as text's by-path commands are, because a call may
name a document other than the active one. The command palette therefore
shows Undo enabled when the active document has nothing to undo; running
it then does nothing. The menus still grey it by the active document
(`menu_model.canUndo`). An enabled check that sees the call's target is
the control plan's open question ("Commands that take a document"), so
it isn't decided here.

## SDK impact

- [x] None. A doc comment on `Plugin.historyGoTo` notes that
`fizzy.undo` reaches it; the fingerprint does not move
(`test-sdk-version` passes).

## Verified

- Integration (`headless: the whole editor comes up…`), on the open text
documents: an author acting as "Claude" writes into `notes.txt` by path
while `main.zig` is in front.
  - A stale `expect` is refused, naming Claude, and changes nothing.
- The right `expect` undoes it, answering `.by = "Claude"`, and
`main.zig` stays in front, untouched.
  - Redo and undo again by the same id work.
- Gates on macOS: `check-fmt`, `zig build test` (573/574, 1 skipped),
`zig build test-integration` (53/53 steps), `check-web`,
`test-sdk-version`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
foxnne added a commit to fizzyedit/chat that referenced this pull request Oct 18, 2026
A file the agent writes into an open document (`fs/write_text_file`,
through `text.write`) is now recorded as that chat's agent's step
(`sdk.Author`, fizzyedit/fizzy#474/#475), labelled with the name the agent
gave in `initialize` (`agentInfo`), rather than as the person's.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

sdk Moves the plugin boundary's fingerprint; ships at the next SDK release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant