Repository navigation
Threads tasks and Stop
This page comes from
site/content/architecture/threading.mdin the repository and is regenerated on every merge tomain. To correct it, edit that file; changes made here are overwritten.
QGIS objects (QgsVectorLayer, QgsFeature, QgsProject, widgets) may only be touched on the GUI (main) thread. An LLM turn, though, spends most of its time waiting on the network. Cartogen AI therefore splits the work:
| Thread | Runs |
|---|---|
| GUI thread | The dock, every ordinary tool, layer creation, project edits. |
QgsTask worker |
The whole agent turn (CartogenAi.run), model calls, and the network phase of fetch tools. Started by core/services/task_runner.py, so the UI stays live while the model thinks. |
| Isolated process | Model-written Python from execute_pyqgis_script (see Security overview). |
The agent loop is on a worker, but most tools need QGIS objects. ToolDispatcher (tool_dispatcher.py) bridges them: it emits a signal with a BlockingQueuedConnection, the GUI thread runs the tool in a slot, and the worker waits for the result. The result container is passed as a plain Python object argument, not a list: PyQt converts list signal arguments to a QVariantList for queued delivery and the slot would then mutate a copy, so the caller always saw an empty result (a bug found live and fixed; the comment in the file says so).
Two sets in agent_orchestrator.py skip or split that bridge. The test tests/test_processing_cost_guards.py fails if any tool that makes network calls is in neither set, because a network call on the GUI thread freezes QGIS.
-
NETWORK_ONLY_TOOLS(11): only HTTP or local file/CPU work, neverqgis.coreobjects (search_web,fetch_osm_features,search_hdx_datasets,search_stac_satellite_imagery,geocode_and_enrich,geocode_batch,generate_chart,aggregate_data, the PDF and Word table extractors,fetch_fts_funding_data). They run directly on the worker. -
TWO_PHASE_TOOLS(15): the network part runs on the worker and the layer-building part on the GUI thread (fetch_usgs_earthquakes,fetch_ucdp_conflict_events,find_incident_reports,fetch_gdacs_disaster_alerts,fetch_nasa_eonet_events,fetch_nasa_active_fires,fetch_geoboundaries,fetch_hdx_admin_boundaries,fetch_building_footprints,fetch_worldpop_population,ingest_osm_features,add_layer_from_pathand others). The pure parsers are separate functions, so they can be tested without QGIS.
Three mechanisms, chosen by what the work touches.
tools/_background_processing.py. run_algorithm runs an algorithm in a QgsProcessingAlgRunnerTask while the calling thread spins a nested Qt event loop, so the window repaints and Stop works, yet the call still returns the algorithm's result to the tool. It was built after a live measurement: routing graphs over a national road network took 292 to 572 seconds per call with QGIS "Not Responding" and no way to stop.
-
Only native C++ algorithms (
native:) are run this way. Python-implemented algorithms (gdal:*,qgis:*,script:*) stay on the GUI thread. They crashed the CI job when moved to a worker (PR #249); a later CI investigation could not reproduce the crash with path, layer-object or layer-id inputs, but nothing proves a worker is safe, so the safe default was kept (decision recorded 2026-10-12). A short pause on slope or hillshade is therefore expected. - Algorithms that change your own layer (a selection) stay synchronous, because edits to a project layer are only safe on the GUI thread.
- If an algorithm ignores a cancel, its objects are kept alive and the call returns after a timeout, rather than destroying them under a running thread.
-
run_callableruns pure Python (no QGIS objects) on a worker with the same Stop handling. Hub-siting ranking, allocation arithmetic, JIAF statistics and 2-opt route ordering use it.
tools/_chunked.py. A loop over features, pairs or pixels cannot leave the GUI thread because it reads QgsFeature objects. Instead it works in slices of about 50 ms: after each slice it lets Qt repaint, checks Stop, and every few seconds reports progress.
for feat in features(layer, "Classifying facilities"): # drop-in for layer.getFeatures()
...About 90 loops across the analysis, validation, styling, export and import tools use this since rc36. Pressing Stop raises AnalysisCancelledError, which every tool lets through; a Stop inside an edit rolls the edit back. Off the GUI thread (a worker, the tests) nothing is pumped and the loop just runs.
unsupervised_classification reads, classifies and writes the raster in row blocks of about 4 million values (centres are fitted on a sample, then every cell is assigned), so memory no longer grows with the raster. A raster over 400 million cells is refused with a "clip it first" message.
core/agent/cancel_signal.py holds module-level state for the duration of one request: a should_stop callable and a status callback. It is not passed through tool signatures because those are the model-facing schema. run() publishes the pair; long tools poll cancel_signal.is_cancelled() and report with report("what I am doing"). Everything in it is best-effort and never raises, so a broken callback cannot break a tool. The agent loop also checks Stop between rounds and between tool calls, and a project change counts as Stop (project_session.py).
schedule_recurring_workflow re-runs a saved step list on a QTimer. A single gate (WorkflowScheduler._guarded_tick) allows one heavy job at a time: a tick that would overlap another scheduled run or an active chat request is skipped (counted, not shown) and the next interval tries again. Interval floor 1 minute, at most 5 schedules, and only an allow-list of tools may be scheduled. See Security overview.
ChatViewPresenter.trim_scrollback removes the oldest whole items once the chat holds more than 300, keeping the newest 200, and shifts the positions of cards that are edited in place. The saved conversation is untouched. The 300 and 200 figures are estimates, not measured.
The slicing, the blocked classification and the scheduler gate have offline tests and live QGIS tests in CI. The speed-up and Stop latency on large real data have not been measured, and Stop against a long-running job has not been proven in a hand test. See Status and open work.
Cartogen AI 1.18.5 · generated from the repository at commit 26536e9f75 on 2026-11-12 · this wiki is rebuilt on every merge to main; edits made in the browser are overwritten, so correct the source files instead.
Get started
How it works
- Architecture overview
- The agent loop
- Threads, tasks and Stop
- Tools and routing
- Providers and networking
- Memory, state and results
- Guards and honesty checks
- The dock and dialogs
- Processing provider
Tools and data
- Tool reference
- AI Imagery Feature Extraction
- Data Analysis & Prediction
- Database & Workflows
- Export & Reporting
- Humanitarian Data (HDX / OSM / geoBoundaries)
- Humanitarian Logistics
- Monitoring & Scheduling
- Print Layouts
- Project Management
- Raster
- Reporting & Document Analysis
- Satellite Imagery & Vision
- Styling & Labeling
- System, Search & Scripting
- Task & Memory Management
- Vector & Geoprocessing
- More tools, by module
- Task register
- External data sources
Humanitarian GIS
Security and privacy
Develop
Project