An agentic spatial workflow lets a language model choose which typed geospatial functions to call and in what order, while a validator inspects that plan before any data is touched, an independent recomputation re-derives the result, and a completeness check confirms every part of the question was answered.

| What it is | A language model choosing which typed geospatial functions to call, wrapped in checks that decide what is allowed to reach a person |
| What you build | Six layers: a typed tool registry, a planner, a validator, an executor, two verification passes and a human gate. The model owns one of them |
| Libraries | shapely, SciPy, NumPy, Ollama, and the Python standard library for the schema |
| Worked on | One national LiDAR tile of eight million points, 48 planning calls from two small local models, a 16 prompt test set |
| Level | Intermediate. Assumes Python and basic geospatial handling. |
So what happens the first time a 4B model plans a clearance survey across eight million points and gets the bookkeeping wrong?
The guide runs in the order you would actually build this, opening with the architecture and closing with the person who has to sign the deliverable. Here is the path:
- What an Agentic Spatial Workflow Is
- Why the Toyota Andon Cord Belongs in Your Pipeline
- What a Language Model Is Genuinely Good At Here
- How to Design Tools an Agent Can Call Safely
- The Validator That Runs Before Any Data Is Touched
- Why Verification Has to Recompute the Answer
- How to Measure Whether Your Agentic Spatial Workflow Works
- Five Agentic Spatial Workflow Mistakes and How to Fix Them
- Why Your Derived Input Is Not What You Named It
- Where the Human Gate Belongs in an Agent Pipeline
- Where to Go Next With Agentic Spatial Workflows
- Build Your First Agentic Spatial Workflow This Month
- Frequently Asked Questions About Agentic Spatial Workflows
Writing the tool stopped being the difficult part a while ago. Ask for a shapely predicate, a KD-tree lookup, a schema built out of a docstring, and working code arrives in seconds.
What no model does for you is pick which question is worth putting to which data, or draw the line between an answer you ship and one you decline.

Here is the concrete version of the problem. A question lands that takes a colleague half a day: which trees threaten the power line, which manholes sit outside the surveyed corridor, which rooms in the scan have no fire exit within twenty metres. The data exists and the tools exist, and the half day goes on the clicking in between.
So can a language model close that gap? Partly, and the interesting word is partly. It reads a sentence and picks which functions to call. It cannot be trusted to decide what a metre is, and if you let it try, you find out in the field rather than in the console.
Designing Refusal Into 3D Workflows
Routing is cheap now. Deciding what a metre means in your survey, what counts as evidence for a claim, what a deliverable is entitled to be called, all of that is as scarce as it ever was, and it stays with the person whose name sits on the report.
Which is why the skill worth building over the next few years is not prompting. It is designing the refusal. Anyone can stand up a pipeline that always answers. The systems still trusted in five years will be the ones that know which questions they are entitled to answer, say so in plain words when they are not, and hand the rest to a person with the reason attached.
Every number below was printed by a script running on a real national LiDAR tile, with two small local models doing the planning.
What you will learn in this article
- What an agentic spatial workflow is, and how it differs from a script with a chat box bolted on
- Which decisions a language model handles well, and which ones have to stay in your code
- How to write typed tools whose docstrings become the schema the model reads
- What two verification layers have to recompute before an answer reaches a person, and where both go blind
- How to measure whether your agent works, using four numbers instead of a vibe
What an Agentic Spatial Workflow Is
An agentic spatial workflow has four moving parts: a registry of typed functions that do real geospatial work, a model that reads a question and emits which functions to call with which arguments, a checker that reads that list before anything touches your data, and a second computation that re-derives the answer independently.
That is the whole design. No autonomous loop rewriting its own code, no swarm of specialists arguing in a group chat. The point is not the cleverness of the orchestration, it is that a question in plain language now reaches a deterministic pipeline through a boundary you control.

