Skip to content

feat(harness): list the agents in a repo without building it - #251

Merged
khushalsonawat merged 16 commits into
feat/TH-8951-read-without-buildfrom
feat/TH-8950-find-agents
Nov 10, 2026
Merged

khushalsonawat merged 16 commits into
feat/TH-8951-read-without-buildfrom
feat/TH-8950-find-agents

Conversation

@khushalsonawat

@khushalsonawat khushalsonawat commented Nov 6, 2026 •

Copy link
Copy Markdown
Member

Stacked on #245 (TH-8951), which is stacked on #225 (TH-8804) and #224 (TH-8806). The base is feat/TH-8951-read-without-build, so the diff shows only TH-8950. It retargets down the stack as those merge.

Summary

The platform's Agents tab needs a list of the agents in a repo before anything is built, so a person can pick one to create. This PR adds --find-agents to the authoring command. It reads the repo at one commit (or an upload) without installing or running anything, and writes /work/reading/agents.json in the shape AgentScanSerializer reads, plus cost.json. It uses #245's shared reading helper. Exit 0 means a file was written (found, none or error); 1 and 78 mean failure.json.

  • The walk (scan_walk.py) reads each file once, the repo's own code before test and example folders, and finds candidates: framework builds, langgraph.json graphs, agent.py, provider clients. It joins files into agents by their imports, so a side-folder script or a weaker file never swallows a stronger one. An agent's folder counts only agents of its own project.
  • The framework word (scan_folders.py) is TH-8805's answer for each agent's folder, with a build's limits. It runs in one child process that the scan's clock can stop.
  • The model step (scan_ai.py) is the only paid part ($0.31–0.50 measured). It runs when the walk found no agent, found agents only in test or example folders, or found only a weak sign (a bare client or chat model) beside TypeScript or JavaScript, which the walk can't read; and only with 360 s left on the clock and the walk not stopped. Its read/glob/grep tools stay inside the source. Only the paths it names are kept, once checked, and nothing it writes reaches the file. If it fails, the walk's rows are still written with truncated: true; only a scan with no rows writes scan_model_error.
  • Faults: a fault in one file leaves that file unread; any other fault in the kit ends as scan_crashed (retryable). Faults that are still absorbed log their trace through the reading helper's secret mask.
  • The file (scan_document.py): names, evidence sentences and paths are built to fit the serializer's rules. The file is checked against a copy of them (fixture pinned to e07d56fcd), and at most 50 agents are kept.

Settings:

  • ALK_SCAN_MAX_SECONDS: the clock, 1,200 s;
  • ALK_SCAN_AI_MAX_SECONDS: the model step's limit, 600 s;
  • the hard stop: the clock + 600 s.

The sandbox needs 4,096 MB, 2 CPU and a lifetime of at least 2,400 s.

Why one PR: find, none and error is one agreed contract with the platform, and TH-8824 needs both halves. The walk alone would answer none for a TypeScript-only repo. The model tools' {a,b} and **/ glob support came from a measured failure on vercel/ai-chatbot (false "No file matches"), not from the spec.

Ticket: TH-8950.

Known limits

  • The step that joins files into agents gets the walk's deadline plus 120 s; past it the scan ends as scan_timed_out with no rows, even if the walk had found some.
  • The walk reads at most 40,000 source files and 256 MiB; past that it stops with scan_limit_reached, or truncated if it found agents. What it holds grows with the names each file imports (about 33 KB a file at 200 imported names, a 1,389 MB peak at 40,000 such files), so the cap keeps that inside the 4,096 MB sandbox. A budget on the names held, instead of a file count, is a follow-up.
  • An agent whose path breaks the platform's path rule is left out with truncated: true, or gets error agent_path_unsupported when nothing else is found. Next.js route folders such as app/(chat)/ hit this; listing them needs the rule to allow ( and ).
  • The framework word is one per folder, so a folder holding several frameworks reads as unknown.
  • A Python repo that also holds a TypeScript agent lists only the Python one when the walk finds a strong candidate.
  • Two agents in one folder are both listed at low confidence; a build can't tell them apart.
  • Measured on a laptop, not on the sandbox:
    • agent-learning-kit: 8 s, 769 MB, 21 rows, $0;
    • future-agi monorepo: 13 s, 637 MB, 11 rows, $0;
    • vercel/ai-chatbot with the model step: 14 s, 534 MB, $0.31.
  • Not reviewed yet:
    • the last review round's fixes, and the fixes for this PR's first review;
    • the handling of model-named paths that break the path rule;
    • glob {a,b} and **/ matching in the model's tools.
  • Pre-existing: authoring_entrypoint.py imports sys and shutil twice (ruff F811), already on the base.

