Skip to content

[FEATURE]: add directory parameter to task tool for monorepo subagent dispatch #29271

Closed as not planned

Description

@trin4ik

Feature hasn't been suggested before.

  • I have verified this feature I'm about to request hasn't been suggested before.

Describe the enhancement you want to request

Problem

In a monorepo each subproject can ship its own .opencode/, AGENTS.md and skills. Today there is no way to dispatch a subagent task into a subproject so that those project-local resources are picked up. The task tool always creates the child session inside the parent session's InstanceContext, so the subagent inherits the parent's working directory, agents, skills and AGENTS.md chain — even when the work clearly belongs to a different subproject.

Use case

Layout (simplified):

my-monorepo/
├─ AGENTS.md                          # monorepo-wide guidance
├─ .opencode/
│  └─ agent/
│     └─ orchestrator.md
├─ backend/
│  └─ api/
│     ├─ AGENTS.md                    # api-specific guidance
│     └─ .opencode/
│        └─ skill/
│           ├─ abstract-model/
│           ├─ events/
│           └─ metrics/
└─ frontend/
   └─ web/
      ├─ AGENTS.md
      └─ .opencode/skill/...

Parent session runs from my-monorepo/. The orchestrator needs to delegate a focused task to backend/api. With the current task tool the subagent sees only the monorepo-root AGENTS.md and root-level skills — none of the backend/api/.opencode/skill/* are available, even though they were authored exactly for that subagent's job.

Proposed solution

Add an optional directory: string parameter to the task tool. When set, the subagent runs inside the matching InstanceContext: cwd, agent registry, AGENTS.md walk-up and skills all resolve against that path. When omitted, behavior is identical to today (subagent inherits the parent session's directory) — fully backwards compatible.

This is essentially the same per-request directory routing that the HTTP middleware already applies (workspace-routing.ts + instance-context.ts → InstanceStore.load({ directory }) + Effect.provideService(InstanceRef, ctx)). The proposal is to make that capability reachable from the task tool's args so it can be driven by the LLM, not only by HTTP clients.

Before vs after

Before — subagent inherits parent's InstanceContext:

task({
  subagent_type: "build",
  description: "...",
  prompt: "...",
})
// → subagent cwd = my-monorepo/
// → AGENTS.md: my-monorepo/AGENTS.md
// → skills: only monorepo-root skills

After — subagent gets backend/api's InstanceContext:

task({
  subagent_type: "build",
  description: "...",
  prompt: "...",
  directory: "/abs/path/my-monorepo/backend/api",
})
// → subagent cwd = my-monorepo/backend/api/
// → AGENTS.md: my-monorepo/AGENTS.md + my-monorepo/backend/api/AGENTS.md
// → skills: monorepo-root + backend/api/.opencode/skill/*

Implementation sketch

Single-file change in packages/opencode/src/tool/task.ts:

  1. Add directory?: string to Parameters and BaseParameters schemas.
  2. Build a scoped helper. When params.directory is set, lazily resolve InstanceStore.Service via Effect.serviceOption and wrap the relevant Effects in store.provide({ directory }, ...). When omitted, pass the Effect through unchanged.
  3. Wrap three calls in scoped:
    • agent.get(params.subagent_type) — resolve the subagent from the target project.
    • sessions.create(...) — persist the new session with directory set correctly.
    • ops.prompt(...) — run the prompt loop inside the target InstanceContext.

Using serviceOption keeps TaskTool's declared dependency surface unchanged when directory is omitted, so existing test layers and the ToolRegistry.layer requirement set do not need to change.

Related context

Backwards compatibility

directory is optional. When omitted, the tool behaves exactly as today: the child session inherits the parent session's InstanceContext. No public API or schema changes for existing callers.

Verification plan

Spin up bun dev serve, create a parent session in a monorepo root, drive the subagent with task({ ..., directory: "<repo>/backend/api" }), then ask it to print pwd, the names of skills it can see, and the AGENTS.md paths it has loaded. Expected: cwd matches the target subproject, project-local skills appear, both root and subproject AGENTS.md are loaded. Compare with a no-directory call to confirm default behavior is unchanged.

I have a working patch and a green local run against this exact scenario. Happy to open the PR if the approach sounds right.

Activity

  1. github-actions commented on May 25, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    If your proposal is meaningfully different (e.g., specifically targeting the task tool's directory parameter rather than a broader orchestration system), it may help to add a brief note clarifying the distinction from #17994.

  2. trin4ik commented on May 25, 2026

    @trin4ik
    Author

    This issue might be a duplicate of existing issues. Please check:

    If your proposal is meaningfully different (e.g., specifically targeting the task tool's directory parameter rather than a broader orchestration system), it may help to add a brief note clarifying the distinction from #17994.

    Thanks for pointing this out. I agree this overlaps with #17994 in motivation, but I don't think it is a duplicate.

    #17994 is a broader request for native multi-agent orchestration / isolated workspaces / worktree lifecycle management. This issue is intentionally much narrower: expose an optional directory argument on the existing task tool so a child session can run inside the matching InstanceContext.

    That means no workspace lifecycle, no worktree creation, no merge strategy, and no new orchestration system. The goal is only to let an existing parent agent dispatch an existing subagent into a specific project directory and have cwd, local .opencode, AGENTS.md walk-up, agents and skills resolve from that directory.

    Related PRs I found:

    So this proposal can be a small standalone addition: task({ ..., directory }) wraps agent.get, sessions.create, and ops.prompt in InstanceStore.load/provide({ directory }). When directory is omitted, behavior stays exactly as today.

    Happy to open a focused PR for just this if the approach sounds acceptable.

  3. trin4ik commented on May 26, 2026

    @trin4ik
    Author

    Quick follow-up while we've been using a local plugin that backports this exact directory param to task (shadow registration via opencode plugin API, stop-gap until this lands upstream): we hit a related Web UI gotcha that's worth documenting alongside this feature, since it surfaces precisely when the per-project subagent dispatch starts working as intended.

    Setup

    After dispatching a subagent with task({ directory: "<repo>/backend/api/v3", ... }), the child session is correctly created with directory=<repo>/backend/api/v3. TUI side everything works as expected — session_child_first keybind navigates into the running child live, regardless of where TUI is anchored.

    Web UI behaviour

    opencode Web UI renders a per-project session list view by filtering through GET /session?directory=<X>. Result:

    Web UI open in Sees parent (directory=<repo_root>) Sees child (directory=<repo_root>/backend/api/v3) live
    <repo_root> ✅ ❌ (only after child finishes)
    <repo_root>/backend/api/v3 ❌ ✅ live

    After the child finishes the subtask-card click in the parent session opens it normally (different render path — direct ID lookup, not listing).

    Probe data confirming directory filter on /session listing

    While a child session was live (parent's directory=<repo_root>, child's directory=<repo_root>/backend/api/v3):

    GET /session                                          → child NOT in listing (count=0)
    GET /session?directory=<repo_root>                    → child NOT in listing (count=0)
    GET /session?directory=<repo_root>/backend/api/v3     → child IS in listing (count=1) ✅
    
    # But direct lookup by ID and parentID-traversal both work cross-instance:
    GET /session/<child_id>                  → HTTP 200, identical JSON regardless of ?directory=
    GET /session/<child_id>?directory=<any>  → HTTP 200, identical JSON
    GET /session/<parent>/children           → child IS in array (no directory filter applied)
    

    So the route-level layer is fine — direct GET and parent→children traversal don't filter by directory. The /session listing endpoint that Web UI consumes for its session list does.

    Why this matters here specifically

    Once this feature ships, monorepo orchestrator workflows will routinely produce child sessions in subdirectories of the parent. Without a complementary tweak on the Web UI side, the live monitoring experience splits:

    • TUI users — full visibility, child-session navigation works.
    • Web UI users — have to open the target subproject in a second tab/window to see the child live; or wait for finish.

    The workaround (open both projects in Web UI in parallel) is acceptable but worth being aware of when announcing this feature.

    Diff in child session metadata that drives the filter

    The /session listing-side filter keys off two fields that differ between a parent-instance child and a target-instance child:

    - "directory": "<repo_root>",
    - "path": "",
    + "directory": "<repo_root>/backend/api/v3",
    + "path": "backend/api/v3",

    Everything else (projectID, parentID, agent, model, permission, ...) is byte-for-byte identical between the two metadata shapes. So the routing is purely off directory.

    Suggestion (separate from the core feature, listing-side)

    When the parent session has the listing open, surface its children regardless of their own directory — either by:

    1. UI side: in addition to /session?directory=<parent_dir>, also call /session/<parent_id>/children (cross-instance, already works on the API) and merge.
    2. Or API side: a ?include_children=true flag on /session?directory=<X> that pulls in children of in-scope sessions regardless of their own directory.
  4. github-actions commented on Jul 26, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions