Skip to content

fix(harness): bound the scan walk by what it holds, merge its two readers, test its untested branches (TH-9449, TH-9450, TH-9451) - #273

Merged
khushalsonawat merged 9 commits into
feat/TH-8804-source-subfolderfrom
feat/TH-9449-scan-followups
Nov 11, 2026
Merged

khushalsonawat merged 9 commits into
feat/TH-8804-source-subfolderfrom
feat/TH-9449-scan-followups

Conversation

@khushalsonawat

@khushalsonawat khushalsonawat commented Nov 10, 2026 •

Copy link
Copy Markdown
Member

Description

These are the agent scan's three follow-ups from round 2 of the review of #251.

  • TH-9449. The walk used to stop at a file count. Now it stops at what it holds: one 1.5 GB budget, charged as
    things are kept, rounded to the allocator's blocks. It covers everything the walk and its join keep. The file limit
    now counts only files the join works on (40,000), so a repository of many small files isn't cut early.
  • TH-9450. The walk and the agent description read through one capped reader, with the same stop order as before.
  • TH-9451. Tests for the five branches no test held, and the masked-trace comments fixed.

Type of Change

  • Bug fix
  • Refactor
  • Tests

Changes

  • scan_walk.py, the budget (_MAX_HELD = 1,536 MiB).
    • It charges facts, keys, import lists and the join's share for each file.
    • It also charges templates, graphs, prompt paths, project lookups with nearest-project links, module sets, and the
      folders os.walk holds to enter.
    • Past the budget, the walk stops at limit and keeps what it found. A cap reached while joining ends the same way.
  • scan_walk.py, the join.
    • _roots is gone. Imports are looked for through the nearest-project links.
    • The join's two loops and each row's pass over the files are linear. The row pass uses bisect ranges and checks
      the clock.
    • Rows keep at most 50 setting names, the most the answer file writes, so the file is unchanged.
      ENV_NAMES_KEPT now lives in scan_walk.
    • The hazard look gives up past 64 Mi characters of listed paths.
  • scan_walk.py, one _CappedReader for the walk and describe_entry.
  • scan_ai.py, scan_folders.py, scan_walk.py: comments say only the platform's secret values are masked.
  • Tests:
    • 16 memory shapes, where the charge is never below what is really held, under tracemalloc and with an independent
      model of the allocator;
    • a check per kept structure, and the stop tests;
    • the join's equivalence;
    • the reader's order;
    • the TH-9451 branches.

Testing

  • Run on this branch merged into the target's head (c1685ecb), on Python 3.12.14 and 3.12.3, one file at a time,
    all passed on both:

    test file passed
    test_scan_walk 147
    test_scan_folders 8
    test_scan_document 17
    test_scan_ai 59
    test_agent_scan 63
    test_authoring_entrypoint_find_agents 5
    test_reading_entry 61
    test_framework_detection 262
    test_harness_architecture 55
  • Planted breaks: 46, one per guard, each caught by a test and restored.

  • Walk only, no model, base (22bcbb87) against this branch at b1633742: the agent learning kit (20 rows) and a
    monorepo checkout (43 rows, stopped=limit).

    • The rows are identical on both.
    • Peak memory is about the same, and varies from run to run. No saving is claimed. These trees are far below the
      budget.
  • Review. A plan review took three rounds, and round 3 cleared. A change review of 22bcbb87..b1633742 by two
    cold reviewers cleared in round 1, with 0 High, 4 Medium and 18 Low between them. One reviewer's differential over
    1,400 generated repositories found no row that differs from the base.

What wasn't reviewed

  • Round 3's plan fixes. They are in the code the change review read.

  • 811a918c, which fixes the change review's Mediums and Lows without another round.

    • In src/, that's walk_repo mapping a cap raised while joining to limit, a row's pass skipping the name merge
      for files with no settings, and two comments.
    • The rest is tests.
  • f82106fc is titled test(harness), but it also removes one redundant 50-name slice in describe_entry. The
    slice changed nothing, because _env_names already returns at most 50.

  • bc3dd187, after CI: test_a_hazard_look_gives_up_past_its_listed_paths assumed an order in which folders are listed, and failed on Linux. os.walk doesn't sort, so the look found both agent.py files first. The long folders now sit at the root, so the root's own listing passes the bound on any file system. This is test-only.

    The other failure in that first CI run, test_post_terminal_wire_block_is_bounded_by_the_remaining_flush_window, is a timing test in the hosted entrypoint, which this PR doesn't touch. It passed on the second CI run and three times here.