Changes since the 10 Nov approval (4eb45c60)

  • 45eb82ba: the cost-note catch logs its trace (your P3).
  • 884043ca: one unreadable file no longer stops an AI row's description; detect_framework gains raise_faults (off for builds) so the scan sees a fault in the framework check and marks the row unfinished; a tool fault keeps its cause.
  • 6aeb4665: settings templates are read with the repo's own code; one trace per unforeseen reader fault; ALK_SCAN_AI_MAX_SECONDS read when the model step is asked; tests isolated from the environment. It also raised the file cap to 200,000, which the next round found unsafe for memory.
  • 81d16b54: a file over 1 MiB marks an AI row short instead of being skipped silently.
  • b46311ad: the file cap is 40,000 (from 20,000 at your review), sized by memory; the partial-row sentence says files couldn't be read.
  • b8970348: the step after the walk (joining files into agents) checks the walk's deadline plus 120 s and ends as a clock stop, so a huge import graph ends in scan_timed_out instead of the hard stop (your P2); the two-pass test pins the template order (your P3).
  • 4dcea5c3: merge of feat(harness): read an agent without building it #245 at edb69263 (no change to this PR's own diff).
  • af8e86e5: the join counts candidate pairs and, past 5,000,000 (about 90 MB), ends with the walk's limit result (your P3 follow-up).

Verification

  • uv run ruff check on the changed files: clean apart from the pre-existing F811 above
  • Focused pytest targets, one file at a time, on Python 3.12.14 and 3.12.3:
    • tests/harness/test_scan_walk.py: 92 passed
    • tests/harness/test_scan_folders.py: 7 passed
    • tests/harness/test_scan_document.py: 17 passed
    • tests/harness/test_scan_ai.py: 59 passed
    • tests/harness/test_agent_scan.py: 63 passed
    • tests/harness/test_authoring_entrypoint_find_agents.py: 5 passed
    • neighbours, on feat(harness): read an agent without building it #245 at ee163b0: test_reading_entry.py 61, test_agent_reading.py 88, test_framework_detection.py 251 passed (framework_detection._Work takes an optional limit, so the walk reuses it; detect_framework takes raise_faults, off for builds, so the scan sees a fault in the check)
  • Full uv run pytest -q: not run
  • TypeScript build/test: not touched
  • agent-learn release-proof: not touched

Checklist

  • Public API names stay under agent_learning.*, agent-learn, or @future-agi/agent-learning-kit
  • New user-facing workflow has docs or an example: the platform's internal command, no user docs
  • New release claim has executable gate coverage: no release claim
  • No hosted-service dependency was added to release-gated examples
  • Security-sensitive output is redacted or justified: evidence is fixed sentences only, settings are names only, and secret-like files are hidden from the model's tools

Adds --find-agents to the authoring entrypoint, run through the shared
reading helper. It writes /work/reading/agents.json in the shape the
platform's AgentScanSerializer reads, plus cost.json on every return.

- A plain code walk finds candidates (framework builds, langgraph.json
  graphs, agent.py, provider clients) and joins files into agents by
  their imports, without running anything.
- Each agent's folder gets TH-8805's framework answer, with a build's
  limits, in one child process the scan's clock can stop.
