Goal-Oriented Action Planning framework for LangGraph with constraint optimization.
Project description
Goal-oriented planning for LangGraph agents.
LangGOAP turns a goal and a set of LangChain tools into a compiled StateGraph that plans before it acts, replans on failure, and stays deterministic by default. The planner is classical A* with optional OR-Tools CP-SAT refinement; the runtime is plain LangGraph, so checkpointing, streaming, interrupt(), and LangSmith all just work.
pip install -U langgoap
[!TIP] For developing, debugging, and deploying agents, see LangSmith. LangGOAP ships a
LangSmithTracerthat maps plan / replan / goal-achieved events to LangSmith runs alongside LangGraph's automatic node-level tracing.
Why LangGOAP?
LangGOAP provides a planning layer for any agent that has to choose tools in a particular order under hard constraints:
- Deterministic planning — A classical A* search over your action set produces a checked plan before any tool runs; the same inputs always yield the same plan.
- Constraint optimization built in — Hard resource caps, soft objectives, temporal
IntervalVarscheduling, and multi-plan Pareto selection via OR-Tools CP-SAT. - The plan is a
StateGraph— Every plan compiles to a real LangGraph graph, so checkpointers, stores, streaming,interrupt(), and LangSmith all just work. - Replans automatically — When an action fails, the world drifts, or a sensor invalidates a precondition, the executor blacklists the offender and the planner picks a new path without any routing code.
- LLM where it earns its keep — Natural-language goals are parsed once by
GoalInterpreter; the loop itself stays symbolic. No ReAct, no agentic reasoning between tool calls.
In Plain English...
You hand LangGOAP a goal in plain English and a bag of tools, and an LLM reads the goal exactly once to turn it into a symbolic target — that's the only place a model gets to make decisions. From there, a classical A* search picks the shortest sequence of tool calls that reaches the goal, and because the search is deterministic, the same goal and the same tools always produce the same plan. If you've declared resource caps, scheduling windows, or objectives to optimize, those are handed to OR-Tools CP-SAT to refine the plan against real constraints, all without extra wiring. The plan then compiles down to an ordinary LangGraph StateGraph, so checkpointing, streaming, interrupt(), LangSmith tracing, and everything else in the LangGraph ecosystem work the way you'd expect. When execution hits the real world and something breaks — a tool errors out, a precondition no longer holds, an external sensor disagrees — the executor blacklists the offending action and asks the planner for a new path, so recovery happens automatically rather than through hand-written routing code.
[!TIP] See
examples/screencast/research_agent/for a head-to-head comparison ofcreate_react_agent, a hand-wiredStateGraph, and LangGOAP — same brief, same tools, real OpenAI + Tavily costs, a revoked API key as the climax.
Quickstart
The snippet below wraps four LangChain tools and asks LangGOAP to publish an article. The LLM parses the natural-language goal exactly once into a symbolic target like {"published": True}, and from there A* takes over. The preconditions and effects dictionaries describe how each tool changes the world: a writer can only run once have_brief is true, and research_topic is what makes have_brief true in the first place. Two writers compete to satisfy the have_draft precondition — write_article_fast at cost=1.0 and write_article_premium at cost=5.0 — and A* picks the cheaper one. If the cheap writer fails at execution time, the executor blacklists it and the planner re-derives a fresh plan through the premium writer without any routing code on the caller's side. The costs={...} mapping is what makes that trade-off explicit; omitted tools default to cost=1.0, and the same numbers feed directly into the CSP layer when you want hard resource budgets like cost_usd or tokens — see examples/screencast/research_agent/ for that. Finally, result_keys plumbs each tool's return value into world_state under a chosen key, so research_topic's output lands at world_state["brief"] where the next tool's brief argument can pick it up — the initial world_state only needs to seed the first tool's argument (topic).
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langgoap import create_goap_agent
# A small counter so the demo can simulate the cheap writer flaking out
# on its first N invocations. Flip `fail_fast_n_times` to 1 to see the
# planner replan through the premium writer.
def make_writers(fail_fast_n_times: int = 0):
state = {"fast_calls": 0}
@tool
def write_article_fast(brief: str) -> str:
"""Quickly draft an article from a brief. Cheaper, occasionally flaky."""
state["fast_calls"] += 1
if state["fast_calls"] <= fail_fast_n_times:
raise RuntimeError(f"upstream rate limit (attempt {state['fast_calls']})")
return f"Fast draft: {brief}"
@tool
def write_article_premium(brief: str) -> str:
"""Premium-quality article. Higher cost, always reliable."""
return f"Premium draft: {brief}"
return write_article_fast, write_article_premium
@tool
def research_topic(topic: str) -> str:
"""Produce a short research brief for a topic."""
return f"Brief on {topic}"
@tool
def publish_article(draft: str) -> str:
"""Publish an article draft."""
return f"Published: {draft}"
write_article_fast, write_article_premium = make_writers(fail_fast_n_times=0)
agent = create_goap_agent(
tools=[research_topic, write_article_fast, write_article_premium, publish_article],
goal="Publish an article about GOAP for LangGraph",
llm=ChatOpenAI(model="gpt-4o-mini", temperature=0),
# Preconditions/effects keep the planner honest — never LLM-inferred.
preconditions={
"write_article_fast": {"have_brief": True},
"write_article_premium": {"have_brief": True},
"publish_article": {"have_draft": True},
},
effects={
"research_topic": {"have_brief": True},
"write_article_fast": {"have_draft": True},
"write_article_premium": {"have_draft": True},
"publish_article": {"published": True},
},
# A* minimizes total cost. Omitted tools default to cost=1.0.
costs={
"research_topic": 1.0,
"write_article_fast": 1.0,
"write_article_premium": 5.0,
"publish_article": 1.0,
},
# Wire each tool's return value into the next tool's input.
result_keys={
"research_topic": "brief",
"write_article_fast": "draft",
"write_article_premium": "draft",
},
)
result = agent.invoke(
{
"world_state": {"topic": "GOAP for LangGraph"},
"goal": agent.goap_goal,
}
)
agent is a compiled LangGraph graph. Use it with streaming,
checkpointers, interrupt(), or any LangGraph feature. The
LangGraph cycle that create_goap_agent compiles to is small and
fixed — every plan flows through the same planner → executor → observer loop:
Scenario 1 — Happy path (cheap writer wins on cost)
A* sees two paths to have_draft: True and picks the cheaper one:
status: 'goal_achieved'
replan_count: 0
blacklisted_actions: []
plan.action_names: ['research_topic', 'write_article_fast', 'publish_article']
plan.total_cost: 3.0
execution_history:
1. [ok ] research_topic
2. [ok ] write_article_fast
3. [ok ] publish_article
world_state (relevant keys): {'topic': 'GOAP for LangGraph', 'brief': 'Brief on GOAP for LangGraph', 'draft': 'Fast draft: Brief on GOAP for LangGraph'}
Scenario 2 — Cheap writer flakes, planner replans through premium
Set fail_fast_n_times=1 and run again. write_article_fast raises on its first call, the executor blacklists it, and the observer hands control back to the planner. A* re-derives a new plan from the current world state (have_brief is already True because research_topic succeeded), so the remaining work is just the premium writer plus publish:
status: 'goal_achieved'
replan_count: 1
blacklisted_actions: ['write_article_fast']
plan.action_names: ['write_article_premium', 'publish_article']
plan.total_cost: 6.0
execution_history:
1. [ok ] research_topic
2. [FAIL] write_article_fast (upstream rate limit (attempt 1))
3. [ok ] write_article_premium
4. [ok ] publish_article
world_state (relevant keys): {'topic': 'GOAP for LangGraph', 'brief': 'Brief on GOAP for LangGraph', 'draft': 'Premium draft: Brief on GOAP for LangGraph'}
Three ways to use it
LangGOAP ships three on-ramps so you can adopt as much or as little as you need without rewriting your action definitions.
create_goap_agent— Natural-language goal plus LangChain tools. The shortest path. Used inexamples/tutorials/deep_research_agent.ipynb.goapify_tool/GoapGraph— Hand-authoredActionSpecobjects with explicit preconditions, effects, costs, and validators. The workhorse API used by every tutorial. Tier 1 primer:examples/tutorials/directory_handler.ipynb.GoapSubgraph/add_goap_subgraph— Drop a GOAP loop into an existingStateGraphas a sealed node. Useful when GOAP is one reasoning mode among many. Quickstart:examples/basics/goap_subgraph.ipynb.
Features
Planning
- Cost-aware planner — finds the cheapest sequence of tool calls that reaches your goal, using a classical A* search over the action set. You supply per-action costs, optional effect validators that double-check what each tool actually changed, and per-action retry budgets.
- Constraint-aware refinement — when the goal carries hard caps (budgets, time windows) or objectives to minimize/maximize, the candidate plan is handed to OR-Tools CP-SAT, which refines or replaces it to satisfy them.
- Pluggable strategies — A* is the default, but the
PlanningStrategyProtocol lets you swap in Monte-Carlo Tree Search (MCTSStrategy, better for stochastic worlds), constraint-first solving (CSPRefinementStrategy), utility-based ranking (UtilityStrategy), interruptible "best plan so far" search (AnytimePlanningStrategy), or in-place repair after execution failures (RepairStrategy). - Automatic strategy selection —
StrategyRouterinspects the problem (hard constraints, stochasticity, branching factor) and dispatches to the right strategy. Routing is a pure function of the problem, so the same inputs always pick the same strategy. - Expected-value vs. sampled dynamics — a
TransitionModelseparates declared effects (what a tool says it changes) from sampled effects (what actually happens). The planner reasons about expected outcomes while MCTS rollouts and the graph runtime see the real sampled world.
Constraints and scheduling
- Plan scoring — rank candidate plans by a single number (
SimpleScore), by feasibility-first hard/soft tradeoffs (HardSoftScore— hard violations make a plan infeasible, soft scores rank the survivors), or by weighted priority levels (BendableScore). All three compare lexicographically and share a consistent penalize/reward sign convention. - Declarative constraints — a fluent
ConstraintBuilderAPI for resource caps, soft objectives, and weighted penalties, e.g.for_each_action().where(...).sum_resource("gpu_hours").bounded(max=budget). No solver code required. - Temporal scheduling — when actions have durations and deadlines, LangGOAP solves a scheduling problem alongside the plan: every action becomes a CP-SAT
IntervalVar, precedence comes from the dependency graph, and the solver minimizes makespan. The resulting schedule renders as a Gantt chart fromCSPMetadata.schedule.
LangGraph-native execution
- The plan is a
StateGraph—GoapGraphcompiles a real LangGraph graph with planner / executor / observer nodes. Each node has sync and async variants so tracer hooks fire from both.invoke()and.ainvoke(). - Replanning memory —
StoreExecutionHistorypersists each step's outcome in any LangGraphBaseStore(InMemoryStore,AsyncPostgresStore, your own). Keyed by tool name, so the planner can avoid actions that already failed in this run. No vector embeddings required. - Multi-goal decomposition —
MultiGoalsequences a list of subgoals into a single executable plan, insequentialorder orany-of mode (succeed when any subgoal is satisfied). - Plan visualization — render any plan as a Mermaid diagram (
render_mermaid), Gantt chart (render_mermaid_gantt,render_ascii_gantt), GraphViz DOT graph (render_dot), or ASCII tree (render_ascii);visualizeis the top-level dispatcher. Pure Python — no binary dependencies for Mermaid or ASCII output.
Reliability and recovery
- Per-action retry and idempotency —
ActionQosdeclares the retry policy and idempotency hints for each action (FIRE_ONCEfor actions that must never repeat,can_rerunfor safe retries,read_onlyfor actions that don't mutate the world). - Human-in-the-loop approval —
require_human_approvalpauses execution at a chosen action and asks for input via LangGraph'sinterrupt(). Pass a PydanticBaseModelto collect a typed form (validated on resume) orTruefor a plain approve/deny gate. - Stuck handlers — when planning fails to find a path,
MulticastStuckHandlerruns an ordered chain of recovery handlers that can patch the world state, swap in a relaxed goal, or escalate to a human. The first handler that returnsREPLANwins. - Early-termination policies — stop a run that's getting too expensive or running too long: cap by dollars (
MaxCostPolicy), wall-clock time (MaxWallClockPolicy), LLM calls (MaxLLMCallsPolicy), tokens (MaxTokensPolicy), or actions executed (MaxActionsPolicy). Combine them withFirstOfPolicy(any cap trips) orAllOfPolicy(all caps must trip).
Observability
- Pluggable tracing —
PlanningTraceris a Protocol with sync and async hooks (on_plan_complete,on_action_retry,on_strategy_chosen, …).NullTracer,LoggingTracer,MultiTracer, andLangSmithTracership in-tree; custom tracers (OpenTelemetry, Prometheus, …) are ordinary classes that implement the protocol. Tracer exceptions never propagate into the planner. - Live cost accounting —
CostAccumulator(withDEFAULT_COST_PER_1K_TOKENSfor common models) tracks LLM token usage and dollar cost in real time, plumbed into world state soMaxCostPolicyand thecost_bounded_research_agenttutorial can enforce hard budgets mid-run.
Checkpointing
- Resume from anywhere — pause a run (because of an
interrupt(), a crash, or a deliberate stop) and resume it later from the saved checkpoint. All three LangGraph backends are supported and tested: in-memory (MemorySaver), Postgres (AsyncPostgresSaver), and Redis (RedisSaver/AsyncRedisSaver). A customormsgpackserializer round-trips LangGOAP's frozen dataclasses,MappingProxyType,frozenset,timedelta, andtuplecorrectly. Install withpip install langgoap[checkpoint-postgres]orpip install langgoap[checkpoint-redis].
Natural-language goals
- Describe goals in plain English —
GoalInterpreterparses a natural-language request into a structuredGoalSpecusing any LangChain chat model that supports structured output.GoapGraph.invoke_nl(request, llm=...)(and its async twinainvoke_nl()) accept the request as a plain string, so callers don't need to constructGoalSpecby hand.
CLI and deployment
langgoapCLI — inspect and run plans from the terminal:plan(search and print a plan),actions(list discovered actions),explain(explain why an action was or wasn't picked),visualize(render a plan), anddeploy-init(scaffold a deployment). Loads actions, goals, and world state frommodule:variablereferences so existing Python code can stay where it is.- LangGraph deployment scaffold —
scaffold_deployment(andlanggoap deploy-init) generates a completelanggraph dev-ready directory in one command. Everylanggraphdeployment automatically serves an/mcpendpoint, so a LangGOAP graph becomes an MCP tool for any compatible client with no extra wiring.
DeepAgents and tool interop
- Use a LangGOAP graph as a LangChain tool —
create_goap_toolwraps aGoapGraphas a LangChainStructuredToolthat a higher-level agent can call. - Use a LangGOAP graph as a Deep Agents sub-agent —
create_goap_subagentreturns aCompiledSubAgent-compatible dict for Deep Agents. - Both wrappers run the request through
GoalInterpreterfirst, so the calling agent only needs to send a natural-language string.
Examples
The examples/ directory holds three flavours of runnable
documentation.
examples/basics/— short primers that each exercise a single mechanic: quickstart, CLI, plan visualization, natural-language goals, tracing, termination policies, stuck handlers, typed-form HITL, action QoS, MCTS vs A* on stochastic domains.examples/tutorials/— end-to-end walkthroughs in three tiers. Tier 1 introduces GOAP on toy domains; Tier 2 covers constraint-optimization workflows; Tier 3 showcases the full stack on substantial problems (deep_research_agent,flexible_job_shop,supply_chain_disruption_mediator,code_review_agent_mcp_deployment).examples/screencast/— the flagship YouTube companion lives atexamples/screencast/research_agent/: four agents (create_react_agentbaseline, hand-wiredStateGraph, LangGOAP, LangGOAP-under-disruption) on the same brief with real OpenAI + Tavily costs and a Tavily-key revocation as the climax. Three hermetic case studies (incident, supply chain, travel) sit alongside it for vertical-specific walk-throughs.
Start with
examples/screencast/research_agent/
to see GOAP replace a routing graph and survive a runtime disruption
in one sitting, or
examples/tutorials/directory_handler.ipynb
if you are new to GOAP and want a smaller starting point.
LangGraph ecosystem
LangGOAP integrates with the rest of the LangChain stack:
- LangGraph — the runtime substrate. Every LangGOAP plan compiles to a real
StateGraphand works with streaming, checkpointing, andinterrupt(). - LangSmith —
LangSmithTraceremits GOAP plan / replan / goal-achieved events to LangSmith alongside LangGraph's automatic node-level traces. - LangGraph deployment —
langgoap deploy-initscaffolds alanggraph dev-ready directory; the generated deployment serves an/mcpendpoint so a LangGOAP graph is callable from any MCP client (Claude Desktop, Cursor, …). - Deep Agents —
create_goap_toolandcreate_goap_subagentembed a LangGOAP graph as a tool or subagent inside a Deep Agents harness.
Documentation
examples/screencast/research_agent/– Flagship walkthrough: ReAct vs. hand-wiredStateGraphvs. LangGOAP on the same brief, with measured OpenAI + Tavily costs.examples/tutorials/– End-to-end notebooks across three tiers (toy domains, constraint-optimization, full-stack).examples/basics/– Short primers, one mechanic per notebook (CLI, visualization, NL goals, tracing, termination policies, stuck handlers, typed-form HITL, MCTS vs A*).langgoap/__init__.py– Authoritative list of public symbols. Anything not re-exported from the top-level package is internal and subject to change.- Changelog – Release notes for every public version.
Contributing
uv sync
make check # full lint + test suite
uv run pytest tests/integration/test_flexible_job_shop.py -vv
uv run pytest -m api # requires OPENAI_API_KEY / ANTHROPIC_API_KEY
Acknowledgements
LangGOAP is inspired by GOAP (Jeff Orkin / F.E.A.R.) and Embabel, which first applied GOAP planning to agentic LLM workflows. The Score hierarchy and fluent ConstraintBuilder are adapted from OptaPlanner; the CSP pipeline is built on OR-Tools CP-SAT; A* correctness was checked against GOApy; and the action/effect model draws on unified-planning. LangGOAP is built on LangGraph by Integrallis Software, but can be used wherever LangChain tools are available.
License
MIT. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file langgoap-0.1.2.tar.gz.
File metadata
- Download URL: langgoap-0.1.2.tar.gz
- Upload date:
- Size: 1.8 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d7378522d8426a82af5a67233a12d14ebde9caeed9ef7fe19108103dfbec2744
|
|
| MD5 |
e0365690fb6fc668e097315db8271693
|
|
| BLAKE2b-256 |
5a433d63e5803f58edc763ceffcd77e39a8f8227e7f7471f2b263a1e5b0a3e93
|
File details
Details for the file langgoap-0.1.2-py3-none-any.whl.
File metadata
- Download URL: langgoap-0.1.2-py3-none-any.whl
- Upload date:
- Size: 213.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4f4940ece1a482ac088b6db3cc9bfa1e7148cc7c87a9959aab5c5af2bbd9f2f3
|
|
| MD5 |
de47974691920294acc7e5f8851d16fe
|
|
| BLAKE2b-256 |
90e9d8190d45afe83d17bfb57fff194baf612e88ef03e82182e95a1e6d5934a3
|