Known, not changed here

The folder check's child process (framework_detection.py) has no memory bound. Its limit counts only Python files, so
a crafted tree of many folders with no Python files could grow it after the walk has stopped. That's pre-existing and
outside this change. It's filed as TH-9511.

Related Issues

Refs: TH-9449, TH-9450, TH-9451

A settings template the decoder rejects marks the description's row
short, against a readable control, with no reader fault logged and the
next file still read. The walk's summary line tells reader faults from
other unread files. A kit fault escaping the description keeps what was
read, with its trace. A MemoryError in one folder's check comes back as
a trace, so the child's loop goes on.

TH-9451

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
…ed reader

The walk's and the description's capped readers were copies. One reader
now counts reads and bytes, in the order every stop point relies on, and
each caller says what a cap means: the walk stops, the description marks
its row partial. No behaviour changes; the walk's tests pass unchanged.

TH-9450

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
What the walk keeps grows with the names its files hold, not with their
number: a crafted 51 KB file of unique dotted imports keeps 671 KB, so
5,100 of them, inside the byte cap and under the 40,000-file cap, kept
3.3 GiB in a 4 GB sandbox. Everything the walk and its join keep is now
charged where it is kept, at the allocator's blocks, against one
1,536 MiB budget: each file's facts, key and import list, its share of
the join, templates, graph entries, prompt paths, folder lookups,
project module sets and the folders os.walk holds to enter. Past it the
walk stops at "limit" with nothing of that file kept.

Four things stop growing instead of being counted: a resolved import
holds the walk's own path, the places an import is looked for follow a
link per folder to its nearest project, a row keeps at most the 50
setting names the answer file writes, and a hazard look gives up past
64 MiB of listed paths. The file cap now counts only files the join
works on, so many small files are read whole. The join's two quadratic
loops, and its pass over the files for each row, become linear.

TH-9449

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
TH-9449

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
Round the measured blocks apart from the code's own rounding, measure a
project lookup on its own, hold the first-names pass's limit and early
stop, a row that leaves out a deeper listed folder and a sibling with
the same start, and the clock check counted only after the walk. The
description's names are capped once, where the row is built.

TH-9449

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
…s up

TH-9449

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
TH-9449

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
Both reviewers cleared round 1. This fixes what they found:

- A cap reached while joining now ends as limit, like the edge budget.
  No lookup in the join charges today; this keeps a future one off the
  crash path.
- A row's pass skips the 50-name merge for a file with no settings.
- describe_entry's comment says only the platform's secret values are
  masked, and the hazard look's count says it is in characters.
- New tests hold what no test held: a charge outside one file's read
  (a listing, a prompt path) ends as limit; the file that crosses the
  budget is kept nowhere; a cap while joining ends as limit; an import
  inside an inner project resolves at an outer project's root; the
  description's byte cap, and that it reads nothing past its cap; each
  lookup charge and ancestor link; the hazard look counting full paths;
  import levels and line numbers over 256, as a shape and as pairs.

TH-9449

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
@khushalsonawat khushalsonawat self-assigned this Nov 10, 2026
khushalsonawat added a commit that referenced this pull request Nov 10, 2026
…h for Dileep

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
…isted in

os.walk doesn't sort, and Linux lists a and b before the long folders
under c, so the look found both agent.py files before its listed-path
bound was passed and CI failed. The long folders now sit beside a and b,
so the root's own listing passes the bound first on any file system.

TH-9449

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>

@cdileep23 cdileep23 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check the comments.

Comment thread src/fi/alk/harness/scan_walk.py Resolved
Comment thread src/fi/alk/harness/scan_walk.py Resolved
Comment thread src/fi/alk/harness/scan_walk.py Resolved
Comment thread src/fi/alk/harness/scan_walk.py Resolved
Comment thread src/fi/alk/harness/scan_walk.py Resolved
@khushalsonawat
khushalsonawat merged commit bc3dd18 into feat/TH-8804-source-subfolder Nov 11, 2026
11 checks passed
@khushalsonawat
khushalsonawat deleted the feat/TH-9449-scan-followups branch November 11, 2026 05:48
khushalsonawat added a commit that referenced this pull request Nov 11, 2026
[TH-9449] fix(harness): answer Dileep's P3 comments on #273, #275, #277 and #285
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.

2 participants