Skip to content

feat(browser): a browser's log of every agent action and tab event, across CDP, MCP tools and pixels (FD-4967) - #613

Open
earakely-scale wants to merge 12 commits into
mainfrom
edgararakelyan/fd-4967-m819-the-action-log-and-tab-log
Open

earakely-scale wants to merge 12 commits into
mainfrom
edgararakelyan/fd-4967-m819-the-action-log-and-tab-log

Conversation

@earakely-scale

@earakely-scale earakely-scale commented Nov 11, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

M8.19 (FD-4967). A browser now keeps one log of every action agents take on it, whichever way in: CDP commands, MCP tool calls and pixel actions. Beside them are tab events (opened, navigated, closed), each naming what caused it. The browser's app server writes it in the browser computer, so it's the same whether an agent connects directly or through a gateway.

Design note, outcome and five decisions (all option a, Nov 10): M8.19 Action log. Not yet reviewed.

What changes

  • agentenv_protocol/website_browser/log.py (new):
    • the marks (acts, ACTING_OPS);
    • secret removal by shape (redacted_params, redacted_op, redacted_args, redact_url, cut);
    • the driver's tool-call header;
    • BrowserLog, the writer and reader;
    • Causes, which ties a tab event to the command in flight that caused it.
  • The relay (website_browser/cdp.py, moved from browser_cdp.py) logs every command. Each entry has its connection's user and number, the method, the tab its session is on (mapped from Target.attachedToTarget), and whether it worked. Also, when there is one, what the guard did.
  • The browser's control (website_browser/control.py):
    • writes the tab events;
    • registers what its own commands cause, under the op running at the time (ACTOR);
    • reports a tab's URL, and whether a password field has focus there.
  • The app server: a new ComputerAppEnvironment.served_asgi() hook, which serve_app uses. The browser wraps its app with it, so each browser/v1 op becomes an entry: every op made for an agent's MCP tool call, and every acting op agentenv makes itself.
  • browser/v1:
    • a new log {after} op;
    • data/get writes browser-log.jsonl beside browser-state.json, and data/add passes over it;
    • the browser's own data/reset writes a reset line.
  • The page driver sends AgentEnv-Tool-Call (tool, call id, arguments) and the caller's AgentEnv-Role on each call it makes for a tool.
  • ToolContext.mcp (new, default False) is true only for an MCP tool call's context. Without it, the driver's own extension ops, such as computer-ready/v1 status, were credited to a tool. The new integration test caught that.
  • Docs: the protocol README (the browser's log), browsers/README.md and drivers/README.md.

The log

/var/log/agentenv/browser-<name>.jsonl, or the app's own .agentenv/ folder when that can't be written. Each line is one JSON object with seq, at, and one of action, tab or reset.

Field What
action.via cdp, mcp (an op for an agent's tool call) or agentenv (its own acting ops)
action.who the CDP connection's user, or the MCP caller's role (default without one)
action.method / tool, call, op the CDP method, or the tool, its call id and the op it made
action.acts acting or reading, by method or op
action.tab, url the tab acted on and its URL; a createTarget takes the tab it opened
action.params / args acting entries only, with secrets removed
action.ok, error, guard whether it worked, why not, and what the guard did
tab.event, id, url, by opened, navigated or closed; by is command (with entry, via, who), agentenv, exit, stop, or page (with after, the latest acting entry on that tab, or its opener's)

A line's number is taken when what it records begins, and the line is written when it ends. So log returns lines in order, up to the last one whose earlier lines are all written, and its last never moves back. A restarted app server carries on the numbering from the file.

An action still going after 60 s is written by the next read as unfinished, in its place, so it holds back nothing after it. When it ends, an outcome line names it (entry) with what it ended with.

Decisions taken while building, for review

  1. reset_env starts a new log (you chose a, Nov 11). The chaos test showed reset_env reboots the computer into a fresh container, so the log lasts the boot. A task that needs the log from before a reset takes it with snapshot_env first. Only the browser's own data/reset writes a reset line into the same file. Decision 3 in the note is updated.
  2. A URL keeps its path and search terms. Only the values of query keys that name a credential are removed (token, key, code, session, sig, X-Amz-* and the like), along with a password in the URL. The note said this would reuse transfers.py's redaction, but that drops the whole path and query, so it's a small new helper; the note is corrected.
  3. A password check that can't decide counts as a password. If the page can't be asked (no session, a timeout), the typed text is withheld. A password field inside a cross-origin frame can't be looked into, so it's missed, as the note says.
  4. A new tab's first navigation counts as the command's that opened it, unless a navigation command's loader claims it. Otherwise a createTarget at a URL would show its own load as the page's.
  5. Agentenv's own reads aren't logged. That covers record, tabs/list and the rest, and the log op itself. Its acting ops (sites/set, start tabs) are, as via: "agentenv".
  6. A relayed command that types waits on its password check before it's sent (Greptile). Each connection's commands keep the order they came in until each is written to the browser, so a click can't overtake the typing before it. Answers still come back as they're ready.
  7. An action that runs long becomes unfinished, then gets an outcome line (Greptile). Without this, a command Chrome never answers would hold every later line back, or a poller would miss it.

Test plan

  • Unit, test_browser_log.py (new):
    • the marks;
    • URL, cookie, header, body and password-typing removal, and long values cut;
    • the header round trip, and a header too long to carry its arguments;
    • numbering and reads in order up to the first unwritten line;
    • a restarted server carrying on after a half-written last line;
    • an unwritable log warning once;
    • every kind of tab event and its cause, through the browser's control on a fake pipe.
  • Unit, test_browser_cdp.py: every relayed command logged on its session's tab. The ops wrapper logs a tool call's ops and agentenv's acting ops, but not agentenv's reads.
  • Unit, test_browser_data.py: data/get exports the log, and data/add passes over it.
  • Unit, test_tool_context_injection.py: only an MCP tool call's context has mcp.
  • Protocol suite, after merging main: 1,455 passed. The framework unit tests for tool context, page, browser, driver and chromium pass, and the whole unit tier passed on this Mac apart from two tests:
    • the Mac-only Windows disk-recipe test, which fails on main here too;
    • the github_clone tests, which hang on a real git clone on this Mac's network.
  • VM integration (c6i.4xlarge, amd64, local Docker): test_its_log_has_an_entry_for_every_action_over_cdp_page_tools_and_pixels_with_no_secrets, the "Done when":
    • A CDP agent opens a form, types a password and a search, sets a cookie, reads the page, navigates and closes its tab.
    • An MCP agent with a role navigates, clicks, types a password and words, lists tabs and takes a screenshot.
    • Every action has an entry with time, who, tab, URL and method or tool. The two passwords and the cookie value appear nowhere in the log, and the typed words do. Each tab event names the command that caused it.
  • VM regression: every integration test in browser_computer_test, chromium_app_test, website_browser_test and browser_gateway_test, 40 passed. The two failures were in the tests and are fixed: a click before any frame, and a pinned list of browser/v1 methods without log. Both pass on rerun. browser_computer_test is being rerun on the merged head.
  • Greptile rounds 1 to 4 (9 findings, then 2, 1 and 4), all fixed, with tests. Round 4 was fixed at your request, past the three-round cap, without replies:
    • password checks before each keystroke and before a relayed command is sent;
    • typed text and keys withheld in any shape;
    • URLs redacted at any depth;
    • bodies and page content withheld;
    • entries finished when a client leaves;
    • same-document navigations credited to their command;
    • indexed reads, off the event loop;
    • a bound on logged op bodies;
    • per-connection command order;
    • unfinished and outcome lines;
    • typing withheld until a check clears it, for queued CDP commands and for MCP ops that fail first;
    • long and failed URLs redacted, in tool arguments and error messages;
    • a failed back leaving no cause behind.
  • The merged head on the VM: all of browser_computer_test, 23 of 23 with nothing skipped. The "Done when" test and chaos pass again on 46ebf7e4, and are being rerun on f7eca21c.
  • VM chaos (not committed):
    • two CDP agents and an MCP agent act at once, and all are logged;
    • the app server is killed midway: numbering carries on with nothing lost (115 lines, then 160), and the relaunch's tab opens by agentenv;
    • reset_env starts a new log;
    • none of seven secrets is in the log, and all the typed text is.

Not in this PR

  • A browser app inside a desktop computer (M8.26), where pixel input goes to the X server rather than the browser.
  • Per-agent identity for MCP calls (M6.1). Today an MCP caller is its role.
  • The recorder's manifest (M8.22), frames (M8.23), the network log (M8.20) and the exporter (M8.46), which build on this log.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The latest changes appear safe to merge; all four numbered previous findings are addressed.

Summary

Adds a shared browser action log for CDP commands, MCP tools, and agentenv actions.

  • The browser keeps one ordered log of agent actions.
  • Browser log entries identify the tool call that caused them.
  • Tab events point to the action that caused each change.
  • Browser logs can be read and exported with browser state.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  CDP[CDP commands] --> Relay[Browser relay]
  MCP[MCP tools] --> Driver[Page driver]
  Driver --> Ops[Browser operations]
  Agentenv[Agentenv actions] --> Ops
  Relay --> Log[BrowserLog]
  Ops --> Log
  Tabs[Tab events] --> Log
  Log --> Read[Ordered reads]
  Log --> Export[Snapshot export]
Loading

Reviews (6) · Last reviewed commit: "fix(browser): no credentials from long o..." · Reviewed by Greptile

earakely-scale and others added 6 commits November 10, 2026 16:22
…cross CDP, MCP tools and pixels (FD-4967)

M8.19. The browser's app server keeps one log, in /var/log/agentenv/browser-<name>.jsonl, numbered lines that last
the boot: an entry for each CDP command through the relay, each op a driver makes for an agent's MCP tool call (the
page driver passes the call in an AgentEnv-Tool-Call header), and each acting op agentenv makes; and an event each time
a tab opens, navigates or closes, naming the command that caused it, agentenv, or the page after the latest action on
its tab. Entries are marked acting or reading; acting ones keep their parameters with secrets taken out by shape.
browser/v1 log reads it, and data/get exports it beside browser-state.json.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ace (FD-4967)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a driver's own extension ops (FD-4967)

ToolContext gains mcp, true only for an MCP tool call's context, so the page driver's calls for
computer-ready/v1 status no longer carry the AgentEnv-Tool-Call header.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tarts a new one (FD-4967)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…7-m819-the-action-log-and-tab-log

# Conflicts:
#	packages/agentenv-protocol/src/agentenv_protocol/website_browser/environment.py
@earakely-scale
earakely-scale requested a review from a team as a code owner November 11, 2026 04:03
…ke, bodies withheld, entries always finished (FD-4967)

- A relayed command that types is checked for a password field before it's sent, and page input before each event,
  so Tab into a password field is caught; a tool call's typed text or keys, in any shape, are withheld then.
- URL fields at any depth are redacted (a batch of cookies' URLs too), and bodies and page content withheld.
- A command whose client leaves before the browser answers is still written, and what it caused settled; a line held
  longer than STALLED_S no longer holds back reads.
- A move within a document is credited to the command that asked for it, and uses up a pending history move.
- Reads use an index of where each line is, off the event loop; an op body is joined once, and one past 1 MiB is
  logged by its length.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…check; a stalled action is stood in for, not skipped (FD-4967)

- Each connection's commands hold a lock from when they arrive until they're sent, so a command waiting on its
  password check lets none after it reach the browser first; answers still come back as they're ready.
- An action still going after STALLED_S is written by the next read as unfinished, in its place, and an outcome line
  names it when it ends, so a read's last never moves past a line it hasn't returned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
earakely-scale and others added 3 commits November 10, 2026 20:32
…word check clears it (FD-4967)

A typing command cancelled while it waited its turn, its connection gone, was logged with its text: it now starts
withheld and is kept only once a check says no password field has focus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…7-m819-the-action-log-and-tab-log

# Conflicts:
#	src/agent_env/browsers/README.md
#	tst/integration/computer/browser_computer_test.py
…e_browser, as cdp and log (FD-4967)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ing stays withheld, in the browser's log (FD-4967)

- A tool call's URL is redacted before the driver cuts it, and a cut URL's kept part is redacted too.
- URLs in an entry's error, as a failed navigation's names its URL, are redacted.
- An op's typing is kept only once every check it made found no password field focused: one that fails, or never
  got to check (no tab open), keeps it withheld, as the relay already did.
- A reload or move in history leaves its cause only once it's sent and answered: one with nowhere to go, or that
  fails, credits nothing after it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant