Skip to main content
Graph of Agentic Thoughts

Counterfactual explainability for agentic planners
Your agent picked a plan. GoAT tells you what else it considered, what it scored, and why it lost.

Python 3.10+ License Status

Quickstart · Configuration · Memory · Development · Troubleshooting · Publication


Why GoAT 🐐

An ordinary agent emits one plan and executes it. Nothing records what else it could have done, or why it didn't. Failures are un-debuggable and decisions are un-auditable.

GoAT wraps the planning step. For a request it judges complex, it generates multiple plans, scores them with an LLM-as-judge critic, executes only the winner, and emits a directed graph whose factual plan is the winning plan and whose counterfactual plan(s) are the rejected alternatives — each carrying the critic's rejection reason on a counterfactual edge. It then renders that graph as two natural-language explanations, one for developers and one for end users.

Without GoAT With GoAT
Single plan generated Multiple diverse plans considered
No explanation of why Clear explanation of plan selection
Black-box decision making Transparent counterfactual reasoning
Hard to debug failures Full graph of alternatives

Package

One distribution, one import surface:

import langgraph_goat
from langgraph_goat import GoatMiddleware, GoatNode, GoatLogger

Quickstart

GoAT offers two integration surfaces:

GoatMiddleware GoatNode
Target API create_agent your own StateGraph
Setup drop-in, one line wrap your planner node
Control limited full
Best for standard LangChain agents custom graph architectures
Example examples/agent_high_level.py examples/agent_low_level.py

GoAT emits plans as tool calls; it does not execute them. create_agent handles execution for you. A custom StateGraph needs a ToolNode downstream of the wrapped planner.

How it works

Graph of Agentic Thoughts
K resolution — exact precedence
  1. force_K set → K = clamp(force_K, 1, max_K), confidence 1.0, classifier skipped.
  2. Otherwise a classifier is present → its suggested_K, capped at max_K (the reason string records the cap).
  3. Otherwise → K = clamp(default_K, 1, max_K).

Director.run raises ValueError if K>1 with no critic configured.

Reading the output

Both integration paths write the same keys. All are absent when K=1 — always use .get().

Key Type Contents
goat_graph GoATGraph factual path + ghost nodes + edges
goat_winning_plan_id str the selected plan's id
goat_complexity PlanComplexity needs_multiple_plans, suggested_K, reason, confidence
goat_explanation Explanation developer_explanation, user_explanation, raw_output
goat_director_result DirectorResult graph, winning_plan, all_plans, complexity, explanation
plan list[dict] serialized winning plan — node wrapper only, back-compatible
if goat_graph = response.get("goat_graph"):
    print(f"GoAT Output: {goat_graph}")

plan step shape: {"tool", "args", "justification", "step_id", "expected_output"}.

Artifacts

Each GoAT run writes to {output_dir}/{graph_id}/:

File Contents Parameter
graph_{graph_id}.gv.svg GoAT graph created by GraphViz render_goat_graph
tele_{graph_id}.json every LLM call — prompts, completions, token counts, latency, pipeline errors render_goat_tele
tele_{graph_id}.html visualised report over the same data render_goat_tele
trace_{graph_id}.svg PlantUML sequence diagram of the run render_goat_trace

GoAT Log

from langgraph_goat import GoatLogger
GoatLogger("agent_name", level=logging.DEBUG)

Configuration

There is no config file and no GoAT runtime environment variable. Everything goes through the two config dataclasses.

GoatMiddlewareConfig

Field Type Default Purpose
classifier PlanComplexityClassifier | None None custom complexity classifier; default LLMClassifier built from the agent's model
critic PlanCritic | None None custom critic; default LLMJudgeCritic
explanation_generator ExplanationGenerator | None None custom explainer; default LLMExplanationGenerator
graph_store GraphStore | None None graph is persisted in NetworkX Store if not provided
store Any | None None LangGraph store for memory retrieval
default_K int 3 K when the classifier is absent or uncertain
max_K int 3 hard cap on K
force_K int | None None bypasses the classifier entirely
planning_prompt_template str | None None custom plan-generation prompt
output_dir str "./output" telemetry and report root directory
callbacks list | None None LangChain callback handlers for token/latency tracking

K is not a hyperparameter to max out. Forcing K=5 on every query defeats adaptive explainability and multiplies cost.

Static values

Not configurable without supplying your own components:

Component temperature max_tokens
Classifier 0.3 1024
Plan Generation 0.7 2024
Critic 0.2 2500
Explanation 0.4 1500

memory namespaces [("preferences",), ("memory",), ("context",)].

Memory

GoAT never writes — it only reads. Persistence stays your application's job.

from langgraph.store.memory import InMemoryStore

store = InMemoryStore()
store.put(("preferences",), "user_123", {"likes": ["hiking", "reading"], "budget": "moderate"})

config = GoatMiddlewareConfig(store=store)      # or GoatNodeConfig(store=store)

Retrieval searches exactly three namespaces — ("preferences",), ("memory",), ("context",). Memories stored anywhere else are invisible. Each hit is formatted [<namespace>] <value>. store.search() is preferred; stores without it fall back to store.get(namespace, <raw user input>).