A script hard-codes the order of operations, so a new question needs a new script. An agentic workflow keeps the operations fixed and chooses the order per question, so one registry answers a family of questions. The catch is that the chooser is unreliable, and the design has to assume so from the first line.
Worth being precise about what “unreliable” costs here, because it is smaller than it sounds. The planner is a router. A router that picks the wrong wire is caught by the thing on the other end of the wire, provided you built that thing. Everything in this guide after the registry exists to be that thing.
🦥 Geeky Note: Planning is cheap and the geometry is not. A local qwen3:4b took a median 3.06 seconds per plan here and llama3.2:3b took 2.85, on CPU with no batching, while the geometry those plans trigger runs in single-digit seconds over eight million points. The model is never your bottleneck, which removes the usual excuse for skipping the checks around it.
Why the Toyota Andon Cord Belongs in Your Pipeline
Toyota’s assembly lines carry a cord above every station, any worker can pull it, and the line stops. The part worth borrowing is that pulling it counts as the system working rather than as a failure, because a defect caught at the station costs minutes while one caught at the dealership costs a recall.
Read that as an engineering rule. Build the stop into the line, put it in reach of whoever sees the problem first, and make stopping cheaper than continuing.

Map it onto an agent and the four cords name themselves. The schema refuses an argument the function never declared. The validator refuses a plan that queries canopies nobody clustered. The verifier refuses a claim a second computation disagrees with. And a person refuses an answer that is arithmetically fine and still wrong on the ground.
Which cord you build first is a real decision, not a matter of taste, so here is the same set priced by what it catches and what it costs.
| Where the line stops | What it reads | The fault it catches | What it costs you to add |
|---|---|---|---|
| The tool schema | The argument names and types the function declared | An argument the function never had, and a string where a float belongs | Nothing beyond introspection, since the schema is generated from the signature |
| The validator | The plan, before any coordinate is touched | An invented tool, a wrong type, a query that runs before the thing it queries exists | Around forty lines, and it turned 5 clean plans out of 48 into 44 your executor can run |
| The correctness layer | The answer, recomputed down an unrelated code path | A unit slip, a stale cache, a buffer nobody applied, which is three of four injected faults | 0.4 seconds per run on this tile, plus a second implementation you have to keep honest |
| The completeness layer | The executor trace, against the asks in the sentence | Half a question nobody answered, which stopped 2 of 4 probes on its own | A mapping from asks to tools, and honesty about the asks you cannot serve |
| A person | The claim, the evidence and the number that triggered the flag | A premise both code paths inherited, such as a reference line derived twenty metres off | One screen of review, on the middle of three exits |
Four cords, four stations, and none of them is the model. If you take one thing away from this guide, take that.
What a Language Model Is Genuinely Good At Here
A language model is very good at mapping fuzzy phrasing onto a small set of names, and that talent is close to the whole of what it contributes here. Ask for “trees near the line”, “vegetation within five metres of the corridor” or “what is going to hit the wire”, and a decent model routes all three to the same function, where writing that mapping by hand means maintaining a synonym list forever.
It is loosely good at ordering too. Given ten tools and a two-part question it usually gets the shape of the sequence right, even when it fumbles the bookkeeping inside that shape.

What it is bad at is anything whose right answer depends on geometry. A benchmark of GIS tool use across 117 atomic tools and 53 spatial analysis tasks had to invent a parameter execution accuracy metric precisely because models pick plausible tools and then fill their arguments wrong, and work on autonomous GIS agent frameworks lands in the same place, wrapping the model in generation, execution and self-correction rather than trusting one pass.
So the design rule writes itself. Let the model choose names and order, and keep every quantity, unit, predicate and threshold in code a test can pin down.
🦚 Florent’s Note: Across 48 planning calls a 4B model produced 5 plans with zero validator faults. Five. Grade the model on that and you throw it out. The same model, wrapped in the architecture described here, did the right thing end to end on 41 of those 48 runs. Ten percent of the plans were clean and 85 percent of the runs were correct, and the distance between those two figures is the entire engineering job.
How to Design Tools an Agent Can Call Safely
A tool an agent can call safely is a promise written down in types. The model reads a name, a one-line description and an argument list, then commits to them, so every ambiguity left in that promise becomes a wrong argument eventually. A longer prompt has never once been the fix.
Five properties do the heavy lifting. Name the function after the verb and the object, never after the module. Type every argument. Put the unit inside the identifier so distance_m cannot be read as feet. Carry the EPSG code in every payload, because the OGC Simple Feature Access standard that defines WKT deliberately leaves the coordinate reference system out of the geometry string. And return an error dictionary instead of raising, so an out-of-range request becomes a message the planner reads.