- A model is asked only when the walk finds nothing usable (mostly
  TypeScript and JavaScript repos). Its read/glob/grep tools stay inside
  the source, and only the paths it names, once checked, are kept.
- Names, evidence and paths are built to fit the platform's rules, and
  the file is checked against a copy of them before it is written.
- An agent at a path the platform's rule can't carry (a Next.js
  app/(chat)/ route, say) is left out with truncated set, and is
  reported as agent_path_unsupported when nothing else is found.

TH-8950

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
@khushalsonawat khushalsonawat self-assigned this Nov 6, 2026
TH-8950

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
#245 replaced reading_entry._secret_values with a functools.cache
reader keyed by the secrets files. The scan tests already point those
files at a fresh temporary path, so each test gets its own cache entry
and there is nothing left to reset.

TH-8950

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/agent_scan.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/agent_scan.py Resolved
Comment thread src/fi/alk/harness/scan_walk.py Resolved
Comment thread src/fi/alk/harness/agent_scan.py Resolved
Comment thread src/fi/alk/harness/scan_walk.py Resolved
Comment thread src/fi/alk/harness/scan_document.py Resolved
Comment thread tests/harness/test_scan_document.py Resolved
Comment thread src/fi/alk/harness/agent_scan.py Resolved
…ts crash

- Any model error now keeps the walk's rows with truncated set and
  exits 0; only a scan with no rows writes scan_model_error, carrying
  the model's own retryable flag. A retry would pay for the model again.
- A fault outside any one file in the walk (run or result) now reaches
  the scan's crash path (scan_crashed, retryable) instead of being
  reported as scan_limit_reached.
- Catches are narrowed to what each step expects. A file's reader
  faults still leave that file unread. A folder check that fails in the
  child sends its trace back, and the row is marked unfinished. Faults
  that are still absorbed (after the paid step, closing the session,
  tool calls) log their trace through the reading helper's MaskSecrets
  filter instead of the class name only.

TH-8950

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
- agent_scan imports EXIT_DONE, EXIT_RETRYABLE, limit_seconds and
  reading_failure from reading_entry, and EXIT_REFUSED from
  agent_type_check. FIND_AGENTS now sets limit_env and
  limit_margin_seconds, so the helper and the scan read
  ALK_SCAN_MAX_SECONDS at the same moment.
- The find-agents stage passes idle_retries=0 itself instead of setting
  session.STAGE_IDLE_RETRIES for the whole process.
- The walk uses framework_detection's _Work, which takes an optional
  limit, instead of its own copy, and bundle_author_v2's ignore list
  instead of a copy held equal by a test.
- The walk and an AI row's description share one folder loop
  (_source_files) and one shadowed-name lookup (_ShadowedNames).

TH-8950

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
- An agent's subfolder counts only the agents of its own project.
  Adding an unrelated project with its own agent deeper in the repo no
  longer moves the first agent's folder, which the platform keys on.
- The walk reads files outside test, example and other side folders
  first, then the side folders. A large tests folder sorted ahead of
  the agent can no longer use up the 20,000-file cap before the agent
  is read.

TH-8950

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

- The shared-folder evidence sentence no longer asks a build to name an
  entry, which a build job has no field for.
- Test names say "the platform format", "the platform rules" and "the
  path rule" instead of "his".
- _result's docstring describes what it does instead of pointing at
  plan step numbers.

TH-8950

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
The model step also runs when the walk found agents only in test or
example folders, so the test is no longer named "only when nothing is
found".

TH-8950

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_ai.py Resolved
The catch still keeps a paid answer from becoming a retry; it now logs
the masked trace like the other absorbed faults, so a fault in the cost
writer can be found.

TH-8950

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

From the review round on the fixes for the first review of this PR:
- One unreadable or unparsable file no longer stops the description of
  a model-found agent. It marks the row short (partial) and the reading
  goes on, as the walk goes on past an unread file.
