Forward vs. Backward Skill Injection: loading prompts after a tool runs, not before
Hi LangGraph team and community,
I am currently a sophomore university student deeply passionate about building agentic systems. While I am still in school, I have been pushing the boundaries of LangGraph (specifically using LangGraph4j with Spring Boot) by building a production-grade WeChat Agent for real-world users.
During this process, I encountered a significant architectural challenge: static graph definitions often struggle with the dynamic nature of complex user intents.
The Challenge
In my WeChat Agent, the available tools (Skills) need to change dynamically based on the conversation context. Hard-coding every possible transition or injecting all system prompts at once became unmanageable and token-inefficient.
My Approach: State-Driven Dynamic Injection
I implemented a SkillManager component that reads the graph state between turns. Instead of static definitions, I use a “Tool-Triggered Skill Loading” pattern:
-
Inspect State — In the
llm_thinknode, I inspectstate.toolExecutionRequestsfrom the previous turn. -
Map Dependencies — I maintain a registry where Skills are bound to specific tool names (e.g.,
WeatherSkillbinds toqueryWeather). -
Dynamic Composition — If a tool was called, the manager appends the matching Skill’s prompt segment to the base system prompt for the next LLM invocation.
-
Auto-Cleanup — Because this is driven by transient state, skills are automatically “unloaded” when the context shifts, keeping the context window clean.
Crucially, this is a backward-triggered design: a skill loads after its bound tool has already run. So a skill isn’t trying to tell the model which tool to pick — it’s telling the model how to render the result, and what to consider next. For example, my WeatherSkill (bound to queryWeather) contains an answer-formatting rule and a chaining rule (“after a weather lookup, if the user also asks about attractions, search indoor vs. outdoor based on the weather”).
Code Logic Snippet (Java/LangGraph4j)
// Pseudocode representation of the enrichment logic
public String enrich(ChatGraphState state, String basePrompt) {
// 1. Get tools called in the previous turn
List<Object> requests = state.getToolExecutionRequests();
Set<String> calledTools = extractToolNames(requests);
// 2. Find skills bound to these tools
List<Skill> activeSkills = skillRegistry.findBoundSkills(calledTools);
// 3. Dynamically append prompt segments
if (!activeSkills.isEmpty()) {
return basePrompt + "\n\n" + activeSkills.stream()
.map(Skill::getPromptSegment)
.collect(Collectors.joining("\n\n"));
}
return basePrompt;
}
Relation to Existing Primitives
I’m aware LangChain’s Skills feature implements forward-looking progressive disclosure — exposing only a skill’s name + description, loading the full prompt on demand to guide tool selection — and that LangGraph’s create_agent lets prompt be a callable of state. My approach is the other direction: post-tool-call guidance. The skill loads after its bound tool has run, teaching the model how to render the result and what to chain next, rather than which tool to pick. (For context, I built this in Java on LangGraph4j — but my questions below are about the pattern itself, not the language.)
Why I’m Sharing This
I believe this addresses a common pain point for developers building complex agents: Context Window Management vs. Tool Availability. I’d love your thoughts:
-
Is there a recognized name for this forward/backward split in prompt injection? I keep wanting a shared vocabulary for it.
-
My chaining rules are forward-looking (“if the user wants attractions, check the weather first”) but sit in a backward trigger. Is that misplaced — and should forward logic live in a proactive skill or a
load_skill-style tool instead? -
What pitfalls should I watch for with backward-triggered loading — e.g. stale injected context across turns, or re-appending the same segment?
Thanks for reading! I’m eager to learn from your feedback.