Skip to content

feat(agents): evolvable agent dependencies for flow agents#39

Merged
zrosenbauer merged 4 commits intomainfrom
feat/registry-provider
Mar 19, 2026
Merged

feat(agents): evolvable agent dependencies for flow agents#39
zrosenbauer merged 4 commits intomainfrom
feat/registry-provider

Conversation

@zrosenbauer
Copy link
Member

Summary

  • Adds agents field to FlowAgentConfig so flow agents can declare named agent dependencies
  • Passes agents to the handler via FlowAgentParams, defaulting to {} when not configured
  • evolve() now shallow-merges agents on flow agents (same as it does for regular agents' tools/agents)
  • Exports new FlowSubAgents type from @funkai/agents

This solves the closure capture problem: when a flow handler references a module-level agent import, evolve() can't rewire it. With this change, handlers use agents.core instead of a closed-over coreAgent, making the reference evolvable.

Before

import { coreAgent } from './core.js'

const flow = flowAgent(config, async ({ $, input }) => {
  // coreAgent is closed over — evolve() can't touch it
  await $.agent({ agent: coreAgent, input })
})

After

const flow = flowAgent(
  { ...config, agents: { core: coreAgent } },
  async ({ $, input, agents }) => {
    await $.agent({ agent: agents.core, input })
  },
)

// Now works — handler receives evolvedCore
evolve(flow, { agents: { core: evolvedCore } })

Test plan

  • New tests in evolve.test.ts: shallow merge, preserve on no override, handler receives evolved agents at runtime, mapper overload
  • New tests in flow-agent.test.ts: handler receives agents from config, empty record default, streaming path
  • All 624 existing tests pass
  • Typecheck clean (tsc --noEmit)
  • Lint clean (oxlint)
  • Build succeeds

Flow agents can now declare named agent dependencies via the `agents`
config field. These are passed to the handler in params and are
shallow-merged by `evolve()`, solving the closure capture problem
where evolve() couldn't rewire agents referenced inside a flow handler.

Co-Authored-By: Claude <noreply@anthropic.com>
@changeset-bot
Copy link

changeset-bot bot commented Mar 19, 2026

🦋 Changeset detected

Latest commit: 3813f4a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@funkai/agents Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link

vercel bot commented Mar 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
funkai Ignored Ignored Preview Mar 19, 2026 8:02pm

Request Review

@coderabbitai
Copy link

coderabbitai bot commented Mar 19, 2026

Caution

Review failed

Pull request was closed or merged during review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a05a6f94-5809-49af-bec9-1d5c0c5f63ca

📥 Commits

Reviewing files that changed from the base of the PR and between 34576e2 and 3813f4a.

📒 Files selected for processing (2)
  • packages/agents/src/core/agents/flow/flow-agent.ts
  • packages/agents/src/index.ts

📝 Walkthrough

Walkthrough

Adds support for named sub-agent dependencies for flow agents: new agents config field and FlowSubAgents type, flow handlers receive a frozen agents record at runtime, and evolve() shallow-merges agents when evolving flow agent configs.

Changes

Cohort / File(s) Summary
Type Definitions & Exports
packages/agents/src/core/agents/flow/types.ts, packages/agents/src/index.ts
Add FlowSubAgents type; add optional agents?: FlowSubAgents to FlowAgentConfigBase; require agents on FlowAgentParams; re-export FlowSubAgents.
Flow Agent Runtime Propagation
packages/agents/src/core/agents/flow/flow-agent.ts
Prepare a frozen agents object from config.agents ?? {} and forward it into handler invocation for both generate() and stream() paths; extend prepared state types.
Config Merging Logic
packages/agents/src/core/agents/evolve.ts
mergeFlowAgentConfigs destructures agents from overrides and shallow-merges base.agents with overrideAgents via mergeRecordField instead of replacing wholesale.
Tests
packages/agents/src/core/agents/evolve.test.ts, packages/agents/src/core/agents/flow/flow-agent.test.ts
Add tests for shallow-merge semantics, default-to-empty-object behavior, handler receiving merged/evolved agents, and mapper-function override behavior.
Release Metadata
.changeset/flow-agent-agents-dep.md
New changeset documenting the agents field, usage examples, and evolve() merge behavior for a minor release.

Sequence Diagram(s)

sequenceDiagram
  participant Dev as Dev / Evolve call
  participant Evolve as evolve()
  participant FlowAgent as FlowAgent (prepared)
  participant Handler as Flow Handler
  participant SubAgent as Sub-agent

  Dev->>Evolve: evolve(flow, { agents: { core: evolvedCore } })
  Evolve->>FlowAgent: mergeFlowAgentConfigs(base, overrides with agents)
  Note right of Evolve: shallow-merge `agents` (base + override)
  FlowAgent->>FlowAgent: prepare state (freeze agents)
  FlowAgent->>Handler: invoke handler(..., agents: frozenAgents)
  Handler->>SubAgent: call via $.agent(agents.core)
  SubAgent-->>Handler: respond
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title accurately summarizes the main feature: adding evolvable agent dependencies to flow agents, which is the primary objective of the changeset.
Description check ✅ Passed Description is directly related to the changeset, explaining the feature, rationale, before/after code examples, and test coverage.
Linked Issues check ✅ Passed Changes are well-scoped to a single cohesive feature (evolvable agent dependencies) with clear test coverage and no scope creep.
Out of Scope Changes check ✅ Passed All changes are directly related to the feature: type definitions, implementation updates, tests, and exports. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/agents/src/core/agents/flow/flow-agent.ts (1)

357-362: ⚠️ Potential issue | 🟠 Major

Prevent shared agents config from leaking across flow invocations.

At lines 361 and 458, config.agents is passed by reference into the handler. If a handler mutates this record, it mutates the shared flow config and affects subsequent invocations. Per guidelines: "Never mutate shared state or function arguments."

Apply a defensive shallow copy with Object.freeze():

Fix
const output = await (handler as FlowAgentHandler<TInput, TOutput>)({
  input: parsedInput,
  $,
  log,
+ agents: Object.freeze({ ...(config.agents ?? {}) }),
- agents: config.agents ?? {},
});

Repeat at both lines 361 and 458.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agents/src/core/agents/flow/flow-agent.ts` around lines 357 - 362,
Prevent mutation of shared flow config by creating a defensive, shallow frozen
copy of config.agents before passing it into handler calls: replace direct uses
of "config.agents ?? {}" in the FlowAgentHandler invocation (the call where
"handler as FlowAgentHandler<TInput, TOutput>" is invoked) and the second
handler invocation later in the file (the other place currently passing
"config.agents ?? {}") with a local variable like "const agents =
Object.freeze({ ...(config.agents ?? {}) })" and pass that "agents" variable
instead so handlers receive an immutable shallow copy rather than a reference to
the shared config.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/agents/src/core/agents/flow/flow-agent.ts`:
- Around line 357-362: Prevent mutation of shared flow config by creating a
defensive, shallow frozen copy of config.agents before passing it into handler
calls: replace direct uses of "config.agents ?? {}" in the FlowAgentHandler
invocation (the call where "handler as FlowAgentHandler<TInput, TOutput>" is
invoked) and the second handler invocation later in the file (the other place
currently passing "config.agents ?? {}") with a local variable like "const
agents = Object.freeze({ ...(config.agents ?? {}) })" and pass that "agents"
variable instead so handlers receive an immutable shallow copy rather than a
reference to the shared config.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0b3da990-87ee-4adc-b90e-f04005328c06

📥 Commits

Reviewing files that changed from the base of the PR and between 5d9fbeb and ab97449.

📒 Files selected for processing (7)
  • .changeset/flow-agent-agents-dep.md
  • packages/agents/src/core/agents/evolve.test.ts
  • packages/agents/src/core/agents/evolve.ts
  • packages/agents/src/core/agents/flow/flow-agent.test.ts
  • packages/agents/src/core/agents/flow/flow-agent.ts
  • packages/agents/src/core/agents/flow/types.ts
  • packages/agents/src/index.ts

zrosenbauer and others added 3 commits March 19, 2026 15:56
Create a shallow frozen copy of config.agents in prepareFlowAgent()
so handlers receive an immutable snapshot rather than a direct
reference to the shared config object.

Co-Authored-By: Claude <noreply@anthropic.com>
The `?? {}` fallback is unnecessary inside a spread — spreading
`undefined` is already a no-op in object literals.

Co-Authored-By: Claude <noreply@anthropic.com>
@zrosenbauer zrosenbauer merged commit 3b78ce6 into main Mar 19, 2026
4 of 5 checks passed
@zrosenbauer zrosenbauer deleted the feat/registry-provider branch March 19, 2026 20:05
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.

1 participant