The trick that keeps all five honest is refusing to write the schema twice. Read the signature, the type hints and the docstring, and build the JSON the model sees from those, so a renamed argument reaches the planner on the next import. Here it is, using the standard library plus shapely.
import inspect
import re
import typing
JSON_TYPES = {int: "integer", float: "number", str: "string", bool: "boolean"}
def arg_docs(doc):
"""Pull the per-argument descriptions out of a Google-style docstring."""
found, current = {}, None
for line in (doc or "").split("\n"):
line = line.strip()
if line.lower().startswith("args:"):
current = ""
continue
if line.lower().startswith("returns:"):
break
if current is None or not line:
continue
match = re.match(r"^(\w+):\s*(.*)$", line)
if match:
current = match.group(1)
found[current] = match.group(2)
elif current:
found[current] += " " + line # the wrapped-line branch
return found
def describe(fn):
"""Build the JSON schema a planner reads, straight from the function."""
hints = typing.get_type_hints(fn)
docs = arg_docs(fn.__doc__)
summary = (fn.__doc__ or "").strip().split("\n")[0]
props, required = {}, []
for name, param in inspect.signature(fn).parameters.items():
props[name] = {"type": JSON_TYPES.get(hints.get(name, str), "string"),
"description": docs.get(name, "")}
if param.default is inspect.Parameter.empty:
required.append(name)
else:
props[name]["default"] = param.default
return {"name": fn.__name__, "description": summary,
"parameters": {"type": "object", "properties": props,
"required": required}}
def canopies_within_distance(distance_m: float, min_height_m: float = 2.0) -> dict:
"""Find canopies whose footprint lies within a distance of the corridor.
Args:
distance_m: Search distance from the right-of-way edge, in metres.
min_height_m: Skip canopies shorter than this, in metres.
Returns:
The matching canopy ids with their distances, plus the EPSG code used.
"""
if distance_m <= 0:
return {"error": "distance_m has to be positive, in metres"}
matches = []
for canopy in WORLD.canopies:
if canopy["height_m"] < min_height_m:
continue
gap = canopy["footprint"].distance(WORLD.corridor["row"])
if gap <= distance_m:
matches.append({"canopy_id": canopy["id"], "gap_m": round(gap, 2)})
return {"canopy_ids": [m["canopy_id"] for m in matches], "matches": matches,
"epsg": WORLD.crs["epsg"], "distance_m": distance_m}
The line that earns its keep is the continuation branch in arg_docs, which appends a wrapped line onto the argument before it. Without it a two-line description silently loses its tail, and if the lost words are “in metres”, your planner starts guessing units with no warning.
One more property is worth writing down even though it costs nothing: the error dictionary is part of the contract. A tool that raises hands your planner a stack trace it cannot read, while a tool returning {"error": "distance_m has to be positive, in metres"} hands it a sentence it can act on. Refusals are only cheap when they are legible.

🪐 System Thinking Note: This is a type system argument, and it transfers well past agents. Wherever two components exchange meaning through a wire, the boundary is worth more than either side: a REST contract, a database schema, a function signature. An unreliable caller with a strict interface beats a careful caller with a loose one, every time.
The Validator That Runs Before Any Data Is Touched
The validator sits between the model’s answer and your data, and it decides yes or no without touching a single coordinate to do it. Four questions cover the bulk of what it has to ask. Is this a tool the registry actually holds? Is every argument key present in that function’s signature? Does every value survive its declared type constructor, turning "5" into 5.0 and "five" into a refusal? And does the plan build its objects before it queries them?

Give every fault a name rather than returning a bare false. A list like ["missing_prerequisite_cluster", "wrong_argument_type"] tells you what to build next, and a boolean tells you nothing. Once faults have names you can grade them, which is where small models stop looking hopeless.
A plan whose only problem is a skipped prerequisite can be repaired by your executor, because it knows the dependency and prepends the missing call. A plan that invents a tool cannot be repaired by anyone. Sort faults into those two piles and a planner that cleared strict validity on 5 of 48 attempts turns into 44 of 48 your executor can actually run, because 39 of the failures were bookkeeping rather than invention.
That ratio is the thing the fault names bought that nothing else could. Thirty-nine bookkeeping faults against a handful of inventions says the model understood the domain and lost track of the sequence, which is a scheduling problem with a known fix. The opposite ratio would have said something much worse.

🦥 Geeky Note: Forty-one of the 48 qwen3:4b plans queried canopies that no earlier step had ever clustered. The ordering is spelled out for the model in ordinary English, and it made no measurable difference. A 4B model will not hold a two-step ordering rule while it is also choosing argument values, so encode the ordering in a graph your executor walks and stop paying for it in prompt tokens.
Why Verification Has to Recompute the Answer
Verification earns its name only when it recomputes the answer down a second code path and compares the two, because syntax is what the validator reads and meaning is what nobody has checked yet. A plan can clear all four validator questions and still land somewhere wrong. That gap is where the money goes, and nearly every agent demo leaves it out.
So do not ask the plan whether it was right. Compute the answer a second time down a different code path and compare. If the agent path builds convex hulls and calls a shapely predicate, the check path walks raw points against a densified boundary with a KD-tree. Same question, different libraries, different failure modes.

Two cheaper checks ride alongside it. Confirm the working CRS is projected with metric axis units, because a five-metre buffer in a geographic frame is five degrees of nonsense. And confirm the distance in the operator’s sentence matches the distance the plan executed, which catches the classic case of somebody asking in feet while the pipeline answers in metres. Here is the whole layer.
import numpy as np
import shapely
from scipy.spatial import cKDTree
def verify_claim(claimed_ids, asked_m, executed_m, crs, corridor, canopies, xy):
"""Recompute a proximity claim independently and refuse it on disagreement.
Args:
claimed_ids: The canopy ids the executed plan reported.
asked_m: The distance parsed out of the operator's question, in metres.
executed_m: The distance the plan actually passed to the tool.
crs: The working coordinate reference system description.
corridor: The right-of-way geometry the claim was measured against.
canopies: The canopy records, each holding its member point indices.
xy: The Nx2 array of planar coordinates the canopies index into.
Returns:
A tuple of the verdict and a sentence naming the reason for it.
"""
if not crs.get("is_projected") or crs.get("axis_unit") != "metre":
return False, "the working CRS is not projected in metres"
if abs(asked_m - executed_m) > 1e-6:
return False, f"asked for {asked_m} m, the plan ran {executed_m} m"
edge = corridor.exterior
n_samples = max(int(edge.length / 0.5), 200) # half a metre between samples
samples = np.array([edge.interpolate(t, normalized=True).coords[0]
for t in np.linspace(0.0, 1.0, n_samples)])
tree = cKDTree(samples)
recomputed = set()
for canopy in canopies:
points = xy[canopy["member_index"]]
gap = float(tree.query(points, k=1)[0].min())
if shapely.contains_xy(corridor, points[:, 0], points[:, 1]).any():
gap = 0.0 # inside the polygon is zero, not positive
if gap <= asked_m:
recomputed.add(canopy["id"])
claimed = set(claimed_ids)
if recomputed != claimed:
return False, (f"recomputation disagrees, missing {sorted(recomputed - claimed)}, "
f"extra {sorted(claimed - recomputed)}")
return True, f"{len(recomputed)} canopies confirmed on an independent path"
How finely you sample that boundary is what separates an honest check from a theatrical one. Space the boundary samples too far apart and the KD-tree ends up reporting how far you are from a straight shortcut across the curve, which pushes every result outward. At half a metre against a five-metre threshold that error stays near one percent, and the contains_xy guard covers the single situation a nearest-edge lookup mishandles on its own, an object sitting inside the polygon, where the gap to the boundary is positive while the real answer is zero. On the clean run this layer confirmed 39 objects against 39 in 0.4 seconds.
Now test the tester, because a verifier that has never refused anything is decoration. Editing an answer list after the fact is not how software actually breaks, so swap a deliberately faulty function into the registry instead and send the same validated plan through it. Four faults went in that way. A unit slip measuring feet where the sentence said metres was refused. A cache key that had dropped the distance argument, serving a ten-metre answer to a five-metre question, was refused. A forgotten buffer that measured to the bare centreline instead of to the corridor polygon was refused, and loudly. Three out of four, and the fourth is the one worth your attention.