Postgres — long-term store plus checkpointer

AsyncPostgresStore takes a connection pool, not a connection string. The checkpointer (per-thread conversation state) is orthogonal to the store (cross-thread long-term memory GoAT reads).

from psycopg_pool import AsyncConnectionPool
from psycopg.rows import dict_row
from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver
from langgraph.store.postgres import AsyncPostgresStore

async with AsyncConnectionPool(conninfo=DSN, max_size=20,
                               kwargs={"row_factory": dict_row}) as pool:
    checkpointer = AsyncPostgresSaver(pool)
    await checkpointer.setup()

    store = AsyncPostgresStore(pool)
    await store.setup()
    await store.aput(("preferences",), "user_123", {"text": "prefers detailed, scientific style"})

    agent = create_agent(model=model, tools=ALL_TOOLS,
                         middleware=[GoatMiddleware(config)],
                         store=store, checkpointer=checkpointer)

Semantic search needs a pgvector embedding index; values must carry the indexed field ("text" by convention) for asearch() to match. Without pgvector, retrieval degrades to exact key lookups. Working reference: examples/agent_high_level_postgres.py.

MarkdownStore — hand-editable memory

examples/res/markdown_store.py is a LangGraph-compatible store backed by a single markdown file — ## headers are namespaces, ### headers are keys. It re-reads the file on every call, so you can edit memory.md mid-session and the agent sees it immediately. Search is case-insensitive substring matching, not semantic.

store = MarkdownStore("memory.md")

Extending GoAT

Every component is a Protocol. Match the shape and pass it in the config.

Protocol Method Reference
PlanComplexityClassifier classify(context, trace_context=None) -> PlanComplexity docs/custom_classifier_guide.md
PlanGenerator generate_K(context, K, trace_context=None) -> list[Plan]
PlanCritic score(plans, context, trace_context=None) -> CriticVerdict docs/custom_critic_guide.md
ExplanationGenerator generate(graph, trace_context=None) -> Explanation docs/custom_explanation_generator_guide.md
GraphStore save(path, graph, trace_context=None) -> str · load(path, graph_id, ...) · list_recent(limit=20, ...) docs/custom_graph_store_guide.md
LLMClient complete(messages, model=None, temperature=0.7, max_tokens=2000, trace_context=None, **kwargs) -> str examples/customllm.py

Development

Build

Installation:

Development mode

pip install -e .

Regular install:

pip install .

After Making Code Changes

  1. Edit code in langgraph_goat/ (adapter) or langgraph_goat/_core/ (engine)

  2. If using editable install (pip install -e .):

    • Changes are reflected immediately, no rebuild needed
    • Just restart your Python process/tests
  3. If using regular install or need to rebuild:

    pip install -e . --force-reinstall --no-deps
    

Clean Build

Remove build artifacts if encountering issues:

Remove-Item -Recurse -Force dist, build, *.egg-info -ErrorAction SilentlyContinue
pip install -e . --force-reinstall

Troubleshooting

Symptom Likely cause Fix
No goat_* fields K was 1, or the pipeline fell back grep logs for Falling back; check "_goat_error" in result on the wrapper path
K is always 1 classifier too conservative, or the model ignores the classification prompt force_K=3 to confirm the rest works, then supply a custom classifier or a stronger model
memory_snippets always empty no store, or namespace mismatch store under ("preferences",), ("memory",), or ("context",) — nothing else is searched
No telemetry files collector not initialized, or write failed os.makedirs(output_dir, exist_ok=True); look for Failed to write telemetry output:
Plans not diverse single model call, low temperature custom planning_prompt_template that names distinct strategies per plan

Publication

Moushumi Mahato and Javaid Nabi. 2026. Graph of Agentic Thoughts: Bridging Agentic Reasoning and Explainability through Graphs. In Proceedings of the 13th ACM IKDD International Conference on Data Science (CODS '25). Association for Computing Machinery, New York, NY, USA, 243–252. https://doi.org/10.1145/3799830.3799871

Metadata

Release files for langgraph-goat 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for langgraph-goat 1.0.0
File Size Uploaded
langgraph_goat-1.0.0.tar.gz 81.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for langgraph-goat 1.0.0
File Interpreter ABI Platform
langgraph_goat-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 168.3 kB

Release files / langgraph_goat-1.0.0.tar.gz

Download URL langgraph_goat-1.0.0.tar.gz
Size 81.4 kB
Tags Source
SHA-256 checksum
How to use checksums
5beadd43b38174a724c749e54ac4dc3ca363783166a7891595e31beb7bbcb3ca
BLAKE2b-256 checksum
How to use checksums
bc4e221d62e3db016b1f7969a63c32625646b9898621141ff20a030df669e578
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.

Transparency log

Release files / langgraph_goat-1.0.0-py3-none-any.whl

Download URL langgraph_goat-1.0.0-py3-none-any.whl
Size 86.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a34a0beaa5577ef5081e315511e6d79916b3b9e1f104f09e4d48ca2ce11ed3dd
BLAKE2b-256 checksum
How to use checksums
71672e5d022b7f80cd852f989ae87323db4b6ff9df3ddbc4396253bd37e8de65
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page