Skip to content

Commit 8f2a84c

Browse files
authored
fix: propagate parent deps to sub-agents by default (#45)
* fix: propagate parent deps to sub-agents by default Sub-agent tools always received empty ctx.deps because shared_keys was hardcoded to []. Now all non-plugin deps flow through automatically. Users can restrict with sub_agent_shared_deps: [:key1, :key2]. * fix: raise ArgumentError for invalid sub_agent_shared_deps type
1 parent 508c850 commit 8f2a84c

File tree

4 files changed

+99
-7
lines changed

4 files changed

+99
-7
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [0.14.2] - 2026-04-13
6+
7+
### Fixed
8+
9+
- **SubAgent deps propagation** — parent deps now flow to sub-agents by default (excluding plugin-internal keys like templates, PubSub, concurrency config). Use `sub_agent_shared_deps: [:key1, :key2]` in deps to restrict which keys are shared.
10+
511
## [0.14.0] - 2026-04-11
612

713
### Added

README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -831,14 +831,17 @@ The `SubAgent` plugin provides two tools:
831831
- `delegate_task` — run a single sub-agent for sequential delegation
832832
- `spawn_agents` — run multiple sub-agents concurrently via `Task.Supervisor`
833833

834-
Each sub-agent runs in its own isolated context. Configure concurrency
835-
limits and timeouts via deps:
834+
Each sub-agent runs in its own context but inherits parent deps automatically
835+
(excluding plugin-internal keys). Configure concurrency, timeouts, and
836+
optionally restrict which deps are shared:
836837

837838
```elixir
838839
deps: %{
839840
sub_agent_templates: templates,
840-
parallel_max_concurrency: 3, # Max concurrent sub-agents (default: 5)
841-
parallel_timeout: 60_000 # Per-task timeout in ms (default: 120_000)
841+
database: MyApp.Repo, # shared with sub-agents automatically
842+
parallel_max_concurrency: 3, # max concurrent sub-agents (default: 5)
843+
parallel_timeout: 60_000, # per-task timeout in ms (default: 120_000)
844+
sub_agent_shared_deps: [:database] # optional: restrict to specific keys
842845
}
843846
```
844847

lib/nous/plugins/sub_agent.ex

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,19 @@ defmodule Nous.Plugins.SubAgent do
4343
4444
- `:parallel_max_concurrency` — max concurrent sub-agents (default: 5)
4545
- `:parallel_timeout` — per-task timeout in ms (default: 120_000)
46+
47+
## Deps Propagation
48+
49+
Parent deps are automatically shared with sub-agents, excluding plugin-internal
50+
keys (templates, PubSub config, concurrency settings). To restrict which deps
51+
are shared, set `:sub_agent_shared_deps`:
52+
53+
deps: %{
54+
sub_agent_templates: %{...},
55+
database: MyApp.Repo,
56+
api_key: "sk-...",
57+
sub_agent_shared_deps: [:database] # only :database is forwarded
58+
}
4659
"""
4760

4861
@behaviour Nous.Plugin
@@ -54,6 +67,15 @@ defmodule Nous.Plugins.SubAgent do
5467
@default_max_concurrency 5
5568
@default_timeout 120_000
5669

70+
@plugin_internal_keys [
71+
:sub_agent_templates,
72+
:sub_agent_shared_deps,
73+
:parallel_max_concurrency,
74+
:parallel_timeout,
75+
:__sub_agent_pubsub__,
76+
:__sub_agent_pubsub_topic__
77+
]
78+
5779
# ===========================================================================
5880
# Plugin callbacks
5981
# ===========================================================================
@@ -294,6 +316,21 @@ defmodule Nous.Plugins.SubAgent do
294316
# Shared internals
295317
# ===========================================================================
296318

319+
@doc false
320+
def compute_sub_deps(parent_deps) do
321+
case parent_deps[:sub_agent_shared_deps] do
322+
keys when is_list(keys) ->
323+
Map.take(parent_deps, keys)
324+
325+
nil ->
326+
Map.drop(parent_deps, @plugin_internal_keys)
327+
328+
invalid ->
329+
raise ArgumentError,
330+
":sub_agent_shared_deps must be a list of keys or nil, got: #{inspect(invalid)}"
331+
end
332+
end
333+
297334
defp resolve_agent(ctx, template_name, _args) when is_binary(template_name) do
298335
templates = ctx.deps[:sub_agent_templates] || %{}
299336

@@ -340,9 +377,7 @@ defmodule Nous.Plugins.SubAgent do
340377
label = if index, do: "[sub-agent #{index}]", else: "[sub-agent]"
341378
Logger.info("#{label} Starting: #{String.slice(task, 0, 80)}")
342379

343-
# Isolated deps — only pass through explicitly shared keys
344-
shared_keys = []
345-
sub_deps = Map.take(parent_ctx.deps, shared_keys)
380+
sub_deps = compute_sub_deps(parent_ctx.deps)
346381

347382
# Propagate PubSub with scoped topic
348383
parent_pubsub = parent_ctx.deps[:__sub_agent_pubsub__]

test/nous/plugins/sub_agent_test.exs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,54 @@ defmodule Nous.Plugins.SubAgentTest do
602602
end
603603
end
604604

605+
# ===========================================================================
606+
# Deps propagation
607+
# ===========================================================================
608+
609+
describe "compute_sub_deps/1" do
610+
test "forwards user deps and excludes plugin-internal keys" do
611+
parent_deps = %{
612+
workspace_id: 42,
613+
database: :fake_db,
614+
sub_agent_templates: %{"t" => %{}},
615+
sub_agent_shared_deps: nil,
616+
parallel_max_concurrency: 3,
617+
parallel_timeout: 60_000,
618+
__sub_agent_pubsub__: SomePubSub,
619+
__sub_agent_pubsub_topic__: "topic:1"
620+
}
621+
622+
result = SubAgent.compute_sub_deps(parent_deps)
623+
624+
assert result == %{workspace_id: 42, database: :fake_db}
625+
end
626+
627+
test "explicit allowlist restricts to specified keys" do
628+
parent_deps = %{
629+
workspace_id: 42,
630+
database: :fake_db,
631+
api_key: "secret",
632+
sub_agent_shared_deps: [:workspace_id]
633+
}
634+
635+
result = SubAgent.compute_sub_deps(parent_deps)
636+
637+
assert result == %{workspace_id: 42}
638+
end
639+
640+
test "empty allowlist returns empty map" do
641+
parent_deps = %{
642+
workspace_id: 42,
643+
database: :fake_db,
644+
sub_agent_shared_deps: []
645+
}
646+
647+
result = SubAgent.compute_sub_deps(parent_deps)
648+
649+
assert result == %{}
650+
end
651+
end
652+
605653
# ===========================================================================
606654
# Integration with Plugin system
607655
# ===========================================================================

0 commit comments

Comments
 (0)