The fourth fault shifted the derived corridor twenty metres sideways before either path ran. Both paths then measured against the same wrong line, agreed with each other perfectly, and the layer shipped it. A second computation can only argue with work done after the two paths part company. Anything settled before that fork is a premise they both swallow whole. So find your fork and write it down, because it marks the outer edge of everything this kind of checking is able to notice.
A second hole sits beside that one, and it has nothing to do with arithmetic. Take the mission sentence and bolt a budget question onto the end of it. A pipeline carrying only a correctness layer will plan the geometry, run it, recompute it, nod at itself and ship. The costing half slips through untouched. Regrading operates on claims, and where nobody made a claim there is nothing for a second computation to argue with.
So run a completeness layer beside the correctness one. Break the sentence into asks, map each ask to the tools that would serve it, and read the executor’s trace to confirm every ask produced an output. Where an ask maps to no tool at all, the empty mapping is the design working: instead of a table with one column quietly absent, the operator gets told in words which part of the sentence this system cannot price. Run four probes through both layers and two get stopped by the completeness half by itself, each of them an answer the arithmetic had already blessed.
Read the trace and not the plan, by the way. A ranking tool that appears in the plan and then throws on execution has delivered nothing at all, though a checker looking only at the plan will cheerfully record it as done.
🌱 Growing Note: Add a shape check next, not another distance one. Scatter twenty probe points across the interior of a flagged polygon, ask the corridor buffer about each probe, and refuse when the probes and the reported verdict disagree. Twenty lines, and it starts catching outlines that sit at the correct range while being the wrong shape entirely.
How to Measure Whether Your Agentic Spatial Workflow Works
Measuring an agentic spatial workflow takes four numbers rather than one, and any single figure hides which layer is carrying the result. Ask someone whether their agent works and you get an anecdote. Ask for the four and you get an engineering conversation.
First, strict plan validity, the fraction of plans with zero validator faults. Report it, then stop treating it as the headline, because it punishes a model for bookkeeping your executor can fix. Second, the runnable-after-repair rate, which prices your dependency graph. Third, end-to-end correctness with both verification layers switched on: did the system answer the whole question when the question was answerable, and refuse when it was not. Fourth, the score of a no-model control, a keyword parser on the same registry and the same two layers.
Over 48 planning calls those came out at 5, 44, 41 and, for the keyword control on its own sixteen-prompt set, 15 of 16. Quote every one of them with the number of trials attached, because a rate off two dozen runs carries an interval wide enough to hold two very different stories at once.

That fourth number is the uncomfortable one. Forty lines of keyword matching, wrapped in the same registry and the same two verification layers, behaved correctly on 15 of 16 prompts and on all eight of the original core set, while the 4B model managed 41 of 48. On a narrow question the parser wins on reliability and wins by orders of magnitude on cost.
So pick your planner by the shape of the question space rather than by what is fashionable, and the three options price out like this.
| Planner | Measured behaviour on the same registry | What it costs to run | Pick it when |
|---|---|---|---|
| Forty lines of keyword matching | 15 of 16 prompts handled correctly, including all eight of the original core set | No model, no GPU, no wait | The question space stays inside a list of words you can write down today |
| qwen3:4b, local, on CPU | 41 of 48 runs correct end to end, against 5 of 48 plans that were strictly valid | A median 3.06 seconds per plan | Somebody phrases a request you never anticipated, which is the only thing you are buying |
| llama3.2:3b, local, on CPU | The same architecture around a smaller planner | A median 2.85 seconds per plan | The 4B model is what makes your loop feel slow, and you can afford to re-measure the 48 |

Actually, let me put that more precisely, because it reads like an argument against agents and it is not. The parser wins while the question space stays inside the list of words you thought of. The model earns its place the moment somebody phrases a request you never anticipated, and not one prompt earlier.

