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
31 changes: 31 additions & 0 deletions docs/adr/0101-gui-tests-preserve-desktop-focus.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,37 @@ report and CI summary list them as skipped. A preconfigured runner can set
`GRAFF_NATIVE_REQUIRE_OS_INPUT=1` to make those permissions mandatory. An ordinary
hosted-runner pass does not establish OS input coverage when permission is absent.

## Engine follow-up: generated and directly launched native helpers

The Electron app and its tests have left this repository. The macOS `open -g`
shim ([0299](0299-tool-commands-launch-apps-in-the-background.md)) does not contain
a directly executed helper that activates itself. #832 recurred with a generated
native fixture copying `cx.activate(true)`; capture also re-entered a leased
root entity and aborted. A later corrected capture fixed reentrancy, not isolation.

`src/gui_verification.zig` supplies the same contract to root and child agents,
across REPL, ACP, models and tool capability gates. Inspect startup, window
creation, reopen and later activation before execution. Default generated GUI
verification to headless/hidden non-activating fixtures or an isolated graphical
session, disposable synthetic state and cleanup of owned windows/processes.
Reuse source-compatible capture helpers; finish typed root/entity updates before
rendering or dispatching callbacks that may re-enter the root.

A screenshot/native-verification request does not authorize desktop takeover.
Only explicit user opt-in permits a bounded foreground diagnostic on their desktop;
respect app switches or pointer movement rather than reclaiming focus. A denied
computer-use tool does not authorize shell-based activation/global-input fallback.
Legitimate explicitly requested computer-use remains supported within its scope
and tool permissions. Missing foreground coverage is reported, not substituted.

`GRAFF_GUI_FOREGROUND` bypasses the shim only for literal `1`, consistently in the
parent environment and per-command wrapper. The agent must not set it to create
consent. This remains an instruction boundary, not an OS sandbox: arbitrary shell
programs and self-activating apps are not mechanically contained. Only OS-observed
checks in an isolated graphical session establish focus/cursor preservation and
cleanup; prompt delivery and mocked launch tests do not. Preserve failed-helper
exits and side effects after recovery without mislabeling them production crashes.

## Consequences

The hidden renderer can exercise composer input, Tab traversal, split resizing,
Expand Down
33 changes: 29 additions & 4 deletions docs/adr/0220-claude-prompt-cache-forks-and-pricing.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,19 @@ these gaps:
that shares no prefix gets no `cache_control`. This covers Anthropic and
Anthropic models through OpenRouter. It is sent uncached (1x) instead of
written (1.25x).
- **A turn-opening request keeps the TTL of the last gap inside a turn.**
The #1320 rule is unchanged within a turn.
- **Separate model latency from the returning user's idle signal** (#1898).
The #1320 rule is unchanged within a turn, and a turn-opening request still
inherits the last model gap. An interactive root conversation also asks for
the hour when it has at least two clean human user turns and either:
- Its turn-opening request gap is between 5 and 55 minutes inclusive; or
- The previous context meter is at least 64,000 tokens, even without a
previous request timestamp (as can happen at an ACP turn boundary).

The idle decision is latched throughout the turn: a fast tool follow-up
must not downgrade its final breakpoints before the next user pause. Tool
results, peer wakes, and provenance-marked notifications are not human
turns. Subs, unattended one-shots, and isolated compaction requests do not
acquire this idle premium. Both REPL and ACP use this shared request policy.
- **1-hour writes are billed at 2x** on metered seats. A price row that
states its own write multiplier is billed as the row says.
- **5.5-era models get graff's effort as shown**, `medium` included.
Expand All @@ -76,9 +87,23 @@ these gaps:
suffix is also kept verbatim after compaction.
- Sessions on Sonnet 5.5 and Fable 5.1 at graff's default now run at
`medium`, which is lower than before.
- The original turn-boundary rule avoided paying 2x for a cold prefix but
ignored the *next* pause. After observing a useful pause, retaining the hour
can trade the next full 1.25x rewrite for a cache read. The current rewrite
still costs 2x; it cannot retroactively recover an expired entry. A request
gap includes model runtime, so it is a conservative predictor rather than
an exact measurement of human idle. The 55-minute bound leaves headroom
under the hour; a longer observed gap alone does not justify an upgrade.
- Returning interactive contexts above 64k anticipate a pause even when their
request clock is fresh. This deliberately pays the hour premium for large
conversations that never pause or never return. The first prompt and small
conversations without an observed useful gap keep the cheaper default.
This heuristic survives frontend request-clock resets without a global
cross-session idle-history cache. It does not guarantee provider-side cache
promotion on a read; the requested TTL, not an assumed hit, is the contract.
- Not done yet:
- Keeping the cache across idle gaps and long waits, by writing the hour at
a turn boundary, or with `max_tokens: 0` refreshes.
- Exact user-idle measurements across process restarts, and proactive
`max_tokens: 0` refreshes.
- Mid-session system-prompt changes (goal, recorded constraints, `/strict`,
`/ultracode`) as mid-conversation system messages.
- Tool changes as inline tool additions.
Expand Down
28 changes: 28 additions & 0 deletions docs/adr/0341-verification-matches-the-requested-application.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# 0341. Verification matches the requested application

Status: accepted

## Context

