Skill-Based Sub-Agents with LangGraph4j and Spring AI
Combine Tools & Skill to create Agent
Modern agentic systems usually expose two strong but separate abstractions:
- Tools, which give an LLM executable capabilities
- Skills, which package reusable instructions for a narrow task
This article shows how to combine both ideas in LangGraph4j so that a skill is not just prompt text, but a fully operational sub-agent exposed as a tool. The result is a practical pattern for building modular multi-agent systems in Java with Spring AI and LangGraph4j, while keeping each agent focused, reusable, and low in context cost.
Starting from a standard ReACT agent, the approach implemented is quite simple.
- Load a
SKILL.mdfile - Read its front matter to define the tool contract ( name and argument description)
- Use the skill body as the sub-agent system prompt
- Restrict the sub-agent to only the tools declared by the skill
- Register the sub-agent in a parent ReAct agent as if it were a normal tool
In other words, a skill becomes an executable tool-backed agent.
Why This Pattern Matters
The standard ReAct pattern already gives an LLM a strong execution model: reason, choose a tool, inspect the result, and continue until the task is complete. That model is useful because the LLM does not need to commit to a fixed plan up front. It can adapt after every tool call.
The next step is to let a tool itself be implemented by another agent.
Once that happens, the parent agent is no longer forced to know every operational detail. It only needs to know:
- which specialized capability exists
- when to invoke it
- what input to pass
The specialized logic is delegated to a narrower agent with its own instructions and its own restricted toolset. This gives us a clean sub-agent architecture without inventing a new orchestration primitive. We reuse the same tool-calling semantics that ReAct agents already understand well.
Why Reusing Tool Semantics Is Powerful
One of the most valuable properties of LLM-based agents is their ability to build and revise a tool execution plan while reasoning over intermediate results. ReAct made this practical by combining thought, action, and observation in a loop.
When a tool call can transparently invoke another agent, we preserve that planning model instead of replacing it.
The parent agent can:
- decide that a specialized capability is needed
- provide only the task-relevant context
- wait for a result exactly as it would for a standard tool
The child agent can:
- run its own reasoning loop
- invoke its own allowed tools
- return a compact outcome to the parent
This gives hierarchical coordination without exposing the full inner workflow to the parent prompt.
Minimizing Context and Token Usage
This pattern also improves context discipline.
Without sub-agents, a single generalist agent often needs:
- every tool definition
- all operating instructions for every domain
- every procedural rule in one large system prompt
That quickly becomes expensive and unreliable.
With skill-based sub-agents:
- the parent only sees high-level capabilities
- each child sees only the instructions relevant to its task
- each child receives only the tools it is allowed to use
This narrower context has practical benefits:
- fewer tokens sent to the model
- lower prompt interference between unrelated domains
- clearer tool choice
- better modularity as the system grows
The
allowed-toolsskill property plays an important role here anabling tool filtering. It turns tool access into an explicit design decision instead of an accidental side effect of what happened to be registered globally.
Reference implementation
I’ve started implementation in the new LangGraph4j release stream 1.9 currently in active development. The implementation is centered on [SkilledReactSubAgent], which turns a markdown skill into both:
- a compiled LangGraph4j sub-graph
- a Spring AI tool (i.e.
ToolCallback)
That means the same component can participate in the graph as an agent and present itself to the parent LLM as a callable tool.
In particular the flow is:
- Parse the
SKILL.md - Extract
nameanddescriptionfrom front matter - Optionally filter tools using
allowed-tools - Use the markdown body as the sub-agent instructions
- Compile the agent
- Wrap it into a function tool whose input is a single
contextfield
The important design choice is that the tool definition is derived from the skill metadata, while the agent behavior is derived from the skill content.
Skill Format
The supported skill format is
---
name: <agent name>
description: |
<multi line description>
allowed-tools: <list of tools>
---
<skill body>
From Skill to Tool-Backed Agent
The conversion happens in SkilledReactSubAgent.Builder#build(...). This is the crucial bridge where:
- the skill metadata defines the external API
- the skill body defines the internal operating behavior
- the allowed tools define the sub-agent execution boundary
The resulting agent is then wrapped as a Spring AI function tool.
From the parent agent point of view, this is just another tool. Internally, it is a complete ReAct-capable agent workflow.
Examples
Purchase Assistant Agent
This is an agent that allow to select a produdct from a reference marketplace and purchase it. Below the simplified component diagram.

