[ENV-A3] Specify and implement isolated-workspace candidate input/output transport
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 24/100
Research direction
Read parent issue #266 and accepted ADRs 0002, 0004, 0005, 0006, and 0008, then trace the listed surfaces: src/autoforge/agent_worktree.py, git_transport.py, materialize.py, safefs.py, locking.py, and evidence.py. Start with the existing candidate verification and Git transport flows; use offline fake provider fixtures to check the listed rejection cases and crash recovery. Done means the manifest, bounded transfer and import checks meet the acceptance criteria, existing candidate guarantees remain authoritative, and the focused test plan or tests are attached.
Written by the indexing model from the issue text.
Description
Artifact: implementation-ready engineering issue. Parent EPIC: #266. Workstream: A. Size: XL (raised from L on 2026-10-23: it now also owns the engine candidate-source abstraction). Baseline: main 711ff09 (2026-10-21). No implementation is authorized by this planning action.
Objective / scoped deliverable
Design controller-owned minimal Git input (exact base commit and necessary tree/object closure), transfer without leaking host refs, git common-dir, state, credentials or hidden untracked files; isolated agent commits in private repository, returns bounded bundle/pack plus manifest. Strictly verify object integrity, exact parent/ancestry/allowed repository, completeness, commit identity and path containment before controller-side import. Reuse existing candidate verification, git_transport, materialize, agent_worktree and shared-Git lock L1. Ensure no uncommitted patch becomes a REMOTE candidate. Crash-safe staging and import receipts.
Existing foundations and boundaries
Read EPIC #266 and relevant accepted ADRs 0002, 0004, 0005, 0006, 0008. The deterministic controller retains every workflow transition, Git/GitHub write, read-back, ReviewCertificate and merge authorization. Do not embed Sandcastle's run(), replace the Python agent providers or introduce an arbitrary privileged mount. Preserve the Linux bwrap baseline. Coordinate session/interaction overlaps with #147/#126. If state schema must change, obtain protocol-number coordination on #23 before the migration.
Dependencies
- Hard: #267 (ENV-A1)
- This issue must not be considered complete by filing a follow-up for a core acceptance criterion; split decisions before implementation only if the EPIC ledger is updated.
Relevant implementation surfaces (starting points, not an exhaustive mandate)
src/autoforge/agent_worktree.py, git_transport.py, materialize.py, safefs.py, locking.py, evidence.py, tests
Acceptance criteria
- Specified manifest includes issue/run identity, base SHA, candidate SHA, hash/object count/size bounds and import state
- Tests reject malicious refs, missing/extra parents, corrupt/oversized bundles, traversal/symlinks, wrong issue and arbitrary host paths
- No host common-dir/object-store mounted into isolated runtime; producer cannot write GitHub or host refs
- Re-import is idempotent after crashes and locked where shared Git data is mutated
- Existing REMOTE exact candidate → ValidationRecord → ReviewCertificate guarantees remain authoritative
- Existing regression suite passes; focused negative and crash-window tests are attached to the PR, or a design-only issue includes an auditable test plan.
- Update documentation and backend capability/rollout status when user-facing behavior changes; do not represent a skipped live test as a pass.
Exclusions / anti-goals
Do not weaken sandbox.mode: required, allow agent-held GitHub write credentials, expose host .git common-dir/state to isolated agents, silently apply raw REMOTE diffs, or invent a second lifecycle controller. EPIC #255 Public Alpha is not blocked by this task.
Verification guidance
Use offline fake executable/provider fixtures first, then explicitly gated disposable-repository real-backend tests when applicable. Record exact candidate/base SHAs and externally observed failures, ensure secure redaction, and demonstrate idempotent cleanup and resume.
Snapshot / private workspace integration clarification (2026-10-23)
- Coordinate #292: accept a Host Controller-prepared exact tree SHA snapshot at
/sourceas read-only data, not the user's unchecked live checkout, while a separately seeded self-contained private Git repository at/workspaceis the only agent-writable code root. - Preserve file modes, symlinks and explicit decisions for submodules, LFS, Git filters and sparse/partial clones without executing repository hooks/filters on the host. Reject nontracked/ignored host secrets in input.
- Container never obtains host GitHub credentials; host uses hardened Git for private object import, ancestry validation and authorized push, and host
ghfor GitHub API effects. Agent-generatedCONTROL_RESULTis data, not command authorization. - Verify host candidate is imported from bounded artifact+manifest, never by trusting a direct write into a host-linked worktree or shared Git store.
Planning reconciliation (2026-10-23)
Artifact: planning update from the 2026-10-23 reconciliation of EPIC #266 with its architecture review (#266 comment). Baseline re-read: main 388ecb3. Planning only: nothing here implements a feature or checks off a criterion. The earlier sections stay authoritative where this one does not change them.
Generalize the existing primitives instead of adding a second import path
The strict import exists already: import_private_objects (src/autoforge/agent_worktree.py:491-640) runs rev-list → pack-objects → index-pack --stdin --fix-thin --strict under SharedGitLock (lock L1, ADR 0008). The engine reads the candidate through _read_candidate (src/autoforge/engine.py:2565). This issue generalizes both rather than writing a parallel path for containers.
Retained exactly: strict object checks; L1 around every shared-store mutation; binding of base and candidate; idempotent import; exact-SHA candidate identity; ADR 0004 D7.5 re-hashing; no host refs and no GitHub credential in the producer; ADR 0006 D6.1 (the candidate's objects are in the shared store before materialize() runs).
Added for a container-produced input:
- The input is untrusted. Today's pack is the controller's own
pack-objectsoutput. A container-produced pack and manifest are attacker-controlled bytes. - Manifest binding. The manifest binds issue, run key, phase, launch id, base SHA and candidate SHA. A manifest from another run, launch or issue is refused.
- Bounds checked before reading. Byte size and object count are checked against configured limits before any read. Today's code reads the whole pack into memory (
agent_worktree.py:618-619). A container-produced pack must instead be streamed intoindex-packor read in bounded chunks. A malicious pack must never be loaded into unbounded memory before its size is checked. - Origin and run authentication. The artifact is accepted only from the controller-owned export location of the launch that produced it, as recorded by #272's journal. A file planted in that location by any other launch is refused.
- Safe re-import. Re-import after a crash at any boundary gives the same result or a typed refusal, never a partial publication.
Engine candidate-source abstraction (moved here from no owner)
Today the engine assumes the candidate is the issue worktree's HEAD. The places that assume it:
WORKTREE_STATE_PHASES(engine.py:634);_import_agent_objectsand its callers (engine.py:2170,:2220,:2236);_read_candidateand its callers (engine.py:2565,:6811,:6982,:15173);_read_worktree_state(engine.py:3485);- the REMOTE worktree-state check (
engine.py:12494).
This issue defines one candidate-source abstraction with two implementations:
- Worktree
HEADfor bwrap anddisabled, unchanged in behaviour (ADR 0005 D6.3, D6.5). - Imported artifact for Docker, after the strict import above. This is ADR 0005 D6.3 as amended by #267.
FIX launches are seeded from the last verified candidate (#267 decision 4). An earlier container's unverified workspace never seeds them.
Prompt and persistence assumptions to review. The worktree assumption is not only in the candidate read:
- For every write phase, the engine reads the host worktree's
git statusand rendersprompts/worktree_state.md. That is #187's uncommitted-changes note:engine.py:12494and_read_worktree_stateat:3485. - The imported head is remembered across a launch (
_imported_head,engine.py:2236).
Under Docker the uncommitted state lives in the container's private /workspace. The design must state for each one:
- where it is read;
- what the prompt says;
- what, if anything, is persisted;
- which exact commit is authoritative in each phase (ANALYZE_EXECUTE, REVIEW, FIX, REPLAN_REEXECUTE, READY_FOR_MERGE).
Responsibility split
| Concern | Owner |
|---|---|
| Import semantics, manifest, bounds, authentication, candidate-source abstraction, FIX seed choice | #269 (this issue), with fakes and a fake export |
| Wiring the abstraction to the Docker backend's export and to #292's seed staging | #271 |
| End-to-end proof on real Docker | #274 |
Persisted-state needs (import receipts, the authoritative candidate source per run) are in the #23 coordination request (comment). No protocol number is claimed.
Additional acceptance criteria (planning reconciliation)
- A container-produced pack is size- and count-checked before any read and is never read whole into memory; a test with an oversized pack proves refusal without allocating its size
- Manifest binding and origin/run authentication are tested: a manifest from another run, launch or issue, or a planted artifact, is refused
- The engine reads candidates only through the candidate-source abstraction; the bwrap and
disabledbehaviour and their existing tests are unchanged - Each phase's authoritative commit, and the source of #187's uncommitted-workspace note under each backend, are specified and tested; no prompt or persisted field assumes a host worktree under the Docker backend
- FIX seeding uses only the last verified, imported candidate; a test proves an unverified workspace is never reused
- Crash-window tests at each import boundary (before L1, during
index-pack, after publication, before the receipt) show idempotent re-import or a typed refusal
- Dominant language
- Python
- Stars
- 0
- Forks
- 0
- Avg merge
- 5h 57m
- Merged PRs (30d)
- 71
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from iceboundrock/AutoForge
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
iceboundrock/AutoForge#304 ·
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 25/100
iceboundrock/AutoForge#333 ·
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 35/100
iceboundrock/AutoForge#332 ·
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 1/100
iceboundrock/AutoForge#331 ·
Maintainers usually reply within 1 day
-
enhancement
Difficulty 4/5 3-5 days Newbie friendliness 45/100
iceboundrock/AutoForge#329 · 1 comment ·
Maintainers usually reply within 1 day
All issues in iceboundrock/AutoForge
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
nansencenter/geodataset#39 ·
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
awslabs/visual-asset-management-system#804 ·
Maintainers usually reply within 1 day
-
follow-up
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
odoo/odoo#297927 · 1 comment ·
Maintainers usually reply within 1 day
-
link-rot
Difficulty 1/5 Under an hour Newbie friendliness 67/100