Issue #1899 showed that passing tests and screenshots of a retired frontend
can be mistaken for proof of a current application change. The checkout can
be valid Git history while still being the wrong implementation for the task.

## Decision

When a current-application request conflicts with visible source/release
metadata or frontend architecture, reconcile the manifest and packaging entry
point before editing or expensive verification. Resolve genuine target
ambiguity with the user. Explicit historical-version work remains supported;
never automatically upgrade, switch branches, or discard changes.

Verification claims must identify their implementation scope and limitations.
Historical tests prove historical behavior, not the current packaged frontend.
This guidance is shared by full and lean root prompts on REPL and ACP.

## Consequences

A version difference alone is not an error and does not force remote lookups
on every task. Targeted reads are required only when visible evidence conflicts
with the requested application. Prompt/eval coverage checks that this guidance
survives composition; scripted evals do not prove autonomous target selection.
40 changes: 40 additions & 0 deletions docs/adr/0342-assistant-replay-is-not-prefill.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# 0342. Assistant replay is not an intentional prefill

## Context

A fresh worker starts with a user task, but that does not guarantee every
request ends with a user turn. A Messages reply with `pause_turn` is saved as
an assistant turn and immediately continues without a tool result. Retrying
a failed turn can likewise retain an assistant tail. Serializing that history
verbatim asks the backend for assistant prefill, which some models reject
before any tools run (#1895).

The worker's pinned-seat fallback also treated this invalid-history rejection
as a reason to try another seat. That changes the model, not the malformed
conversation, and can produce a second deterministic rejection.

## Decision

At the shared Messages request-body boundary, preserve a trailing assistant
reply and append one harness-authored user continuation. Store the continuation
in the agent's history, not only in serialized output: subsequent retries and
signed-thinking replay must see the same prefix. A user task or tool-result
turn needs no extra note. Existing dangling-tool repair runs before this guard
and retains its explicit interrupted-tool outcome rather than replacing it
with prose.

Use the same boundary for native credentials, gateway traffic, workers and
roots, and both REPL and ACP. Other wire formats are unchanged. A residual
assistant-prefill rejection remains a structural failure and must not earn a
pinned-seat fallback or an unchanged worker retry.

## Consequences

A continuation costs a small user turn only when a request would otherwise
end with an assistant reply. Existing assistant text, signed reasoning and
tool evidence are preserved. Rebuilding the body is idempotent; a second
rebuild does not append another continuation or change the request bytes.

Offline regressions exercise a pause before the first tool, native and gateway
serialization, login-token serialization, root/worker callers, interrupted-tool
receipts and the no-fallback failure path. They do not call a live backend.
2 changes: 2 additions & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,8 @@ record only when you need the evidence or the edge cases.
| [0338](0338-acp-answers-initialize-during-boot.md) | `graff acp` answers `initialize` at a boot phase boundary: a non-blocking check takes a complete first `initialize` off the one stdin reader, answers it through the engine's `handleLine`, takes it off the reader only once the whole answer was flushed, and replays it silently at loop start so dispatch state is rebuilt by the late-path code; any other first line, or a failed answer, is left for the loop. `Graff.AcpEarlyInit` proves the line bookkeeping only; byte equality rests on the Zig real-engine cube test and `scripts/test-acp-early-init.py`. Builds without ACP compile none of it. |
| [0339](0339-a-one-shot-joins-the-companion-handshake-with-a-budget.md) | A one-shot joins the prewarmed companion with a 250 ms budget (`GRAFF_MCP_COMPANION_BOOT_MS`, 1500 ms on Windows; unset/0/garbage mean the default, above the 60 s cap clamps to it). Over budget the attempt keeps running and merges into the live registry when it lands instead of being cancelled or restarted, so a warm companion's catalog is unchanged and a silent one costs the budget, not the probe plus handshake caps. The join reports `.late`, never "connected"; a failure that lands after the budget is recorded and printed (stderr on `-p`), and a stop teardown asked for is recorded as `.stopped` and printed as nothing. Teardown kills the child and joins the worker on both platforms (`stopAtExit`, also on the one-shot's own `std.process.exit` paths); macOS measured at ~0 ms, the Windows branch cross-compiled only (`zig build -Dtarget=x86_64-windows`) and unmeasured. A cold-and-slow companion can miss the one-shot catalog, visibly. |
| [0340](0340-a-cold-catalog-cache-never-holds-boot.md) | Boot never waits on a live model-catalog GET to show the prompt: a keyed dynamic provider (or Kimi) with no disk cache boots on its baked rows, its fetch runs on a detached worker, and its list activates on the main thread at the next `ensureForQuery` beat after it lands (`catalog_landing`, `router_catalog_boot.activateLanded`). Only a `--model` naming a row the baked table lacks keeps the bounded 800 ms wait. A failed fetch activates nothing, prints nothing at boot, and is not retried in-process. A 100 ms cold budget was rejected: it costs every first keystroke and almost never covers a real round trip. |
| [0341](0341-verification-matches-the-requested-application.md) | Reconcile visible current-app source/frontend conflicts before edits; historical tests prove only that implementation, and explicit historical work never forces an upgrade. |
| [0342](0342-assistant-replay-is-not-prefill.md) | Messages requests preserve a trailing assistant reply and append one durable user continuation; replay is not prefill, and a residual prefill rejection never earns a pinned-seat fallback. |

## When to write one

Expand Down
Loading
Loading