Repository navigation
feat(computer): record a computer's display (computer-recording/v1) - #346
Open
earakely-scale wants to merge 9 commits into
Open
earakely-scale wants to merge 9 commits into
earakely-scale wants to merge 9 commits into
Conversation
…stream or by polling @capture computer-recording/v1 in ComputerDriverEnvironment. A driver declares `recording = Recording()` to serve it; the base class owns each recording's life: start and end with their retries, max_concurrent, max_duration_s and a free-disk floor, segments rolled over at max_segment_s, on a size change or after a gap, uploads through the start's write_namespace grant as each segment closes, a fresh grant at end for any that missed, and the manifest. The pixels come from a new @record operation, an async iterator of RecordedFrame (frames the OS streams, which the base class encodes as H.264 with each frame's captured_at as its timestamp) or RecordedSegment (files a native recorder finished). A driver without @record falls back to the base class polling @capture, with a pointer drawn at @pointer_position, capped at 15 fps. PyAV comes in a new `recording` extra; start without it is refused unsupported. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
At full, a driver that declares computer-recording/v1 records without a grant for two seconds: the probe reads each segment back through computer-exec and checks its size, SHA-256 and MP4 header (size, frame count, duration) against the manifest, with no gap between segments; a repeated request_id joins the first recording; a recording past max_concurrent is refused limit_exceeded. Every recording the probe made is ended and removed. The MP4 reader parses moov's tkhd, mdhd and stts, with no decoder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd_computer_recording start_computer_recording starts a recording on a deployed env whose card declares computer-recording/v1, with a write_namespace grant under agentenv-recordings/ that lasts the env's max_duration_s and a margin, or the longest the store signs. end_computer_recording ends it with a fresh grant on the same namespace and registers the uploaded segments and the manifest as FileArtifacts, pointing at the objects, in one FileArtifactUniverse; context.metadata["computer_recordings"][name] says how it went. A recording its driver no longer knows, as after a restart, is registered from what its namespace holds, ended_by error. teardown_run, the teardown_sandboxes step and reset_env end the recordings a run left open first, with reason teardown or reset. Both steps are best effort unless fail_task_on_error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reference driver declares recording = Recording(max_fps=30) and a @record stream: PyAV's x11grab on its display, read in a thread of its own, its frames stamped with the wall clock they carry and the real cursor drawn when asked. A reader that falls behind gets the latest frames. The scaffold installs the recording extra. A new recording task records a scripted drive of the Fixture Desk, and the agent task records Claude Code's run. The integration test decodes the recording: every drive screenshot matches the video frame at its captured_at, within a frame, and the clicked grid cell changes between the screenshots before and after its click. The dev extra gains av, to decode with. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ding steps Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
…keyframe A decoder marks every frame it hands over a keyframe, x11grab's included, and libx264 honours the mark, so a recording from the reference driver's stream was all keyframes: 11.7 MB for 20 s of a mostly still desktop. The encoder clears the mark. PyAV is imported once, at the module's top, as None without the recording extra. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…at end, and probe large or short recordings - max_duration_s is a wall-clock timer, so a stream that stalls, or a native recorder that hasn't closed a segment, still ends as limit and frees its slot. - A segment being sealed when end cancels the recording finishes sealing, and end waits for it. - The failing clock resets only once an item is saved, so an encoder that fails on every frame ends the recording as error after FAILING_S. - A recording that stops itself releases its encoder thread; the stopped recordings no one ended are kept to the latest KEEP_ENDED. - RETRY_S and DISK_CHECK_S name the retry delay and the disk check's interval. - The probe reads segments back in chunks under computer-exec's output cap, checks the video spans the recording with frames enough, and removes the recordings folder when it's left empty. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, and never let a recording block teardown - A recording's namespace is named by its start's request id, so a retried start_computer_recording joins the first attempt's recording and its uploads. - A start under the name of a recording still open is refused, and the open one is left alone. - A recording whose driver forgot it and whose namespace can't be listed is marked failed; ending the open recordings never raises, so teardown always goes on. - A grant takes MAX_SEGMENTS (1000) segments and MAX_RECORDING_BYTES, as a recording also rolls over early on size changes and gaps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A chunk read that failed or came back empty retried the same offset forever. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collaborator
Author
End-to-end results at 57792e9The full numbers are in the Testing section above.
|
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
M7.1 of the CUA env epic (FD-4345):
computer-recording/v1, FD-4426. It follows the reviewed design, CUA Env M7.1: Computer Recording, with option C: the driver streams and the SDK does the rest.What's in it
The SDK (
agentenv_protocol.computer.recording). A driver declaresrecording = Recording()to servecomputer-recording/v1. The base class owns each recording's life:startandend, with their retries;max_concurrent,max_duration_sand a free-disk floor;max_segment_s, on a size change, or after a gap;start'swrite_namespacegrant as each segment closes, withend's fresh grant retrying any that missed;The pixels come from a new
@recordoperation, an async iterator of either:RecordedFrame: frames the OS streams, which the base class encodes as H.264, each timed by itscaptured_at;RecordedSegment: files a native recorder finished.A driver without
@recordfalls back to the base class polling@capture, with a pointer drawn at@pointer_position, capped at 15 fps. PyAV comes from a newrecordingextra (av>=17, which keeps Python 3.10).The probe (
full). It records for 2 s without a grant and reads every segment back throughcomputer-exec. It checks each segment's size, SHA-256 and MP4 header (size, frame count, duration) against the manifest, and that there's no gap between segments. It also checks that a repeatedrequest_idjoins the recording, and thatmax_concurrentholds. A small MP4 header reader reads the files, with no decoder.Core:
start_computer_recording {env_id, name, fps?, max_edge?, cursor?}issues the grant, underagentenv-recordings/<uuid>/.end_computer_recording {name}ends the recording and registers the uploaded segments andmanifest.jsonas oneFileArtifactUniverse,<instance>__recording-<name>.context.metadata["computer_recordings"][name]says how it went.teardown_run,teardown_sandboxesandreset_envend open recordings first, with reasonteardownorreset.ended_by: error.The reference driver. Its
@recordis PyAV'sx11grabon the display, read in a thread, with the real cursor and wall-clock frame times, at up to 30 fps.The scaffold:
recordingextra;recordingtask records a scripted drive of the Fixture Desk;Where this differs from the design
startreturns right away, withstarted_atset to the time it was asked. The manifest carries each segment's first- and last-frame times.start_computer_recordingtakes noscopeorwindowyet. The driver records the whole display until M7.2.agentenv-recordings/<uuid>/, assnapshot_envnames its own, rather than<instance>/<name>/. The instance and name are in the metadata and the universe id.full. Reading a segment back needscomputer-execandpython3, whichfullalready requires.Testing
Unit:
tst/unit10,094 passed, andpackages/agentenv-protocol/tests844 passed. Recording coverage is 99%, and the protocol tests also pass on Python 3.10 with av 17.Integration (
tst/integration/computer/scaffold_test.py, laptop): 3 passed.captured_at.EC2 c6i.4xlarge (agent-environments-admin) at 57792e9:
recordingtask:captured_at, within 45 ms (thumbnail difference ≤ 1.21 of 255);restart): registered from its namespace, with 3 segments plus the manifest,ended_by: error, and none missing;full, 3 times: 51 passed and 1 not applicable each time, withcomputer-recording/v1verified, and no recordings folder left behind.Chaos (16 checks on each machine), with every recording read back and decoded:
limit; what was recorded playsrequest_idjoins the first recording; a third is refused withlimit_exceeded;endagain returns the same manifestendanswersinvalid_argument; the closed segments stayCPU as a share of one core, on a still 1280×800 screen:
@record@recordOne hour at 10 fps (EC2, at 8eab232, the commit before the review fixes) with a click every 10 s:
36,000 frames in 12 segments of 5 minutes; every frame decodes, and the largest gap between segments is 100 ms;
ended as
limitat the defaultmax_duration_sof 3,600 s;driver memory: 100 MB at the start, rising 2–4 MB at each of the first six segment rollovers, then flat at 148 MB for the last 27 minutes.
At 57792e9, a 15-minute run of the same: 9,027 frames in 4 segments, every frame decodes, gaps ≤ 100 ms, and memory went from 100 to 141 MB in the same steps.
An agent at work, recorded both ways (laptop; Claude Code on Sonnet 5.5, the Fixture Desk task, both runs passed):
@record(x11grab)@capture)@pointer_positioncaptured_atEach recording was registered as a FileArtifactUniverse. Each segment's SHA-256 and size match the manifest, and every frame decodes.
Found by the e2e
x11grab marks every frame a keyframe, and libx264 honoured that, so every encoded frame was a keyframe: 11.7 MB for 20 s. The encoder now clears
pict_typebefore encoding, which brings the same recording to 423 KB. A unit test confirms it: 16 of 16 frames were keyframes without the fix.Review
Greptile's 12 findings are fixed, and each thread has a reply:
MAX_SEGMENTS;🤖 Generated with Claude Code


Confidence Score: 5/5
The PR appears safe to merge; no blocking findings remain.Summary
Adds display recording through
computer-recording/v1, with segment uploads, saved artifacts, and cleanup before reset or teardown.Diagram
%%{init: {'theme': 'neutral'}}%% flowchart LR Start["Start recording"] --> Driver["Driver stream or capture"] Driver --> Segments["Close and upload segments"] Segments --> End["End, reset, or teardown"] End --> Retry["Retry missed uploads"] Retry --> Save["Save segments and manifest as artifacts"]Reviews (4) · Last reviewed commit: "fix(computer): fail the probe on a segme..." · Reviewed by Greptile