Marketplace Skill
---
name: agent-marketplace
description: |
marketplace agent, ask for information about products. This is agent that provides the information on the product marketplace.
required data are:
* product name
allowed-tools: searchByProduct
---
## Retrieve product information
We need all information about about the product of interest in particular is mandatory to have
* product name
with such information we have to invoke tool `searchByProduct` and return the result
Payment Skill
---
name: agent-payment
description: |
payment agent, this is the agent that provides payment service.
required data are:
* Product name
* Product price
* Product currency
* the International Bank Account Number (IBAN) - optional
allowed-tools:
- submit-payment
- retrieve-iban
---
## submit a payment for purchasing a specific product
We need all information about purchasing to allow the payment
* Product name
* Product price
* Product price currency
* International Bank Account Number (IBAN)
With such information you must invoke tool `submit-payment` and return the result of the payment transaction plus the IBAN.
If IBAN is not provided invoke tool `retrieve-iban` to retrieve the required IBAN.
Observing the skills above they define three different concerns:
name: the tool name exposed to the parent agentdescription: the tool description shown to the modelallowed-tools: the only tools available inside the sub-agent
Everything after the front matter becomes the sub-agent system prompt.
Again, this is a strong separation of concerns. The parent agent sees a concise tool contract, while the child agent receives richer operating instructions.
Finally the code
Below a representative code snippet that show how wires two skill-based sub-agents into a parent agent:
var subAgentMarketplace = SkilledReactSubAgent.builder()
.chatModel(chatModel)
.tools(Marketplace.tools())
.build(compileConfig,
SkillSource.of(Paths.get("skills/agent-marketplace/")));
var subAgentPayment = SkilledReactSubAgent.builder()
.chatModel(chatModel)
.tools(Payment.tools())
.build(compileConfig,
SkillSource.of(Paths.get("skills/agent-payment/")));
var purchaseAssistantAgent = AgentExecutorEx.builder()
.chatModel(chatModel)
.tool(subAgentMarketplace)
.tool(subAgentPayment)
.build(compileConfig);
var input = """
search for product 'X'.
If found proceed to payment with IBAN US82WEST1234567890123456
to purchase it
""";
var result = purchaseAssistantAgent.invoke(
GraphInput.args( Map.of("messages", new UserMessage(input) )), runnableConfig);
This composition is interesting because the purchase agent (the parent) does not need detailed knowledge of:
- how product lookup works
- how payment submission works
- when IBAN retrieval is required
Those rules are delegated to the sub agents themselves.
That is a simple but good example of behavioral encapsulation. The parent agent only decides to involve marketplace agent to retrieve product and after that to involve payment agent to purchase.
This is a very basic but representative example, it is useful because it shows the intended layering:
- the user gives a single business request
- the parent agent decomposes the work
- specialized sub-agents execute their own logic
- the final answer comes back through the normal tool-response path
Interesting note that each sub agent, potentially, can relies on different LLM chat model so we can further optimize overall agent execution process
Below a more detailed componet diagram generated by LangGraph4j itself.

From this diagram it is quite clear that the sub agents are nothing but sub-graphs
Design Advantages
This approach has several practical advantages.
-
Reuse Existing Agent Patterns
There is no need to invent a separate orchestration protocol for sub-agents. A sub-agent is exposed as a tool, so the parent agent can use standard ReAct behavior without extra prompting complexity.
-
Better Modularity
Each skill owns:
- its tool-facing identity
- its internal instructions
- its allowed tool boundary
That makes the system easier to evolve than a single monolithic agent prompt.
-
Smaller and Cleaner Context
The child agent receives only what it needs. This reduces token usage and lowers the risk that unrelated instructions distort tool choice or task execution.
-
Safer Capability Scoping
By filtering tools from
allowed-tools, the framework prevents a child agent from accidentally seeing tools outside its intended domain. -
Better Reusability
A skill can be packaged, versioned, and reused across different parent agents. The same marketplace or payment skill can participate in many workflows as long as the tool contract remains stable.
Tradeoffs and Implementation Considerations
This pattern is strong, but it is not free of tradeoffs.
-
The Parent Must Summarize Well
Because the child receives a single
contextfield, the parent agent needs to pass the relevant facts clearly. If critical information is omitted, the child may need extra recovery steps or may fail to act. -
Tool Descriptions Matter More
Since the parent sees the child as a tool, the
nameanddescriptionfields in the skill front matter become part of the routing surface for the LLM. Weak metadata will reduce invocation quality. -
Prompt Boundaries Need Discipline
The skill body is effectively the child system prompt. If it is vague, contradictory, or overloaded, the benefits of modularity disappear. Skills need to stay focused.
-
Observability Is Important
Nested agents can make execution harder to inspect if tracing is weak. LangGraph4j’s graph structure and step streaming help here, but production systems should still invest in logging and traceability.
Why LangGraph4j Fits This Well
LangGraph4j is a good fit for this pattern because it already models agent workflows as explicit graphs and integrates naturally with Spring AI tool abstractions.
That matters for two reasons:
- the sub-agent is not just prompt composition; it is a compiled graph with state and execution hooks
- the parent integration remains ergonomic because Spring AI already treats capabilities as tools
- LangGraph4j support interruptions in subgraph enabling Human In The Loop also when involving sub agents
So the implementation is not forcing two incompatible ideas together. It is aligning:
- LangGraph4j graph execution
- Spring AI tool calling
- markdown-defined skills
into a single reusable agent-building pattern.
Conclusion
Turning a skill into a sub-agent is a small change with a large impact.
It lets us:
- preserve the strengths of the ReAct loop
- delegate specialized behavior cleanly
- reduce context size
- constrain tool access per domain
- build multi-agent systems out of reusable skill modules
The idea is implemented in a compact and pragmatic way: a SKILL.md file becomes the source of both the sub-agent prompt and the tool contract exposed to the parent agent.
For Java teams building agentic applications with Spring AI and LangGraph4j, this is a practical pattern for moving from a single large agent toward a modular multi-agent design without abandoning the tool semantics that current LLMs already handle well.
The implementation is in progress on release 1.9-SNAPSHOT in develop branch of LangGraph4j repository but the first result are very promising.
Hope this could help and encourage usage of LangGraph4j for your next Agentic Workflow. Checkout project, try it and let me know your feedback and… happy AI coding! 👋