Repository navigation
feat(harness): run the contract save's file checks in a child with a deadline (TH-9445) - #310
Open
khushalsonawat wants to merge 12 commits into
Open
khushalsonawat wants to merge 12 commits into
khushalsonawat wants to merge 12 commits into
Conversation
The browser scan stopped reading an agent's code at any of its limits (file size, the scan, script and Python totals, the parse total, the token total and work, the decode and search totals, a skipped file, a package.json it couldn't read) and returned what it had, so padding read first or a large honest repository hid a plain browser import, silently. The scan now returns the limits that cut it, and a cut reading gets one second look with its own message before the model's answer stands; a cut never refuses on its own. One WARNING per cut scan names the limits and counts. The import walk's own stops are left to its own fix. Refs: TH-9512 Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
… (TH-9512) Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
…total Brings in #269, #276 (through #283), #283 and #288. Three hunks against #288 (TH-9448) were resolved by hand, keeping both intents: - a skipped file logs its type once at WARNING, and its path and trace at DEBUG (#288); it is also counted as the scan's `skipped` cut, and that count is in the cut line, so #288's separate "skipped N files" line is not repeated (TH-9512); - #288's skip tests and its NUL-byte test now expect the cut line, which holds names and counts only. Tests on 3.12.3: test_agent_type_refusal 720, test_browser_scan_cut 59, test_contract_tools_subfolder 180, all passed. Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
TH-9445 Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
… read package.json as Node does (TH-9512) - An oversize .py counts as file_size only when its bytes name a browser package, its coding line names a codec that isn't raw-safe or that Python refuses, or the search can't clear it: at most 4 MiB a file and 8 MiB a scan are searched, and a file past either bound, unreadable, not a file, or grown since its size was taken still cuts. It is never parsed. - package.json is read as Node reads it, UTF-8 with replacement characters and integers kept as text; invalid JSON means no dependencies, since npm refuses it too. Only OSError and RecursionError leave it unreadable, a cut. TH-9512 Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
…t/TH-9512-past-the-parse-total Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
…H-9512) - A file exactly at the per-file search bound, its needle in the last bytes, cuts. - An oversize module whose only tool is PlayWrightBrowserToolkit cuts. - Past what's left of the scan's search total, an oversize file isn't opened. TH-9512 Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
…the-parse-total agent_type_check.py, the skip handler: #291 drops the trace at every level; this branch counts a skip as the skipped cut. Kept both: no trace, the type once at WARNING, the count in the cut line. Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
…deadline (TH-9445) The save read the customer's files in a thread, and Python's parser holds the GIL, so a crafted repository could hold the hosted conversation for seconds. The checks now run in a forkserver child, one per builder session, with a 1.5 s deadline for each save. - A save cut at its deadline saves nothing. It answers with the problems that arrived, or "not checked in time". - The browser scan and instrumentation run once per session in the child, with their own 30 s bound. The answer is cached, and no save's deadline kills that job. - The child runs today's _checked_contract, so the in-thread fallback and the child share one code path. The fallback is used when forkserver is missing, when the start-up trial fails, after two deaths in a row, for a second event loop, and for a closed checker. - Every read limit stays. A watchdog kills a child past 400 MB. Logs come back on the answer pipe. - Tests that patch checks the child runs use save_checks="thread". Both new test files are in sdk-smoke.yml. TH-9445 Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
… checks run (TH-9445) On a loaded host the crafted files' session job can outlast the 1.5 s save deadline, so the first save answers "not checked in time" and the model would write again. Both child-mode loop-stall tests now do the same, every 0.5 s up to 60 tries, and still check that the loop is never held and that the last answer is a real save. Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
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.
Review only 21fcddc, the merge 6cf081b and b4f4122. Everything before them is the base:
feat/TH-8804-source-subfolderat c0e51b7 (#225, with #268, #276 and #283 merged in) and #281's head e174292, which is still open.TH-9445.
Summary
When the builder saves the agent's contract, the kit reads the customer's files: the browser-tool scan and the import walk, tool evidence, the prompt copy, the model citation, the sibling-build check, and instrumentation. That reading ran in a thread. Python's parser holds the GIL, so a crafted repository could hold the conversation for seconds, and every TH-8804 review round found a new shape just past one of the roughly 28 limits that keep it short.
This change runs those checks in a forkserver child, one per builder session, with one wall-clock deadline per save:
_checked_contract, unchanged in what it checks.Changes
save_checker.py(new):SaveChecker, the parent side. It handles the deadline, the cache, the reader and watchdog threads, kill and restart, the death count and the thread fallback.save_child.py(new) andforkserver_preload.py(new): the child, its trial, and the shared preload list.scan_folders.pyuses the same list.tools.py:submit_contractasks the checker.contract_toolsbuilds one checker per tool server, starts it only when there is a source to read, and closes it with a finalizer on the handler, which survives the hosted chat's copy of the server._checked_contractgains two keywords,instrumentation=andprogress=. Nothing else in it changes.test_save_child.pyandtest_save_checker.py(new, both added tosdk-smoke.yml), plus child-mode rows in the contract-tool tests. A conftest fixture closes every checker after each test and fails a test that leaves a child alive. The 15 tests that patch something the child runs, andtest_agent_reading.py's two, run in thread mode.Testing
CI: sdk-smoke runs
test_save_child.pyandtest_save_checker.py, which this PR adds to its list, along with every other test file it touches.The CI list, run on a Mac with CI's install (3.12.14): 4,884 passed, 3 skipped, 2 deselected, 1 xfailed.
test_certify_only.py's real seed test (3 cases). It needsALK_TEST_POSTGRES_DSN.test_private_github_submission_becomes_hosted_jobandtest_public_github_branch_url_populates_job_ref. They fail on dev too (github_commit_sha_required, TH-8978).test_hosted_github_route_accepts_a_submitted_value. Submit doesn't yet give hosted jobs thededicated_vmruntime.Changed-area files on 3.12.3, on a Mac: 2,714 passed, after merging fix(harness): give a browser scan its limits cut a second look (TH-9512) #281's head.
Planted bugs: 509 planted, 491 caught. The rest are equivalent mutants or the known limits below. The two new gaps the plants found now have rows that fail on them.
Hosted gate: 30 fresh E2B sandboxes from the hosted image built from this branch (4 vCPUs, 8 GB), 10 per tree. Every run checked in the child, with no deaths and no checker warnings.
ai_toolsThe save job here is the first save that reaches the child, timed cold, on a small honest contract. The gate image used HTTPS for its own apt step, because plain-HTTP downloads arrived corrupt on the network the image was built from. That is a build-only change and isn't in this PR.
Changes since the last clear review
The change review cleared in round 3. Everything after it had a fix-only check by one fresh reviewer, and both checks were CLEAR with no findings:
tools.py, keeps both sides. feat(harness): build an agent from a repo's subfolder #225's GC freeze in the three loop-stall tests survived it.test_a_crafted_script_does_not_hold_up_the_conversationfailed 2 of 3 runs. The session job outlasted the deadline, and the save rightly answered that it was still running.Known limits
ai_toolsand 10 of 10 on the whole futureagi repository. A save that comes before it ends answers "not checked in time", and the model writes the contract again.wait_forcan swallow a cancel that lands in the same tick as the answer. On 3.11 and later the code usesasyncio.timeout_at. CI runs 3.12 only, so it wouldn't catch a revert towait_foreither.psevery 50 ms. On Linux it reads/proc.SAVE_DEADLINEis read when the tool server is built, so changing it later has no effect on that server.Path.resolve()doesn't (the sandbox runs 3.12.3);Left alone