Skip to content

Inherit user-attached files in sub-agent sessions#2532

Merged
dgageot merged 4 commits intodocker:mainfrom
dgageot:board/file-path-resolution-issue-with-sub-agen-1bc0a174
Apr 27, 2026
Merged

Inherit user-attached files in sub-agent sessions#2532
dgageot merged 4 commits intodocker:mainfrom
dgageot:board/file-path-resolution-issue-with-sub-agen-1bc0a174

Conversation

@dgageot
Copy link
Copy Markdown
Member

@dgageot dgageot commented Apr 27, 2026

Problem

When a user attaches a file via @-mention in their initial prompt, only the root agent sees the file content (it's inlined into the user message). When that agent then delegates work via transfer_task, the sub-agent starts in a fresh session with no record of which files the user originally attached. If the parent's task description says "edit something.go" instead of "edit /abs/path/something.go", the sub-agent must scan the workspace, often picks the wrong file when several share the same name, and burns tokens doing it.

This was reported as a frequent pain point when working with multi-agent teams. See: user feedback in the issue.

Approach

Two complementary changes — one prompt-only, one structural:

A. Prompt guidance (LLM-side reminder)

  • The parent agent's "you are a multi-agent system" prompt now explicitly says: when the task involves files, always include absolute paths in task; sub-agents do not see the conversation history or attached files.
  • The sub-agent's task system message now says: treat absolute paths in <task> as authoritative; if a referenced file is given by name only, do not guess — search the workspace or ask the calling agent.

This catches the case where the parent agent itself discovers a new file mid-flight (i.e. files that were never attached by the user).

C. Inherit attached files in sub-sessions (deterministic)

  • New Session.AttachedFiles []string field with AddAttachedFile, AttachedFilesSnapshot, and a WithAttachedFiles option (deduplicated, order-preserved).
  • The TUI App.Run path and the CLI PrepareUserMessage / CreateUserMessageWithAttachment helpers register every successfully attached file's absolute path on the session.
  • runtime.newSubSession snapshots the parent's AttachedFiles, propagates them to the child via WithAttachedFiles, and surfaces them in the task system prompt under <attached_files>. Sub-sub-sessions inherit too because the child becomes the next parent.
  • branch.go and the in-memory store preserve AttachedFiles when copying session metadata.
  • In-memory only (not persisted in SQLite) — the use case is delegation within an active session; reloaded sessions can re-attach.

This is the structural fix that doesn't trust the LLM to remember.

Why both?

A alone C alone A + C
Files the user attached with @ LLM may still drop the path ✅ deterministic ✅ deterministic
Files the parent agent discovers mid-flight ✅ LLM is reminded ✗ not in AttachedFiles ✅ LLM is reminded

Commits

  1. a896bc03d Encourage absolute file paths when delegating to sub-agents — prompt-only change in pkg/session/session.go (parent prompt) and pkg/runtime/agent_delegation.go (sub-agent prompt). E2E cassettes (TestA2AServer_MultiAgent, TestRuntime_MultiAgent_SessionReload) updated to match.

  2. 359b8f6a8 Propagate user-attached files to sub-agent sessions — adds the AttachedFiles plumbing (session field + helpers, TUI/CLI registration, sub-session inheritance, system-prompt surfacing). New tests in pkg/session/session_options_test.go and pkg/runtime/agent_delegation_test.go.

  3. 86641e4fe Tidy up the AttachedFiles plumbing — pure refactor, no behaviour change. Single strings.Builder in buildTaskSystemMessage, noAttachment closure to collapse seven duplicated returns in CreateUserMessageWithAttachment, drops a double clone in branch.go, trims over-eager comments.

  4. 22e40848e Reject dangling and non-absolute paths in AttachedFiles — review fixes:

    • Bug: App.Run recorded att.FilePath before processFileAttachment validated it, so paths to missing files or directories silently propagated to sub-agents. processFileAttachment now returns a bool ("the path resolved to a real, regular file") and App.Run only records on true. Content-handling failures (too large to inline, unsupported MIME, transient read errors) still record because a sub-agent may have larger limits or different tools than the parent.
    • Quality: AddAttachedFile's parameter is named absPath and the docstring claims absolute, but the function accepted any string. Added a filepath.IsAbs guard with a slog.Debug for visibility.
    • Doc: Session.AttachedFiles field comment now mentions all three entry points (@-mention, /attach, --attach).

Tests

  • mise test ✅ all green
  • mise lint ✅ green (golangci-lint run: 0 issues, custom lint .: 778 files, no offenses, go mod tidy --diff: clean)
  • New unit tests: TestAddAttachedFile (dedup, empty, non-absolute, snapshot independence), TestWithAttachedFiles, TestSubSessionInheritsAttachedFiles, TestSubSessionWithoutAttachedFilesOmitsBlock, plus expanded cases in TestBuildTaskSystemMessage.
  • E2E cassettes for multi-agent tests updated and replay cleanly.

What's intentionally not changed

  • Not persisted in SQLite. The use case is delegation within an active session; persisting would need a schema migration with no current consumer.
  • No exposure via HTTP API. GET /sessions and GET /sessions/:id use curated DTOs (api.SessionsResponse, api.SessionResponse) that don't include AttachedFiles. POST /sessions echoes a freshly-created session where the field is empty. Same exposure surface as the pre-existing CustomModelsUsed.
  • AttachedFiles is not literal @-resolution by sub-agents. That would reproduce an editor concern in the runtime and doesn't help when the parent drops the @ entirely (the actual reported failure mode).

dgageot added 4 commits April 27, 2026 14:45
Sub-agents created via transfer_task receive only the task string; they have no access to the parent's conversation history or to the files the user attached. When the parent passes a bare filename like "something.go", the sub-agent has to scan the workspace and may pick the wrong file when several share the same name.

Update both system prompts to make this contract explicit:

* Parent agents are told to always include absolute paths in the task

  description and reminded that sub-agents do not see attached files.

* Sub-agents are told to treat absolute paths in <task> as authoritative

  and to ask for an absolute path (rather than guess) when a file is

  referenced by name only.

The matching e2e cassettes are updated so the prompt change still replays.

Assisted-By: docker-agent
When the user attaches files via @-mentions or /attach, only the root agent sees them - the file content is inlined into the parent's user message. When that agent delegates work via transfer_task, the sub-agent starts in a fresh session and has no record of which files the user originally attached, so a bare filename in the task description forces the sub-agent to scan the workspace and risks picking the wrong file when several share the same name.

Track attached files explicitly on the session and inherit them in sub-sessions:

* session.Session gains an AttachedFiles []string field plus AddAttachedFile,

  AttachedFilesSnapshot, and a WithAttachedFiles option. Paths are deduplicated

  and order-preserved.

* The TUI App.Run path and the CLI PrepareUserMessage / CreateUserMessageWithAttachment

  helpers register every successfully attached file's absolute path on the session.

* runtime.newSubSession copies the parent's AttachedFiles into the child and

  surfaces them in the task system prompt under <attached_files>, so the

  sub-agent has the absolute paths up-front.

* branch.go and the in-memory store preserve AttachedFiles when copying

  session metadata.

AttachedFiles is in-memory only (not persisted in SQLite) since the use case

is delegating within an active session; reloaded sessions can re-attach as

needed and would otherwise require a schema migration.

Assisted-By: docker-agent
Pure cleanup, no behaviour change:

* buildTaskSystemMessage now uses a single strings.Builder throughout

  instead of mixing string concatenation with a one-off builder for the

  attached_files block.

* CreateUserMessageWithAttachment introduces a noAttachment closure so

  the seven best-effort fallback paths share one return statement instead

  of repeating session.UserMessage(userContent), "" everywhere.

* branch.go drops a redundant cloneStringSlice on the result of

  AttachedFilesSnapshot, which already returns an independent copy.

* The over-eager three-line comments at the AddAttachedFile call sites

  and on top of newSubSession are replaced with a single line each that

  states the contract.

Assisted-By: docker-agent
Two correctness fixes uncovered while reviewing the AttachedFiles plumbing:

1. App.Run was recording att.FilePath on the session before

   processFileAttachment had a chance to verify the file existed or

   was a regular file. A user typing @some/missing/path or

   @some/directory in the editor would silently propagate that

   broken reference to every sub-agent created in the rest of the

   session.

   processFileAttachment now returns a bool meaning "the path resolves

   to a real, regular file we attempted to surface" and App.Run only

   records on true. Content-handling failures (too large to inline,

   unsupported MIME, transient read errors) still record the path

   because a sub-agent may have larger limits or different tools than

   the parent.

2. AddAttachedFile is documented as taking an absolute path but

   accepted any string. A buggy caller could leak a relative path

   into the sub-agent's system prompt, where it would be ambiguous.

   Add a filepath.IsAbs guard that silently drops non-absolute paths

   (with a debug log) and update the doc comment.

Also clarify the Session.AttachedFiles field comment to mention all

three entry points (editor @-mention, /attach directive, --attach CLI

flag) and extend TestAddAttachedFile / TestWithAttachedFiles to cover

the new validation.

Assisted-By: docker-agent
@dgageot dgageot requested a review from a team as a code owner April 27, 2026 13:39
@dgageot dgageot merged commit 579c933 into docker:main Apr 27, 2026
9 checks passed
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