Agent¶
The optional agent layer (pip install "agribound[agent]"). See
Agent layer.
agent ¶
The Agribound agent: a tool-use loop with a human confirmation gate.
:func:agent sends a natural-language request to an LLM together with the
typed tools of :mod:agribound.agent.tools, runs the tools the model calls,
and returns an :class:AgentResult. Its autonomy is deliberately low:
- the model can only propose a run (
propose_run); a human approves or denies the exact plan (hash-bound, single-use approval; :mod:agribound.agent.gate); - the gate enforces a hard execution limit (
max_executions, default 1); - the loop stops right after an execution attempt or a denial, without another model turn, so at most one plan runs per call and the model cannot re-run or re-tune anything; a follow-up run needs a new call by the human;
dry_run=Trueremovesexecute_planaltogether and leaves a plan YAML foragribound delineate --config.
Every model turn and tool call is recorded in a JSON transcript
(:mod:agribound.agent.session).
This module imports only the standard library at import time; the tools
(pydantic) and the backend SDK are imported when :func:agent runs.
STATUSES
module-attribute
¶
STATUSES = ('completed', 'executed', 'execution_failed', 'denied', 'refused', 'max_tokens', 'max_turns', 'error')
AgentResult
dataclass
¶
Outcome of one :func:agent session.
Attributes:
| Name | Type | Description |
|---|---|---|
status |
str
|
One of :data: |
final_text |
str
|
The most recent non-empty text written by the model in a complete
turn. Partial text of a refused or |
report |
str
|
Deterministic summary written by Agribound (not by the model): plans, gate decisions, execution results and the transcript path. |
plans |
list of dict
|
Every plan proposed in the session (:meth: |
plan_yaml_paths |
list of str
|
YAML files of the plans ( |
executions |
list of dict
|
Execution attempts with their summaries or errors. |
transcript_path |
str
|
The JSON transcript. |
session_id, workdir |
str
|
Session identifier and directory. |
error |
str or None
|
Error message for |
stop_details |
dict or None
|
Provider detail for |
Source code in agribound/agent/agent.py
agent ¶
agent(request: str, *, study_area: str | None = None, gee_project: str | None = None, workdir: str | Path | None = None, backend: Any = 'anthropic', model: str | None = None, base_url: str | None = None, confirm: Any = None, dry_run: bool = False, max_turns: int = 20, max_executions: int = 1, reference_boundaries: str | None = None, allow_network: bool = True) -> AgentResult
Plan (and, after human approval, run) an Agribound delineation from a request.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
str
|
Natural-language request, e.g. |
required |
study_area
|
str or None
|
Default study area for the tools (vector file, GEE asset ID,
|
None
|
gee_project
|
str or None
|
Earth Engine project used in plans and live availability checks. |
None
|
workdir
|
(str, Path or None)
|
Session directory (default |
None
|
backend
|
str or LLMBackend
|
|
'anthropic'
|
model
|
str or None
|
Model ID for a named backend (default |
None
|
base_url
|
str or None
|
Anthropic-compatible endpoint for a named backend (e.g. a local Ollama or vLLM server). |
None
|
confirm
|
(None, 'prompt', 'deny' or callable)
|
Confirmation callback of the gate. None: typed |
None
|
dry_run
|
bool
|
Disable execution: |
False
|
max_turns
|
int
|
Maximum number of model responses (default 20). |
20
|
max_executions
|
int
|
Execution limit of the confirmation gate (default 1). The loop stops
after the first execution attempt, so at most one plan runs per call
whatever the value; |
1
|
reference_boundaries
|
str or None
|
Default reference layer for resolvability/evaluation tools. |
None
|
allow_network
|
bool
|
Allow tools to contact Earth Engine, TESSERA, Source Cooperative or
the USGS NAIP Plus ImageServer. With False, the read-only tools skip
or refuse their network parts and |
True
|
Returns:
| Type | Description |
|---|---|
AgentResult
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
For invalid arguments. |
ImportError
|
If the backend's SDK or |
Source code in agribound/agent/agent.py
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 | |
Tools¶
tools ¶
Provider-neutral, typed tools of the Agribound agent.
Every tool is a plain function tool(context, validated_input) -> output
with a pydantic input model and a pydantic output model. The same
:class:ToolRegistry drives the local tool-use loop
(:mod:agribound.agent.agent) and the MCP server
(:mod:agribound.agent.mcp_server). This module imports neither
anthropic nor mcp.
Read-only tools
list_sources, list_engines, describe_study_area,
check_availability, estimate_resolvability,
recommend_configurations, evaluate_against_reference and
query_published_ftw. None of them runs the pipeline or modifies user
files. query_published_ftw writes the downloaded polygons into the
session work directory, and the network-backed checks may fill download
caches (published FTW tiles under <workdir>/ftw_cache, the TESSERA tile
manifest in the geotessera cache).
Gated tools
propose_run freezes a validated configuration into a
:class:~agribound.agent.plans.Plan (and writes its YAML); it never runs
anything. execute_plan runs :func:agribound.pipeline.delineate for a
plan only after the :class:~agribound.agent.gate.ConfirmationGate
approved that plan's exact hash. Before the reviewer is asked,
:func:preflight_execution refuses plans that cannot run in this session
(execution disabled, no executions left, changed inputs, or remote
services needed while network access is off).
Network access
With ToolContext.allow_network=False no tool contacts Earth Engine,
TESSERA, Source Cooperative or the USGS NAIP Plus ImageServer: the
read-only tools refuse (or skip) their network parts, and
execute_plan refuses every plan whose pipeline run would need one of
these services (:func:plan_network_services). Model weights are a
separate matter: engines download them from Hugging Face unless they are
already cached (agribound prefetch); this flag does not block that.
Expected failures raise :class:~agribound.agent.errors.AgentToolError.
ToolContext
dataclass
¶
State shared by the tools of one agent session or MCP server process.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
workdir
|
str or Path
|
Session directory. Plans, plan outputs, downloaded FTW polygons and
the shared cache ( |
required |
study_area
|
str or None
|
Default study area for tools called without one. |
None
|
gee_project
|
str or None
|
Default Earth Engine project (plans and live checks). |
None
|
gee_service_account_key
|
str or None
|
Service-account key injected into plans (never set by the model). |
None
|
reference_boundaries
|
str or None
|
Default reference layer for resolvability and evaluation tools. |
None
|
allow_network
|
bool
|
Whether tools may contact Earth Engine, TESSERA, Source Cooperative
or the USGS NAIP Plus ImageServer. False disables the live
availability checks, published FTW polygons and GEE-asset study areas
of the read-only tools, and makes |
True
|
execution_enabled
|
bool
|
Whether |
False
|
gate
|
ConfirmationGate or None
|
The confirmation gate (required for execution). |
None
|
embedding_cache_dir
|
str or None
|
Cache directory for the TESSERA manifest used by live checks. |
None
|
Source code in agribound/agent/tools.py
ToolRegistry ¶
Validating dispatcher over :data:TOOL_SPECS for one :class:ToolContext.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
context
|
ToolContext
|
Shared session state. |
required |
include_execute
|
bool or None
|
Offer |
None
|
Source code in agribound/agent/tools.py
2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 | |
definitions ¶
call ¶
Validate arguments and run tool name; never raises for tool failures.
Source code in agribound/agent/tools.py
preflight_execution ¶
Checks made before the reviewer is asked to approve plan.
None of them involves the reviewer, so a plan that cannot run is refused without asking for an approval that could not be used.
Raises:
| Type | Description |
|---|---|
ExecutionDisabledError
|
Execution is disabled (dry run) or no gate is configured. |
ExecutionLimitError
|
The gate has no executions left. |
PlanChangedError
|
The plan no longer matches its hash
(:func: |
NetworkDisabledError
|
Network access is off and the plan needs a remote service
(:func: |
Source code in agribound/agent/tools.py
plan_network_services ¶
Remote data services the pipeline contacts for config.
Judged from the configuration alone: caches are not inspected, so a plan is listed as needing a service even if, for example, its composite is already cached. Model-weight downloads (Hugging Face) are not listed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
AgriboundConfig
|
Validated configuration. |
required |
Returns:
| Type | Description |
|---|---|
list of str
|
One entry per service and stage, e.g. |
Source code in agribound/agent/tools.py
Plans and the confirmation gate¶
plans ¶
Run plans: frozen, hash-identified Agribound configurations proposed by the agent.
A :class:Plan is created by the propose_run tool. It stores the fully
validated :class:~agribound.config.AgriboundConfig as canonical JSON, a
fingerprint of the run's inputs, and a SHA-256 plan hash over both. The
confirmation gate (:mod:agribound.agent.gate) binds every approval to that
hash, and recomputes it immediately before execution, so an approval can
never authorise a different configuration, a different study-area geometry,
or a modified reference/raster file.
Input fingerprints
study_area: :func:agribound._cache.aoi_fingerprint(geometry-based for files,bbox:strings and WKT; the asset ID string for GEE assets).reference_boundariesandlocal_tif_path: absolute path, size and modification time (the file contents are not hashed).
Plan
dataclass
¶
A proposed Agribound run, identified by the hash of its content.
Instances are immutable; a changed configuration is a new plan with a new
plan_id and plan_hash and needs its own approval.
Attributes:
| Name | Type | Description |
|---|---|---|
plan_id |
str
|
|
config_json |
str
|
Canonical JSON of |
inputs_json |
str
|
Canonical JSON of :func: |
plan_hash |
str
|
:func: |
created_utc |
str
|
ISO-8601 creation time (UTC). |
rationale, limitations, alternatives |
The agent's explanation, shown to the reviewer. |
|
warnings |
tuple of str
|
Warnings generated by Agribound (not by the model). |
non_default_fields_json |
str
|
Canonical JSON of :func: |
estimated_cost_json |
str
|
Canonical JSON of the size/resource estimate. |
yaml_path |
str or None
|
Where the plan's configuration YAML was written. |
Source code in agribound/agent/plans.py
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 | |
inputs
property
¶
A fresh copy of the input fingerprints recorded at proposal time.
non_default_fields
property
¶
Fields that differ from the package defaults.
to_config ¶
Rebuild the validated :class:~agribound.config.AgriboundConfig.
current_hash ¶
Recompute the plan hash from the stored configuration and current inputs.
Differs from :attr:plan_hash when the stored configuration was
altered or when an input file or the study-area geometry changed.
Source code in agribound/agent/plans.py
stored_hash_is_consistent ¶
True if :attr:plan_hash matches the stored JSON (no input re-check).
config_yaml ¶
The configuration as YAML, in :class:AgriboundConfig field order.
Source code in agribound/agent/plans.py
to_dict ¶
JSON-serialisable view (used in tool results and the session transcript).
Source code in agribound/agent/plans.py
render ¶
Human-readable description shown by the confirmation gate.
The sections generated by Agribound (warnings, fields that differ from
the defaults, estimate) come first. The text written by the agent
(rationale, limitations, alternatives) follows, with every line
prefixed by " | ", so agent text cannot pass itself off as an
Agribound section. The full configuration YAML comes last. Control
characters in any value (terminal escape sequences, line breaks in
a single-line field, bidirectional overrides) are shown escaped
(:func:display_safe), so no value can move the cursor, erase or
reorder the screen, or add lines of its own.
Source code in agribound/agent/plans.py
gate ¶
Human confirmation gate for agent-proposed runs.
No plan is executed unless a :class:ConfirmationGate holds an unused
approval bound to that plan's hash:
- An approval is created either by the gate's callback (called from
:meth:
ConfirmationGate.request_approval) or explicitly with :meth:ConfirmationGate.record_approval(used by the MCP server after an elicitation, or by Python code that approves a plan it has inspected). - :meth:
ConfirmationGate.authorize_executionrecomputes the plan hash from the stored configuration and the current input files, requires a matching unused approval, marks it used (approvals are single-use) and counts the execution againstmax_executions(default 1 per session). When the hash check fails, the unused approvals of that hash are revoked, and :meth:ConfirmationGate.request_approvalalways asks the callback anew, so an approval that could not be used never authorises a later run. - :func:
check_plan_currentperforms the same hash check on its own; theexecute_plantools call it before asking the reviewer, so nobody is asked to approve a plan whose inputs already changed.
Callbacks
A callback receives the :class:~agribound.agent.plans.Plan and returns
True to approve. Ready-made callbacks:
- :func:
prompt_confirm-- prints the full resolved configuration and requires the reviewer to typeyes(anything else denies). - :func:
deny_all-- denies every plan (the default for non-interactive sessions).
:func:default_callback picks :func:prompt_confirm when standard input is
an interactive terminal and :func:deny_all otherwise.
ConfirmationGate ¶
Single-use, hash-bound approvals and a hard execution limit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
callback
|
callable or None
|
|
None
|
max_executions
|
int
|
Maximum number of plans this gate lets run (default 1). |
1
|
approver
|
str or None
|
Name recorded with callback approvals (default: the OS user name). |
None
|
Examples:
Source code in agribound/agent/gate.py
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 | |
check_limit ¶
Raise :class:ExecutionLimitError if no executions remain.
Source code in agribound/agent/gate.py
record_approval ¶
Record an approval obtained outside the callback (e.g. MCP elicitation).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
plan
|
Plan
|
The approved plan; the approval is bound to |
required |
approver
|
str
|
Who approved (free text, recorded in the transcript). |
required |
method
|
str
|
How the approval was obtained (recorded in the transcript). |
required |
Returns:
| Type | Description |
|---|---|
Approval
|
|
Source code in agribound/agent/gate.py
record_denial ¶
Record a denial (for the transcript).
Source code in agribound/agent/gate.py
request_approval ¶
Ask the callback to approve plan and return the new approval.
The callback is asked on every call. An earlier approval of the same
plan hash that was never used (for example because
:meth:authorize_execution refused it) is revoked first, so it can
never authorise a later run without a new answer from the reviewer.
Raises:
| Type | Description |
|---|---|
ExecutionLimitError
|
If no executions remain (the reviewer is not asked). |
ExecutionDeniedError
|
If the callback returns anything but True or raises. |
Source code in agribound/agent/gate.py
authorize_execution ¶
Consume the approval for plan immediately before it runs.
Recomputes the plan hash from the stored configuration and the current input fingerprints, then marks the matching approval used and counts the execution.
Raises:
| Type | Description |
|---|---|
PlanChangedError
|
If the recomputed hash differs from |
ExecutionLimitError
|
If no executions remain. |
ExecutionNotApprovedError
|
If there is no unused approval bound to the plan hash. |
Source code in agribound/agent/gate.py
to_dict ¶
Transcript view of the gate's state.
Source code in agribound/agent/gate.py
prompt_confirm ¶
prompt_confirm(plan: Any, *, input_fn: Callable[[str], str] | None = None, out: TextIO | None = None) -> bool
Show the full plan and approve only if the reviewer types yes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
plan
|
Plan
|
Plan to review. |
required |
input_fn
|
callable or None
|
Reads the answer; it is called with an empty prompt string because
the question is printed to out (default: the built-in
:func: |
None
|
out
|
text stream or None
|
Where the plan and the question are printed (default |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
True only for the exact answer |
Source code in agribound/agent/gate.py
deny_all ¶
check_plan_current ¶
Raise :class:PlanChangedError unless plan still matches its hash.
Recomputes the hash from the stored configuration and the current
input fingerprints (:meth:Plan.current_hash). An input that can no
longer be fingerprinted (for example a deleted study-area or reference
file) also raises :class:PlanChangedError.
Source code in agribound/agent/gate.py
Errors¶
errors ¶
Exceptions of the agent layer.
Every expected failure of an agent tool (invalid arguments, a missing file,
no network permission, a plan that was not approved) is an
:class:AgentToolError. The local tool-use loop returns its message to the
model as an is_error tool result, and the MCP server maps it to
mcp.server.mcpserver.exceptions.ToolError so that MCP hosts show the
message instead of a generic failure.
This module imports only the standard library.
AgentToolError ¶
GateError ¶
Bases: AgentToolError
Base class of confirmation-gate refusals.
ExecutionDisabledError ¶
ExecutionDeniedError ¶
ExecutionNotApprovedError ¶
ExecutionLimitError ¶
PlanChangedError ¶
UnknownPlanError ¶
Bases: AgentToolError
No plan with the requested ID exists in this session.
NetworkDisabledError ¶
Bases: AgentToolError
A tool (or a plan's pipeline run) needs a remote service while network access is off.
Session transcript¶
session ¶
Agent session transcript.
:class:AgentSession records one agent session as JSON
(<workdir>/agent_session_<session_id>.json): the request, backend and
model ID, package/SDK versions, every model turn (stop reason, text, tool
calls, token usage, duration), every tool call (validated arguments, error or
result summary, duration), the plans, the confirmation gate's approvals and
denials (who, how, when), the executions, and the final status. The file is
rewritten after every turn and once more when an approved plan starts to run
(with the approval and a "running" execution record), so an interrupted
session, including one that crashes during a long pipeline run, still leaves
a record.
AgentSession ¶
Collects and writes the transcript of one agent session.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
str
|
The user's natural-language request. |
required |
workdir
|
str or Path
|
Session directory; the transcript is written there. |
required |
session_id
|
str or None
|
Identifier (default :func: |
None
|
backend_info
|
dict or None
|
Backend description (name, model, base URL, request options). |
None
|
options
|
dict or None
|
Session options (dry run, limits, study area, ...). |
None
|
Source code in agribound/agent/session.py
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 | |
record_turn ¶
Record one model response (a :class:~agribound.agent.backends.base.ModelTurn).
Source code in agribound/agent/session.py
record_tool_call ¶
record_tool_call(*, turn_index: int, call_id: str, name: str, outcome: Any, duration_s: float) -> dict[str, Any]
Record one tool call and its :class:~agribound.agent.tools.ToolOutcome.
Source code in agribound/agent/session.py
record_plan ¶
Record a proposed plan.
A re-proposal of the same configuration has the same plan ID (the ID
hashes the configuration and inputs, not the agent's text). Its entry
is replaced by the latest proposal, which is the version the
confirmation gate shows to the reviewer, and n_proposals counts
how often it was proposed.
Source code in agribound/agent/session.py
record_execution ¶
Record an execution attempt, or update it.
An entry with the same "attempt" number is replaced (a
"running" record is written when the run starts and replaced by
the final "success"/"failed" record).
Source code in agribound/agent/session.py
set_gate ¶
finish ¶
finish(status: str, *, final_text: str = '', report: str = '', error: str | None = None, stop_details: dict[str, Any] | None = None) -> None
Set the final status and timing.
Source code in agribound/agent/session.py
usage_totals ¶
Token usage summed over all turns.
For a turn that reports per-attempt iterations (e.g. a declined
attempt followed by a fallback model), the iterations are summed,
because the top-level usage then covers only the final attempt;
otherwise the top-level usage is used.
Source code in agribound/agent/session.py
write ¶
Write the transcript atomically and return its path.
Source code in agribound/agent/session.py
MCP server¶
mcp_server ¶
MCP server exposing the Agribound agent tools (mcp >= 2.2).
agribound mcp serve starts an :class:mcp.server.mcpserver.MCPServer
named "agribound" over stdio (default) or Streamable HTTP. Any MCP host
(Claude Desktop/Code or a local-LLM MCP host) can then call the same typed
tools that the built-in agent loop uses (:mod:agribound.agent.tools).
Tools and annotations
- Read-only tools carry
ToolAnnotations(read_only_hint=True).query_published_ftwandpropose_runwrite files into the server's work directory (downloaded polygons, plan YAML), so they are annotatedread_only_hint=False, destructive_hint=False. propose_runis always registered; it never runs anything.execute_planis registered only when the server is started with--allow-execute. At mostmax_executions(default 1) plans run per server process.- Arguments are validated by the same pydantic models as in the local loop, including the rejection of unknown argument names (MCPServer's own argument model would silently drop them; the raw request arguments are checked).
Work directory
Default: $XDG_DATA_HOME/agribound/mcp or ~/.local/share/agribound/mcp
(:func:default_workdir), not a path relative to the working directory the
MCP host happens to start the server in. --workdir overrides it.
Human confirmation of execute_plan
confirm="elicit"(default): before anything runs, the server sends an MCP elicitation showing the full plan and asks the user to typeyes. Clients that did not declare the elicitation capability get an error explaining the alternative. MCP lets a client answer elicitations itself (for example an automated host), so the server cannot prove that a human answered; the answer and method are recorded with the approval.confirm="host": no elicitation; the server relies on the host's own per-tool approval prompt. Use it only with hosts that ask the user before every tool call. The approval is recorded with the method "host tool-approval prompt" and an approver the server cannot verify.
In both modes a plan that cannot run (unknown plan, no executions left,
changed inputs, or remote services needed while allow_network=False) is
refused before the user is asked (:func:agribound.agent.tools.preflight_execution).
Streamable HTTP
The streamable-http transport has no authentication: any process or user
that can reach the host and port can call the tools, and with
--allow-execute such a client can answer the execute_plan elicitation
itself. :func:serve therefore refuses (:func:check_http_exposure, before
the server is built) --allow-execute over streamable-http and a
--host that is not a loopback address, unless
allow_unauthenticated_http=True (--allow-unauthenticated-http), which
logs a WARNING. The default host is 127.0.0.1.
Expected failures are raised as ToolError so the model reads the reason.
For execute_plan every failure after the approval (the pipeline, the
gate's final checks, or an unexpected exception) is reported as a
ToolError with its type and message. For the other tools, an exception
that is not an :class:~agribound.agent.errors.AgentToolError is reported by
MCPServer as a generic "Error executing tool" and logged by the server.
While a tool runs, sys.stdout is redirected to sys.stderr; the mcp
stdio transport additionally points file descriptor 1 at stderr while
serving (best effort, mcp.server.stdio), so stray prints, including those
of native code, do not corrupt the protocol stream. Long runs send
report_progress notifications every 15 s.
build_server ¶
build_server(*, allow_execute: bool = False, confirm: Literal['elicit', 'host'] = 'elicit', workdir: str | Path | None = None, study_area: str | None = None, gee_project: str | None = None, reference_boundaries: str | None = None, allow_network: bool = True, max_executions: int = 1) -> Any
Create the Agribound MCP server.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
allow_execute
|
bool
|
Register |
False
|
confirm
|
('elicit', 'host')
|
How |
"elicit"
|
workdir
|
(str, Path or None)
|
Directory for plans, outputs, downloads and the cache (default
:func: |
None
|
study_area
|
str or None
|
Session defaults for the tools. |
None
|
gee_project
|
str or None
|
Session defaults for the tools. |
None
|
reference_boundaries
|
str or None
|
Session defaults for the tools. |
None
|
allow_network
|
bool
|
Allow tools to contact Earth Engine, TESSERA, Source Cooperative or
the USGS NAIP Plus ImageServer. With False, |
True
|
max_executions
|
int
|
Maximum number of plans executed by this server process. |
1
|
Returns:
| Type | Description |
|---|---|
MCPServer
|
The server; its tool context is available as |
Source code in agribound/agent/mcp_server.py
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 | |
default_workdir ¶
$XDG_DATA_HOME/agribound/mcp, else ~/.local/share/agribound/mcp.
Anthropic backend¶
anthropic_backend ¶
Anthropic Messages API backend (manual tool-use loop).
Requests
First-party API (no custom base URL): client.beta.messages.create with
model(default"claude-opus-5", overridable by argument or theAGRIBOUND_AGENT_MODELenvironment variable),max_tokens=16000,thinking={"type": "adaptive"},output_config={"effort": "high"},tool_choice={"type": "auto"},- the system prompt as one text block with
cache_control(ephemeral), - server-side refusal fallbacks:
betas=["server-side-fallback-2026-07-01"]andfallbacks="default"(both typed parameters ofclient.beta.messages.createin anthropic 1.8.0).
Custom base_url (e.g. Ollama >= 0.14 or vLLM Anthropic-compatible
/v1/messages endpoints): client.messages.create without betas,
fallbacks, cache_control or tool_choice (these servers do not all
support them; auto is the API default anyway). thinking and effort
are sent only when given explicitly. The options actually used are recorded
in :meth:AnthropicBackend.info and therefore in the session transcript.
No sampling parameters (temperature, top_p, top_k) are sent;
anthropic 1.x removed them from messages.create.
Responses
When a response contains fallback blocks (one per model that declined
mid-output), :meth:AnthropicBackend.to_turn returns only the tool_use
blocks after the last fallback block as tool calls, and
:meth:AnthropicBackend.assistant_message omits the thinking,
redacted_thinking and tool_use blocks (and unpaired server-tool
blocks or unknown block types) that precede that boundary when the turn is
echoed back (:func:echoable_content); a WARNING is logged whenever a block
is omitted. Text blocks, paired server-tool blocks, the fallback blocks
and everything after the boundary are echoed unchanged. usage.iterations
(per-attempt usage) is recorded, and a fallback_message entry marks a
response served by a fallback model.
The two sources available when this was written disagree about pre-boundary thinking blocks:
- the claude-api skill (bundled with Claude Code 2.1.282, read 2026-09-27;
shared/model-migration.md, "Echoing fallback turns back") says to omitthinking,redacted_thinkingandtool_useblocks before the finalfallbackblock; this module follows it; - the
BetaFallbackBlockParamdocstring of anthropic 1.8.0 says to echo the assistant turn back verbatim with thefallbackblock in its original position, and that the server validates thinking runs on both sides of it.
Both agree that the declined model's tool_use blocks are not run and not
echoed without results. The rule has not been checked against the live API.
The same skill page says that for non-streaming requests (the only kind this
backend sends) a mid-output decline omits the declined partial entirely, so
the pre-boundary branch is not expected to be reached in practice.
AnthropicBackend ¶
:class:~agribound.agent.backends.base.LLMBackend for the Anthropic Messages API.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
str or None
|
Model ID. Default: |
None
|
base_url
|
str or None
|
Custom endpoint (Anthropic-compatible local server). None uses the
SDK's resolution ( |
None
|
api_key
|
str or None
|
API key; None lets the SDK resolve credentials. Local servers need
a placeholder key (e.g. |
None
|
client
|
object or None
|
Pre-built client (anything with |
None
|
max_tokens
|
int
|
Output token limit per response (default 16000). |
DEFAULT_MAX_TOKENS
|
effort
|
Any
|
|
AUTO
|
thinking
|
Any
|
|
AUTO
|
fallbacks
|
Any
|
|
AUTO
|
cache_system_prompt
|
Any
|
|
AUTO
|
timeout
|
float | None
|
Passed to :class: |
None
|
max_retries
|
float | None
|
Passed to :class: |
None
|
Source code in agribound/agent/backends/anthropic_backend.py
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 | |
request_kwargs ¶
request_kwargs(*, system: str, tools: Sequence[ToolDefinition], messages: Sequence[Any]) -> dict[str, Any]
Keyword arguments of the messages.create call (exposed for tests).
Source code in agribound/agent/backends/anthropic_backend.py
to_turn
staticmethod
¶
Convert an SDK Message/BetaMessage into a :class:ModelTurn.
text joins every text block. tool_calls holds only the
tool_use blocks after the last fallback block (a declined
model's tool calls are not run; see the module docstring).