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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ window → indexed elements → operation + target → guarded execution → obs
- One request chooses the operation, every target head and risk on the same observed state. Consume only the matching head. The model may choose only observed targets and supported operations.
- Never replay a mutation. Consume its decision before input and record delivery before post-action observation. Unknown delivery stops with history intact. Preserve returned focus facts even if read-back fails.
- Capture-time uniqueness and a fresh target census guard execution. Unknown identities, incomplete reads and process replacement refuse input. Preserve secure classification before value reads, redacted history, separate AX deadlines and bridge replacement after transport failure.
- Keep the action space small. INCREMENT/DECREMENT once changed Calculator from 6/6 to 1/8 despite identical observations; they stay unoffered. Measure any new operation against tasks that do not need it.
- Keep the action space small. Measure any new operation against tasks that do not need it.
- Preserve separate content/control publication budgets. A content-heavy window must not hide its toolbar. Static text names come from their values. Inspect traversal depth before declaring an Electron app inaccessible.
- SELECT needs observed options. Do not expand it based on an unmeasured assumption that closed menus expose choices. Popup/menu-opening presses can activate apps and stay withheld.
- No token-probability reconstruction, extra planner or new compatibility framework without a concrete product requirement.
Expand Down
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ Give it one app and one natural-language goal. Each observation produces a numbe
Requires macOS, Python 3.11+, Swift and Accessibility permission for the terminal or app launching the bridge. No Screen Recording permission is used.

```sh
uv sync
uv sync --extra agent
scripts/build.sh
uv run --env-file .env open-computer-use inspect --no-open
uv run --extra agent --env-file .env open-computer-use inspect --no-open
```