Build the test set before the agent, and build more of it than feels necessary. Sixteen prompts is a workable floor: five your registry can genuinely answer, and eleven traps built from wrong units, wrong coordinate systems, unknown objects, missing distances, data layers you do not have, and at least one that asks a perfectly answerable spatial question and then attaches a second half nothing you own can serve. A high refusal rate there is not a defect, it is the specification.
Eight prompts felt like plenty until the interval was written down beside the rate. Eight prompts run three times each put strict validity at 2 successes in 24, and the ninety-five percent interval around that stretches from 2 percent to 26, which is wide enough to mean “hardly ever” and “one time in four” simultaneously. Doubling the set moved the point estimate by two points and cut the interval width by a quarter, and that is all extra prompts were ever going to buy. What they actually bought was two defects nothing in the original eight could reach: a fixed percentage tolerance in the correctness layer that refuses a correct answer when the set is small, and the entire missing completeness layer.

🦚 Florent’s Note: The number I keep coming back to is the 25 percent. On 177 canopy objects the flat test and the real three-dimensional one disagreed on a quarter of the verdicts, 6 flagged that were harmless and 3 missed that were not. None of that is the agent’s doing, it was already in the workflow the agent automated. A model on top would simply industrialise the error. And that 177 is itself a setting rather than a fact, which is why it now travels with its clustering parameters wherever it goes.
Five Agentic Spatial Workflow Mistakes and How to Fix Them
Five mistakes account for the distance between an agentic spatial workflow that demos well and one that survives a second site, and each of them shows up as a number rather than as an error message.
Letting the Plan Skip a Prerequisite
A plan that queries canopies nobody clustered is the fault you will meet more than any other, and it showed up in 41 of the 48 qwen3:4b plans here. The fix belongs in the executor rather than in the prompt: hold the dependencies as a graph, walk it, and prepend the missing call. Spelling the ordering out in ordinary English inside the prompt made no measurable difference at this model size, so stop paying for it in tokens.
Grading the Planner Instead of the System
Strict plan validity punishes a model for bookkeeping your own code can repair, and reporting it alone throws away a working system. On this build the planner cleared the validator without a fault on 5 of 48 attempts while the system around it behaved correctly on 41 of 48. Report both, and let the gap between them tell you how much of the reliability is architecture rather than model.
Trusting a Single Verification Layer
One layer of checking always fails in one direction. Recomputation only argues about work done after its two code paths part, which is why the fourth injected fault, a reference line derived twenty metres off course, sailed through with both paths agreeing perfectly. Completeness looks the other way and holds no opinion on correctness at all, though it stopped 2 of 4 probes nothing else caught. Run both, then write down what the union still misses.
Shipping Without a No-Model Control
An agent that never gets compared against a parser is an unfalsifiable claim about your model. Forty lines of keyword matching on the same registry and the same two layers behaved correctly on 15 of 16 prompts, against 41 of 48 for the 4B planner. If your model cannot clear that line on your own prompt set, you are paying 3.06 seconds and a dependency for nothing.
Quoting an Object Count Without Its Settings
An absolute count off a clustering run is a parameter choice wearing the costume of a measurement. Vary the DBSCAN neighbourhood from 0.8 to 2.5 metres against a minimum sample count of 3 to 8 and this tile yields anywhere from 90 to 798 objects, and even the defensible band runs 152 to 226 around the 177 the pipeline ships, a spread of 42 percent. Quote ratios with confidence and absolute counts with their settings attached.
Why Your Derived Input Is Not What You Named It
A derived input carries the name you were looking for rather than the thing you measured, and that mismatch is the failure no validator in your pipeline will ever see. The reference strip in this build was derived from the data by a scoring sweep, and it got called a utility corridor because that was the question in the ticket.
Score the same detector over nine windows tiling the same tile and the working window tops out at 0.739 while the best window holding no corridor at all reaches 0.368, so the separation is a factor of two rather than the factor of ten that would let you trust it blindly, and one window that genuinely does carry the strip scores 0.149. The classification layer cannot break the tie either, because a tarmac carriageway is filed as class 2, ground, exactly like the grass next to it.

What settled it was intensity, profiled across the strip rather than averaged inside it. There is a trough of 592 near the centreline, verge peaks of 1,079 either side, and an 8 metre dark band sitting at 0.55 of the brightness of the verge beside it. That pattern has a physical reading. These sensors fire in the near infrared, a band where a chlorophyll-rich verge throws light back hard and a dry bituminous surface swallows it, so a narrow dark lane running between two bright shoulders is the return signature of something people drive on.
Nothing in the numbers breaks. The strip sits precisely where the sweep put it and every distance measured to it is honest, so what has to change is the word on the deliverable rather than any figure inside it. The deliverable now names a derived linear clearing consistent with a surfaced track, and it stops claiming to be a utility corridor. Name your derived inputs by what you measured, not by what you were looking for.