- detect_framework takes raise_faults (off for builds). The scan's
  folder check turns it on, so a fault inside the framework check comes
  back as a trace and the row is unfinished, instead of a finished
  unknown. Both the child's and the parent's halves are tested.
- A model tool's fault keeps the worker's exception as the cause, so its
  logged trace shows the real error.
- Tests pin the run-time read of ALK_SCAN_MAX_SECONDS and the secret
  mask on every scan logger. The log comment says only the platform's
  secret values are masked.

TH-8950

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
…es with own code

From the review round on the fixes for the first review of this PR:
- The file cap is 200,000, up from 20,000. Measured: 100,000 tiny
  source files walk in 13 s at 266 MB, so the byte cap and the clock
  are what bound the walk. A repo whose own code passes 20,000 files no
  longer stops before its agent is read.
- A folder's settings template (.env.example and its kin) is read in its
  folder's pass, not with the side files, so a big tests folder can't
  keep it from being read.
- A reader fault of an unforeseen kind logs its trace once; the rest
  are counted (reader_faults in the walk's summary line).
- ALK_SCAN_AI_MAX_SECONDS is read when the scan starts, as the clock is.
- Tests: every file is read in exactly one pass; tests no longer depend
  on an exported ALK_SCAN_MAX_SECONDS; the process-wide idle-retry
  setting is set through monkeypatch.

TH-8950

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
From the last review round on the fixes for the first review of this PR:
- A file over 1 MiB in a model-found agent's folder now marks the row
  short (partial) instead of being skipped silently, so a paid row with
  a missing file is never reported complete.
- The description logs one trace for a reader fault of an unforeseen
  kind, then counts the rest, as the walk does.
- Comments say traces are logged with the platform's secret values
  masked, not that they are fully masked.

TH-8950

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

From the last review round on the fixes for the first review of this PR:
- The file cap is 40,000, not 200,000. What the walk holds grows with
  the names each file imports, not its bytes: about 33 KB a file for
  files that each import 200 names, a 1,389 MB peak at 40,000 such
  files. At 200,000 such files the sandbox would kill the scan. 40,000
  still covers a repo whose own code passes 20,000 files.
- The partial-row sentence says files couldn't be read, not that they
  weren't read in time, since a file that fails to parse also sets it.
- A test pins that ALK_SCAN_AI_MAX_SECONDS is read when the model step
  is asked, and the comment says so.

TH-8950

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 tests/harness/test_scan_walk.py Resolved
@khushalsonawat

Copy link
Copy Markdown
Member Author

Since your approval at 4eb45c6 there are five commits, listed in the body under "Changes since the 10 Nov approval". The two that change behaviour beyond your comments: detect_framework gains raise_faults (off for builds), and the walk's file cap is 40,000, sized by memory. Could you take another look?

The step that joins the walk's files into agents (the import search
from each candidate, and the per-agent loops) now checks a deadline:
the walk's own deadline plus 120 s, well inside the helper's 600 s
margin before the hard stop. Past it the walk ends as stopped by the
clock, so the scan writes its own file (scan_timed_out, exit 0) instead
of being ended by the hard stop.

The two-pass test now pins the order it claims: a settings template in
a folder that sorts after tests/ is read before tests/test_a.py.

TH-8950

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

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
Candidates that reach each other hold one entry per pair, which grows
with the square of the candidates. The join now counts them as it
builds them and, past 5,000,000 (about 90 MB), ends as the walk's other
caps do (stopped="limit", scan_limit_reached), instead of growing
until the clock stops it.

TH-8950

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
@khushalsonawat
khushalsonawat merged commit 4c97485 into feat/TH-8951-read-without-build Nov 10, 2026
11 checks passed
@khushalsonawat
khushalsonawat deleted the feat/TH-8950-find-agents branch November 10, 2026 10:11
@khushalsonawat
khushalsonawat restored the feat/TH-8950-find-agents branch November 10, 2026 10:11
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