Open the inspector at `http://127.0.0.1:8767`. It shows the observed element table, pending decision, risk and executed history. The server binds to loopback and requires its startup token for changes.
Expand All @@ -35,13 +35,15 @@ DECISION_MODEL=deepseek-flash
DECISION_API_KEY=your-key
```

The model-driven Agent and Inspector use the optional `agent` extra (`pip install "open-computer-use[agent]"` or `uv sync --extra agent`). Their public Python API is unchanged.

The optional Jev backend answers structured choice heads instead of writing JSON. Set `JEV_API_KEY`, optionally `JEV_BASE_URL` and `JEV_MODEL`. Jev cannot generate field values; `TEXT_MODEL_API_KEY`, `TEXT_MODEL_BASE_URL` and `TEXT_MODEL` configure that helper, falling back to the decision backend settings. The product reads only user-configured environment variables.

The chat backend supports JSON schema and JSON object responses. `DECISION_RESPONSE_FORMAT=json_schema` or `json_object` overrides provider detection; `DECISION_REASONING=default` keeps the provider's reasoning behavior. No token logprobs are requested.

## From a coding agent

A coding agent can be the decision model itself. `install.sh` compiles the bridge, installs `open-computer-use` as an editable uv tool and copies [the skill](skill/open-computer-use/SKILL.md) into `~/.claude/skills` and `~/.codex/skills` when they exist. No decision backend or key is involved.
The native CLI has no Python runtime dependencies and loads neither the model client nor the Inspector. A coding agent can be the decision model itself. `install.sh` compiles the bridge, installs `open-computer-use` as an editable uv tool and copies [the skill](skill/open-computer-use/SKILL.md) into `~/.claude/skills` and `~/.codex/skills` when they exist. No decision backend or key is involved.

```sh
open-computer-use observe Calculator # header with fingerprint, then one element per line
Expand All @@ -67,7 +69,7 @@ with Agent("TextEdit", f"Replace the document with exactly '{sentence}'.", verif
print(agent.state["verified"])
```

`verify` receives the final observation: `True` confirms the goal, `False` rejects `DONE`, and `None` means the result could not be judged. Without it, `DONE` is the model's claim.
`verify` receives the final observation: `True` confirms the goal, `False` rejects `DONE`, and `None` means the result could not be judged. Without it, `DONE` is the model's claim. `verified` checks the goal only; it does not certify the whole background interval. An optional `focus_check` callback connects the caller's existing full-task monitor: `True` means clear so far, `False` a recorded activation, and `None` unavailable. A violation or unavailable configured monitor stops before further input. The caller must keep monitoring through cleanup before accepting the task.

Consequential operations are held at `risk >= 0.5`. Inspect the decision before calling `agent.approve_pending()`, or supply an `approve(decision)` callback. The rating shares the decision request; it is the same model's judgment, not an independent safety reviewer.

Expand All @@ -85,11 +87,15 @@ Consequential operations are held at `risk >= 0.5`. Inspect the decision before

There is one native executor. Activation flags, Codex-host execution, mouse input, dragging, screenshots and visual fallback have been removed. A canvas without usable accessibility controls is outside scope; it cannot be made a successful background task by taking the screen.

Known `took_focus` facts stop the Agent, including facts returned with post-dispatch errors. CLI `act` returns `focus_violation` and exit code 3; this is never a retry instruction. Other exits are 0 for a delivered command, 1 for refusal/staleness, and 2 for uncertain or unconfirmed delivery. None establishes whole-task success.

Native APIs can still cause app-specific side effects. `took_focus` records observed endpoint changes only. Full-run acceptance independently listens for macOS application-activation events, including a transient activation followed by a return to the original app. A run that changes the requested value but steals focus fails acceptance. Bringing the previous app back does not undo that failure.

## Delivery and identity

Every result and bridge exception carries `disposition`:
Every action result and bridge exception carries `disposition`. Action replies missing or contradicting it are protocol failures: the bridge is reset, the in-flight mutation remains uncertain, and no input is replayed. `AXBRIDGE_BIN` overrides must implement the current protocol.

The delivery states are:

| Delivery | Evidence | Retry |
| --- | --- | --- |
Expand All @@ -110,7 +116,7 @@ Secure fields are classified before value reads; their values and descendants ar

One app per run, including its accessible windows and open menus. Publication budgets and identity census limits are separate; incomplete observations are explicit. A run stops at 40 operations, 80 decisions, three consecutive unchanged operations, or before delivering the same action a third time on the same observed state. WAIT is exempt from repeat stopping. Existing history supplies the count; a stop is blocked, never task success. Observed state can hide real progress, and changing or unobserved state can evade this heuristic. Model task success varies independently of reliable execution.

`examples/calculator.py` and `examples/textedit.py` read their intended results back from the app. An endpoint sample showing the app inactive is not proof it stayed inactive throughout. Focus acceptance requires the independent event record over the same task interval.
`examples/calculator.py` delegates to the shared owned-instance acceptance runner; supply `--max-calls`, `--usage-file` and `--output` for your approved batch. Its optional `--goal` and `--expect` still customize the single task. `examples/textedit.py` edits an already-open disposable document and reports its goal reading only. An endpoint sample showing the app inactive is not proof it stayed inactive throughout. Focus acceptance requires the independent event record over the same task interval.

[The background acceptance runner](evals/README.md) retains the 16 legacy task definitions, reads real app results independently and requires a complete activation-event record through cleanup. It preserves already-running user apps and records unavailable cases explicitly; historical scores are not current results.

Expand Down
4 changes: 4 additions & 0 deletions docs/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,7 @@ One request chooses the operation, a speculative target for each operation, fiel
The inspector displays the table, pending decision, risk and executed history. It binds to loopback, checks Host/Origin, requires a startup token and serializes commands. It has no activation control. Its `DONE` state remains a model claim unless an outcome check supplied evidence.

Budgets remain 40 operations, 80 decisions and three consecutive unchanged actions. History also records each pre-action fingerprint and editing parameters. Two delivered matching state/action pairs block a third input, including A → B → A cycles; refusals do not count and WAIT is exempt. The check runs before field-value generation, uses the existing redacted history path, and rechecks freshness before refusing. No parallel counter or planner state is added. A blocked repetition does not establish task completion or progress in hidden fields.

The native CLI and optional model Agent share execution and delivery semantics, not a claim of whole-task completion. A delivered command is not a completed goal; `verified` is a goal reading, not full-interval focus evidence. Known endpoint focus facts stop the Agent and survive dispatch errors; CLI actions report them as `focus_violation` (exit 3). A caller may pass the existing full-task observer as `focus_check`, checked before a command, after prediction/value preparation and after the command. False or unavailable evidence stops further input. The acceptance owner continues observing through cleanup; it alone can establish the complete interval. No new session service or second activation observer is introduced.

The core package has no runtime dependencies. Agent imports are lazy, and only the optional `agent` extra loads HTTP/model and Inspector code. The separate model choices, speculative heads and Jev text helper remain in that optional layer: their decision contract is unchanged in this cleanup. Replacing that contract or dropping standalone Agent mode remains a separate measured product decision.
3 changes: 2 additions & 1 deletion evals/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ The 16 task goals and result checks come from the legacy evals branch. Its scrip
Approve a usage-billed batch before running it. Select tasks explicitly and run only one writer for a shared usage file:

```sh
uv run --env-file .env python -m evals.run \
uv run --extra agent --env-file .env python -m evals.run \
--tasks calc-multiply textedit-edit --reps 1 \
--max-calls 20 --usage-file /absolute/path/batch.json \
--output /absolute/path/new-acceptance-folder
Expand All @@ -18,6 +18,7 @@ A pass requires all of the following:
- The independent app check is false after setup and true after the real Agent run. No expected answer is injected by a scripted solve. Calculator reads its display, TextEdit its document, Settings its pane title (or a recognized detail heading while the title is unavailable), Safari its window title, and Dictionary its definition text. Finder reads the exact owned window's target directory, so row selection cannot satisfy opening a folder.
- System Settings navigation tasks hold operations other than native row selection, scrolling, waiting and Escape, using the existing approval callback; they never approve unrelated preference changes.
- The Agent ended with `done`. `DONE` with a false reading is recorded separately as `false_done`.
- The same observer supplies `Agent.focus_check`, so an activation during model prediction or field-value preparation can withhold the next input. An unavailable configured monitor also stops.
- The activation-event observer started before app setup and ended after cleanup. **Any activation event invalidates this run**, including during cleanup. An unrelated human app switch is conservatively inconclusive; the runner does not label it task-caused or silently exclude it. It never restores foreground focus.
- The owned instance/window was confirmed closed. Missing observer endpoints, native errors and cleanup failures invalidate acceptance.

Expand Down
15 changes: 14 additions & 1 deletion evals/run.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Real model tasks, independent result readings, and lifetime-wide focus events."""
import argparse
from dataclasses import replace
import json
import queue
import subprocess
Expand Down Expand Up @@ -67,6 +68,12 @@ def acquire(self):
if event["event"] in {"unavailable", "error", "eof"}:
raise RuntimeError(event.get("detail", "Ownership monitor ended before acquiring an instance"))

def focus_clear(self):
if (not self.events or self.events[0]["event"] != "ready" or self.process.poll() is not None
or any(e["event"] == "error" for e in self.events)):
return None
return not any(e["event"] == "activated" for e in self.events)

def close(self):
self.process.stdin.close()
# The native owner closes only what it created, before its stopped event.
Expand Down Expand Up @@ -128,7 +135,7 @@ def phase(name):
if record["before"] is not False:
raise RuntimeError("The initial result check was not false after setup")
phase("agent")
with Agent(str(pid), task.goal, menus=False, risk_threshold=0,
with Agent(str(pid), task.goal, menus=False, risk_threshold=0, focus_check=lease.focus_clear,
approve=lambda d: task.app != "System Settings" or d["operation"] in {
"CLICK", "SCROLL_UP", "SCROLL_DOWN", "WAIT", "PRESS_ESCAPE"}) as agent:
try:
Expand Down Expand Up @@ -177,11 +184,15 @@ def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--tasks", nargs="+", choices=[t.id for t in TASKS], required=True)
parser.add_argument("--reps", type=int, default=1)
parser.add_argument("--goal", help="Override the goal for a single selected task")
parser.add_argument("--expect", help="Override its independent expected result")
parser.add_argument("--max-decisions", type=int, default=16)
parser.add_argument("--max-calls", type=int, required=True, help="Approved total batch cap, including prior stages")
parser.add_argument("--usage-file", type=Path, required=True, help="Shared cumulative batch counter; run one writer at a time")
parser.add_argument("--output", type=Path, required=True, help="New artifact directory outside the checkout")
options = parser.parse_args()
if (options.goal is not None or options.expect is not None) and len(options.tasks) != 1:
parser.error("Goal/result overrides require exactly one task")
if min(options.reps, options.max_calls, options.max_decisions) < 1:
parser.error("Budgets and repetitions must be positive")
root = Path(__file__).resolve().parents[1]
Expand All @@ -202,6 +213,8 @@ def main():
continue
if budget.data["calls"] >= options.max_calls:
return
task = replace(task, goal=options.goal if options.goal is not None else task.goal,
expected=options.expect if options.expect is not None else task.expected)
record = run_once(task, binary, output / f"{rep + 1:02d}-{task.id}", options.max_decisions)
stream.write(json.dumps(record, ensure_ascii=False) + "\n"); stream.flush()
print(json.dumps({k: record[k] for k in ("task", "passed", "status", "before", "after", "monitor_complete", "cleaned", "focus", "error")}), flush=True)
Expand Down
Loading
Loading