One habit closes the loop on all of it. Print the trace: every call, its arguments, the first line of its result, the wall-clock cost. Six months on somebody asks why object 82 was on the cut list, and the trace is the only thing that answers.
🪐 System Thinking Note: This is the two-key rule from safety engineering. You do not make one operator more careful, you require two independent actions before an irreversible one. A validator and an independent recomputation are two keys on different code paths, which is why they catch different faults. If your second check shares a library with your first you have one key painted to look like two, and if it shares an input you have one key regardless of how the code was written.
Where the Human Gate Belongs in an Agent Pipeline
The human gate belongs on exactly one of three exits, and sending everything through it is the wrong answer, because a reviewer handed every result approves by reflex within a fortnight. Every serious pipeline ends up with a person in it. The question is where.
Route by what the pipeline already knows. Both paths agree, every ask got served, the units check out and every value sits inside the tested range, ship it. The run leaned on a derived input, or a value sits close to a threshold, send it to a person with the reason attached. The paths disagree, or part of the question got no answer, or the question is outside what the registry can answer, refuse and say so.

Make the review cheap. A reviewer should see the claim, the evidence and the number that triggered the flag, in that order, on one screen. If they have to open a GIS and reproduce the query to form an opinion, you moved the half day rather than removed it.
Here is the part that decides why the gate exists at all rather than just where it sits. Your two automated layers fail in opposite directions. Recomputation only inspects what happens after its two paths part, so it will never question the reference line they both start from. The completeness layer looks the other way entirely: it spots a part of the sentence nobody answered, and it holds no opinion on whether the parts that were answered are right. Stack them and you cover strictly more than either alone, which is the entire reason to run both.
Then look at what the union still misses, and you find one hole sitting exactly where the two layers share an input. Add a third algorithm and the hole does not move, because the fault lives in a premise every algorithm inherits. Closing it needs a second source rather than a second computation: a survey line, an aerial image, a cadastral record, somebody who has driven down the thing. That is the cord, and this is the station it hangs above.

🌱 Growing Note: Log every refusal with its reason and you get a dataset for free. After a few hundred runs the distribution tells you what to build: a pile of unit refusals means you need a unit parser, a pile of out-of-scope refusals names the tool you are missing. It is the cheapest product feedback you will ever collect, and hardly anybody keeps it.
Where to Go Next With Agentic Spatial Workflows
Agentic spatial workflows sit on top of ordinary spatial engineering, and that layer underneath does not get easier because a model is calling it. If your clustering is fragile, an agent calls fragile clustering faster. Build the pipeline first, then put the agent on top of something you already trust.
Test that trust with a sweep before you automate anything on top of it. Vary the DBSCAN neighbourhood from 0.8 to 2.5 metres against a minimum sample count of 3 to 8, and the object count on this tile runs from 90 to 798. Narrow it to the settings a careful person would defend on this point density and the range is still 152 to 226, which puts a 42 percent spread around the 177 the pipeline ships. Cross-check with a method that shares no code, a marker-controlled watershed on the canopy height model, and you get 423 crowns instead, because it counts trees while the clustering counts work items.
Both counts are defensible, and the distance between them is a direct price tag on how much of your total is an artefact of the technique you picked. The 30 metre length at which a long hedgerow gets cut into separate objects is not a justified threshold either, it is how much hedge one crew clears in a session, and saying so out loud costs nothing. So the honest phrasing is never “the tile holds 177 objects”, it is “at these parameters and that split, this tile yields 177 work items”, and every downstream number inherits that qualifier.

For the geometry, processing LiDAR point clouds in Python covers reading and structuring, and 3D point cloud segmentation and clustering in Python covers the object extraction that turns points into things a tool can name. For the semantic layer, open-vocabulary 3D semantics in Python and semantic spatial AI and scene graphs show how a scene becomes a structure worth querying, and 3D spatial AI in Python maps how the pieces fit.
On the agent side, read the ReAct paper for the interleaving of reasoning and acting, Toolformer for how models learn when to call an API, and the GISClaw work on geospatial agents for where the field is pushing now. Ollama is the shortest path to a local planner, and its JSON output mode removes a whole class of parsing failure before you start counting the interesting ones.
One layer sits between the geometry and the registry above, and it decides how much a plain sentence can ever reach. I took it apart in Build 3D scene graphs for spatial AI and LLMs from a point cloud on Medium, where a scan becomes a graph of objects and relations a model can query without ever touching a coordinate, which is the same boundary this guide draws around tools. My other write-ups live at medium.com/@florentpoux.
Build Your First Agentic Spatial Workflow This Month
Your first agentic spatial workflow should start smaller than you think. Find the request that lands on somebody’s desk every Monday, and list the handful of operations they carry out to close it. Turn each one into a typed function with a real docstring, and you have a week of work you owed the codebase anyway.
Then write the verification pass, before you touch a model at all. The moment you can recompute an answer independently you hold the part that makes the rest safe.
Point a local model at it last, and measure. Bad numbers are still useful numbers, because the fault list names the guard you are missing, and a guard is a thing you can write. Mine started at 5 clean plans out of 48 and the system around them was right 41 times, which is the only pair of numbers worth putting in front of a client.

For a first real 3D Python task to run tonight, the free mission hands you genuine scans and a roadmap within an evening, which is the fluency everything above quietly assumes. When you want the spatial engineering and the orchestration built together rather than bolted to each other afterwards, the 3D AI Program is where I teach it end to end.
Toyota put the cord where the worker stood, not where the manager sat. So which question goes first in your own pipeline, and where are you going to hang the cord above it?
Frequently Asked Questions About Agentic Spatial Workflows
What Is an Agentic Spatial Workflow?
A geospatial or 3D pipeline where a language model picks which typed functions run and in what order, while a validator inspects that plan before any data is touched, an independent recomputation re-derives the result, and a completeness check confirms nothing in the question went unanswered. Every measurement, unit and predicate stays in ordinary code, which is what keeps a wrong plan from becoming a wrong answer.
Can a Small Local Language Model Plan a GIS Workflow Reliably?
Not on its own. A local qwen3:4b cleared the validator without a single fault on 5 attempts out of 48, and the dominant defect was a skipped prerequisite, which showed up in 41 of those 48 plans. Wrap it in a typed registry, a validator, an executor that repairs skipped dependencies, an independent recomputation and a completeness check, and the same model behaved correctly end to end on 41 of the 48 runs.
How Do You Stop an AI Agent Hallucinating a Spatial Answer?
Two layers, not one. Derive the result a second time through unrelated code and throw it out on any disagreement, then independently confirm that no part of the sentence went unanswered. Two cheap checks ride alongside: that the coordinate reference system is projected in metric units, and that the number the operator typed is the number the tool received. Here the correctness layer confirmed 39 canopies against 39 in 0.4 seconds and refused three of four faults injected into the executor. It missed the fourth, a reference line derived twenty metres off course, because both paths measured against the same wrong line.
Do I Need a GPU to Run an Agentic Spatial Workflow?
No. The geospatial work is CPU-bound and finishes in seconds, from reading eight million points through to the ranked output. A GPU only accelerates the optional language model, and a 3B or 4B model plans acceptably on CPU at roughly three seconds per plan. You can skip the model entirely: a keyword planner with the same registry and the same two checking layers answered 15 of 16 test prompts correctly.
How Should I Measure Whether My Spatial Agent Works?
Report four numbers rather than one. Strict plan validity, plans with zero validator faults. Runnable-after-repair, plans your executor could fix by prepending a skipped dependency. End-to-end correctness, whether the system answered the whole question when it could and refused when it could not. And the score of a no-model control planner on the same test set. Here those were 5 of 48, 44 of 48, 41 of 48 and 15 of 16, and only all four together tell you what the model is contributing.
What Should You Install to Build This?
The stack stays small and none of it is exotic. Ollama runs the local planner and gives you JSON output mode, shapely supplies the predicates and the buffers, SciPy contributes the KD-tree the verification path walks, NumPy carries the coordinate arrays, and the inspect and typing modules in the standard library generate the schema from your own function signatures.