mirror of
https://github.com/kvcache-ai/AgentENV.git
synced 2026-11-05 01:17:36 +00:00
[PR #383] [MERGED] refactor: break the module dependency cycles and enforce layering #392
Labels
No labels
bug
documentation
enhancement
enhancement
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
kvcache-ai/AgentENV#392
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/kvcache-ai/AgentENV/pull/383
Author: @yingdi-shan
Created: 10/14/2026
Status: ✅ Merged
Merged: 10/14/2026
Merged by: @yingdi-shan
Base:
main← Head:refactor/module-layering📝 Commits (4)
4121cecstyle(image): satisfy rustfmt and clippy in is_valid_digest_algorithm902ff5arefactor: break the dependency cycles between top-level modules86fd03ftest: fail on dependency cycles between top-level modulesdc3b617fix(repository): back off when an OSS document changes while being read📊 Changes
70 files changed (+1858 additions, -1588 deletions)
View changed files
📝
.github/actions/sandbox-ci-setup/action.yml(+2 -2)📝
CLAUDE.md(+3 -0)📝
crates/e2e-tests/tests/snapshot_oss_e2e_test.rs(+6 -6)📝
deploy/docker/Dockerfile.agentenv(+1 -1)📝
services/shared/observability/buckets.go(+1 -1)📝
src/api/impls/sandbox.rs(+3 -2)📝
src/api/proxy.rs(+1 -1)📝
src/api/server.rs(+1 -1)📝
src/cfg.rs(+14 -6)📝
src/deps/mod.rs(+18 -13)📝
src/deps/overlaybd_tools.rs(+3 -3)📝
src/image/buildkit/history.rs(+21 -26)📝
src/image/cache/mock.rs(+1 -1)📝
src/image/cache/store.rs(+1 -1)📝
src/image/oci_image.rs(+1 -1)📝
src/image/resolver.rs(+2 -2)📝
src/lib.rs(+4 -0)📝
src/logging.rs(+181 -4)➕
src/module_cycles.rs(+210 -0)📝
src/observability/mod.rs(+0 -1)...and 50 more files
📄 Description
What
Remove every dependency cycle between the top-level modules of the
agentenvcrate, then add a test that keeps them layered. #382 already tookrepositoryoffsnapshot,image,volumeandsandbox. This PR does the rest in four commits:style(image): the rustfmt/clippy fixmainneeds (item 8 below).refactor: breaks the cycles (items 1–6).test: the module-cycle test (item 7).fix(repository): the OSS document-read race that failed CI (item 9).The cycles it breaks:
P2pTransportKindandMAX_VOLUME_MOUNTSmove intocfg, which no longer importsp2porvolume.build_id-span events into build logs moves fromtemplatetologging, and so doesBuildLogger, which now writes to aBuildLogSink. Builds register that sink instead.template::logsre-exportsBuildLogger; the buffer, size limits and repository store stay intemplate.MetricGuard,SandboxStageTimer, HTTP middleware) move unchanged tocrate::prometheus.repository,imageand the ublk manager no longer reach the orchestrator throughobservability.RuntimeArtifactSetmoves totypes. The BuildKit history client stays inimage::buildkitand takes itsBuildLoggerfromlogging, soimageno longer depends ontemplateand code inimagecan still drive BuildKit builds. Its reconnect test records log lines with an in-memory sink instead of a repository-backedBuildLogssession.setup/deps.rs,setup/overlaybd.rs) moves unchanged to a newcrate::deps. Tools-image resolution becomessandbox::resolve_tools_image, which passesImageResolvertodeps::prepare_tools_image.snapshotbecomes the lower layer.ExtraDrive,CustomExtensionParamsandVolumeModemove totypes, andSandboxSnapshotManifestmoves intosnapshot. Layer compaction moves tocrate::overlaybd.publish_capturedand the runtime-version probe move tosandbox.src/module_cycles.rsbuilds the module graph fromlib.rsand everycrate::path, groupeduse crate::{..}import and doc link, then fails on any cycle. It names each edge of the cycle and the file that creates it. No module list is kept, so adding, renaming or removing a module needs no test change. It replaces the repository-only test. It readsmoddeclarations with any visibility (pub,pub(crate),pub(in ..)).is_valid_digest_algorithmthat came in withd243f6eonmain.concurrent_document_updates_on_oss_lose_nonefailed in CI withobject changed too often while reading its version.get_bytes_with_etagretried its stat plusIf-Matchread five times in a row, with no wait, so 16 writers on one record could make every attempt lose. It now reads once and reports the race.update_if_unchangedthen retries with its existing jittered backoff, up to 32 attempts.Resulting module graph, bottom to top (for review; the test does not hard-code it):
Why
Before this PR the crate's modules formed one strongly connected component of 14 modules. A few edges caused most of it:
cfg → p2p/volume,logging → template, andrepository/image/sandbox → observability → orchestrator. Thesandbox ↔ snapshotandsetup ↔ image/sandboxcycles were real design ones. As a result, no module could be read, tested or changed in isolation, and nothing kept new back-edges out.Related issue
None. This follows up on the code-quality review that produced #380 and #382.
Scope and non-goals
BuildLogSink::loggains astepargument (the tracing layer passesNone), and the buffer-append code fromBuildLogger::logmoves unchanged into theMutex<Buffer>sink impl. The rest is moved code or call sites updated to new paths.route_build_logsdrops dead sinks when it registers one, so an aborted build can't leave its entry behind. The compaction tests now look for the UUID-suffixed temp file that compaction actually writes; before, they checked a path it never creates.sandboxre-exportsExtraDrive,RuntimeArtifactSet,CustomExtensionParams,SandboxSnapshotManifestandFIRECRACKER_BACKEND, andvolumere-exportsVolumeMode.Design and behavior changes
The intended behavior change is none. Points worth a look:
sandbox:publish_captured, which downcasts to Firecracker artifacts and starts the startup-pack recording VM, andprobe_runtime_versions, which runs commands in the guest.sandbox::publish_captured(&manager, metadata, captured)keeps the old argument order and still holds the capture until publication finishes.stop_build_log_routingcompares weak pointers. As before, it leaves a newer sink registered under the same build id in place.deps::prepare_tools_imagekeeps the locking, legacy-drive and install logic of the oldresolve_tools_image. Only the image lookup is now passed in by the caller.publish_capturedspan is kept and now wraps a nestedpublishspan.Compatibility and operations
setup::overlaybd,observability::prometheus,sandbox::manifest,SnapshotManager::publish_capturedandSnapshotRuntimeVersions::probe. All in-repo callers are updated..github/actions/sandbox-ci-setup/action.yml) now hashessrc/deps/*, so the first run after merge rebuilds that cache once.Validation
make fmtmake clippymake test-unitmake -C services test(one Go comment changed)maketarget (no schema changes)Commands and results:
After squashing and the review fixes:
After keeping the BuildKit history client in
image:Skipped checks and reasons:
make test-agent-integrationwas not rerun after the squash or the BuildKit history change. Those changes touch build logging, the OSS document read and the compaction tests, not VM or storage paths.Risks and reviewer notes
git show --color-movedmakes that easy to confirm. The moved code itself is unchanged; the edits are insandbox/backend.rs(publish_captured),sandbox/runtime_versions.rsand thetypes/extra_drive.rssplit.template::logstests cover routing, truncation and session cleanup.module_cycles.rsscans source text, comments included. Doc links create real rustdoc dependencies, so counting them is intended, and a false positive fails loudly instead of hiding a cycle. It does not followsuper::super::paths across top-level modules, and none exist today.Checklist
oci_image.rsstyle commit is the exception;make fmtandmake clippyfail onmainwithout it.)🤖 Generated with Claude Code
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.