Files
LangBot/docs/BOX_OUTBOX_EXPORT.md
947d8addea fix(box): validate and export explicitly selected files (#2641)
* 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]>
2026-10-19 15:31:33 +08:00

70 lines
3.9 KiB
Markdown

# 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
```bash
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.