* fix(box): retain outbox files when export fails * test(box): retain ownership evidence in CI logs * test(box): exercise LocalAgent export repair through real runtime and Docker Co-authored-by: multica-agent <[email protected]> * fix(box): complete bounded outbox recovery contract * feat(box): export explicitly selected files Co-authored-by: multica-agent <[email protected]> * test(box): exercise explicit export on real Docker Co-authored-by: multica-agent <[email protected]> * test(box): verify explicit exports with live model and Docker Co-authored-by: multica-agent <[email protected]> * fix(box): align explicit export SDK and integration contracts * docs(box): describe selected-file export contract --------- Co-authored-by: HaiYangBG1 <[email protected]> Co-authored-by: Chin Rock <[email protected]> Co-authored-by: multica-agent <[email protected]>
3.9 KiB
Explicit Box file export
A Runner calls ctx.export_box_files(paths) with a non-empty list of normalized
absolute paths inside its bound sandbox. The run's outbox is the recommended place
for deliverables. Writing a file alone does not deliver it; LocalAgent exposes an
export_box_files model tool and performs no final directory scan.
Core validates every selected file before registering any new handles. Missing, unreadable, non-regular or symlinked paths, exceeded quotas and malformed runtime responses fail the whole batch and retain its files. The selected-file reader runs inside the existing Box with its original configuration and Workspace/generation fencing. It opens directories and files without following symlinks and uses nonblocking opens to avoid hanging on FIFOs.
The existing limits remain: 20 files and 50 MiB per call, 10 MiB per file, and 100 files / 50 MiB cumulatively per run. Raw SDK stdout is capped at 1 MiB per stream including base64/JSON overhead, so transport may reject a batch below its nominal file quotas. Only explicit SDK clipping notices are classified as truncation; ordinary invalid JSON is not guessed to be a size failure. Quota errors include the threshold, known actual value or lower bound, sandbox paths, retention state and suggested action.
Successful acceptance returns opaque run-scoped handles and removes only selected files whose content still matches the accepted bytes. Unselected files remain. Cleanup failure is a warning on accepted handles; it must not cause duplicate registration or delivery. Re-reading unchanged retained files reuses their IDs; changed content or distinct source paths produces distinct handles. Already submitted IDs are never returned for another delivery.
message.completed.file_ids attaches accepted files to automatic Pipeline output.
Explicit Agent replies use ctx.reply_files(). Platform-send failure after Core
acceptance terminates LocalAgent rather than regenerating or resending possibly
already-delivered files. Model export retries use ordinary paired tool-call
messages within the original tool/context/deadline budgets. There is no separate
two-attempt completion recovery loop. Streaming text already sent cannot be
retracted. Retention lasts only within the existing Box/run lifetime; expiry and
startup cleanup remain in effect.
Coordinated dependency
This API requires the matching Core, LocalAgent and SDK implementation. The current
Core and LocalAgent uv locks pin SDK commit
b300830b0e5339e7989be237efa811bedf761d55 from SDK PR #180. Published SDK 0.7.4 and
0.7.13 do not include the paths argument. LocalAgent rejects an older Runtime SDK
before preparing its file tool. Maintainers must coordinate the SDK release and
Runtime upgrade before release deployment.
The internal directory-collection helpers retain their strict bounded reads and host-permission fallback; they do not define the explicit Runner export API.
Regression checks
uv run --no-sync pytest tests/unit_tests/box -q
uv run --no-sync pytest tests/integration_tests/box/test_outbox_export.py -v -s
LANGBOT_LEGACY_PLUGIN_ROOT=/path/to/pinned/plugins uv run --no-sync pytest --required-suite local_agent -q
The Docker integration records uid/gid/mode, checks exact bytes from root-owned
0600 output using a non-root Core on Linux, verifies Box reuse and rejects
cross-run handle reuse. The required LocalAgent suite uses the integrity-checked
plugin source in tests/fixtures/legacy_plugin_identity_sources.json; Core,
Runtime, SQLite and Docker are real while the model and faults are scripted.
Cases cover selection, repair, streaming, tool-budget exhaustion, cleanup retry,
invalid response, timeout, real-ledger cancellation and no implicit export.
The opt-in test_live_explicit_export.py additionally needs dedicated
BOX_LIVE_API_KEY and BOX_LIVE_MODEL configuration. Its absence is a skip,
not evidence of a live-model acceptance run.