How It Works#
The Agentic Predictive Maintenance (APM) blueprint follows an on-demand detect-then-reason model: clicking “Run Pipeline” starts the DL Streamer video-inference pipeline, waits for it to finish processing the (finite) source video, and then triggers a single multi-agent reasoning pass over exactly the detections that the run produced, generating structured maintenance tickets. Detection and reasoning are two independent, decoupled services connected only by a shared run_id and an event-driven MQTT handoff. This section describes each stage so you can understand, verify, and debug the pipeline independently.
System Overview#
Web UI (browser)
│ HTTP :8080 (via apm-nginx)
▼
UI Service (apm-ui)
│ REST: POST /run ──▶ Detection Service (apm-detection): POST /detection/run
│ REST: GET /api/detection/status/{id}, GET /api/agents/status/{id} (merged view)
▼
Detection Service (apm-detection)
│
├─ REST: POST /pipelines/user_defined_pipelines/<pipeline_name> ──▶ DL Streamer (apm-dlstreamer)
│ REST: GET /pipelines/status (start + poll to completion)
│
├─ MQTT subscriber (topic: apm/detections) ◀── DL Streamer publishes raw detections
│ REST: POST /detections (batch) ──▶ Storage Service (apm-storage)
│
└─ On terminal state (success or failure), publishes one "batch-complete"
MQTT event (topic: apm/batch-complete) carrying the run_id and the
id-window (start_id/end_id) of detections this run produced.
This is the *only* handoff between detection and reasoning — the
detection layer never calls the agent directly.
Agent Service (apm-agent) — external EAL "agent-quality-handler" image
│
├─ MQTT subscriber (topic: apm/batch-complete) ◀── reacts to the event above
│ Skips reasoning entirely if status != "completed" (no stale-data reasoning)
│
└─ Runs the 4-agent LangGraph pipeline bounded to the event's id-window:
Policy Agent → Analysis Agent → Evidence Agent → Ticketing Agent
(each agent reads detections from Storage Service via GET /detections,
bounded by min_id/max_id — never the whole history)
Detection and reasoning are fully decoupled processes: the detection layer
owns DL Streamer control and raw-detection persistence; the agent layer is
detection-agnostic and only reacts to the terminal “batch-complete” event.
The UI service is the only component that talks to both, merging their two
independent run states into one phase for display.
Stage 1 — Startup#
Run the setup script with a use case:
source setup.sh --use-case pipeline-defect-detection
Validates the environment and resolves
USE_CASE_*paths fromapps/<use-case>/.Sources
.env_<use-case>for model, device, and mode settings.Runs
docker compose up -dfor all services.
Services started:
Container |
Role |
|---|---|
|
Mosquitto MQTT broker |
|
Downloads detection model on first run |
|
Video inference (DL Streamer Pipeline Server) |
|
REST API + SQLite storage for detections |
|
Owns DL Streamer control, raw-detection ingestion, and the batch-complete event |
|
Multi-agent reasoning orchestrator (external EAL image, reacts to batch-complete) |
|
Web dashboard (Run Pipeline form, results, detections) |
|
Reverse proxy ( |
|
LLM service (OpenVINO model server) for agent reasoning |
Verify all containers are running:
docker ps --format "table {{.Names}}\t{{.Status}}"
Stage 2 — Trigger a Detect-Then-Reason Run#
Clicking Run Pipeline in the UI (or POSTing to the detection-service
directly) starts one full detect-then-reason cycle:
Detect — the detection-service starts the DL Streamer pipeline matching the selected Device (CPU, GPU, or NPU — each maps to its own pipeline definition in
configs/pipeline-server-config.json), optionally overriding the source Video with the file selected in the UI, and blocks until the pipeline reaches a terminal state.Handoff — once the pipeline reaches a terminal state (success or failure), the detection-service publishes a single
apm/batch-completeMQTT event carrying the outcome and the exactstart_idorend_iddetection window this run produced.Reason — the agent-service, subscribed to that topic independently, picks up the event under its own
run_idcorrelation and runs the four-agent pipeline bounded to exactly that ID window, never any earlier history. If the event’sstatusiserror(for example, an NPU device is selected but is not physically available), the agent-service records the run as failed immediately and skips reasoning entirely — it never reasons over stale or previously-stored detections.
Because detection and reasoning are separate services, only the
detection-service enforces “one run at a time”: a concurrent
POST /detection/run call is rejected with 409 and the id of the
currently running run. The agent-service reacts to events as they arrive
and has no concept of “a run in progress” beyond that.
Run Pipeline Inputs (UI)#
Field |
Description |
|---|---|
Use Case |
Read-only; identifies the deployed use case ( |
Device |
|
Video |
Source video file populated from the shared |
Manual Trigger#
curl -X POST http://localhost:8080/run \
-H "Content-Type: application/x-www-form-urlencoded" \
-d 'device=CPU&video_filename=sample.mp4'
Or call the detection-service directly through the proxy:
curl -X POST http://localhost:8080/api/detection/run \
-H "Content-Type: application/json" \
-d '{"device": "CPU", "video_filename": "sample.mp4"}'
# {"run_id": "abc123", "status": "running"}
Poll progress — the UI merges the two services’ independent states into one
phase that moves detecting → reasoning → completed/error:
curl http://localhost:8080/api/detection/status/abc123
# {"run_id": "abc123", "status": "running", "phase": "detecting"}
# once detection completes, the agent-service takes over:
curl http://localhost:8080/api/agents/status/abc123
# {"run_id": "abc123", "status": "running", "phase": "reasoning"}
List available source videos:
curl http://localhost:8080/api/detection/videos
Note: this release runs one bounded detect-then-reason cycle per click over a finite source video. True live and continuous background detection (independent of the “Run Pipeline” click) is a possible future direction; see the scalable architecture diagram (
docs/apm-scalable-arch.drawio) for a proposed decoupled design. Detection and reasoning are already decoupled services today; extending to live streams would mean periodic “checkpoint” batch-complete events instead of a single terminal one, not a re-architecture.
Stage 3 — Video Inference (DL Streamer → MQTT)#
DL Streamer runs the configured pipeline (CPU, GPU, or NPU) against the selected video and publishes each detection to MQTT.
Verify inference is running:
docker logs -f apm-dlstreamer
Verify MQTT messages are flowing:
docker exec apm-mqtt-broker mosquitto_sub -t 'apm/detections'
Each message is a JSON payload with label, confidence, bbox, frame_id, and timestamp.
Stage 4 — Detection Storage and the Batch-Complete Handoff#
The detection-service subscribes to the apm/detections MQTT topic on
startup and writes every detection to the storage service. Once its DL
Streamer run reaches a terminal state, it publishes one apm/batch-complete
event — the sole contract between detection and reasoning.
Verify detections are being stored:
# Recent detections
curl http://localhost:8080/api/storage/detections?limit=5
# Aggregate summary
curl http://localhost:8080/api/storage/detections/summary
# Current watermark (max detection id + total count)
curl http://localhost:8080/api/storage/detections/max_id
Verify the batch-complete event:
docker exec apm-mqtt-broker mosquitto_sub -t 'apm/batch-complete'
{
"run_id": "abc123",
"status": "completed",
"device": "CPU",
"video_filename": "sample.mp4",
"start_id": 1204,
"end_id": 1339,
"pipeline_status": {"state": "COMPLETED", "avg_fps": 24.7}
}
See the agent-service integration guide for the full contract any application needs to satisfy for plugging its own detection layer into the agent-service, or vice versa.
Stage 5 — Multi-Agent Reasoning (LangGraph)#
The agent-service’s meta-agent runs four agents via a LangGraph state
machine, bounded to the start_id/end_id window of the batch-complete
event. All agents read from the storage service.
Agent 1 — Policy Agent#
Reads agents.yaml thresholds and the run’s detections. Determines which defect classes triggered policy violations.
RuptureorDisconnectabove threshold: HIGH priority alert.Uses
policy_fallback.jsonrules in fallback mode; no LLM calls.
Agent 2 — Analysis Agent#
Filters detections by min_confidence (default 0.5). Produces:
Dominant defect class and counts
Confidence distribution
Temporal trend across frame IDs
Clustering of bounding box regions
Agent 3 — Evidence Agent#
Builds a formal audit trail:
Total frames inspected versus frames with detections.
Per-class counts and confidence statistics.
Top five highest-confidence detections per class.
Compliance status: PASS or FAIL.
Agent 4 — Ticketing Agent#
Synthesises outputs from Policy and Analysis agents. Produces a structured JSON maintenance ticket:
{
"priority": "HIGH",
"title": "Rupture detected in pipeline segment A3",
"description": "...",
"affected_component": "segment-A3",
"recommended_action": "HALT_PIPELINE",
"estimated_resolution_time": "4 hours",
"tags": "Rupture, Disconnect"
}
LLM versus Fallback Mode#
Mode |
How Agents Reason |
|---|---|
|
Agents send prompts to the LLM service (served via OVMS); responses are LLM-generated |
|
Agents apply rule-based logic from |
Set the mode when starting:
# Fallback (rule-based, no GPU or LLM required)
LLM_MODE=fallback source setup.sh --use-case pipeline-defect-detection
# LLM mode (requires the apm-llm/OVMS service)
source setup.sh --use-case pipeline-defect-detection
Stage 6 — View Results#
Check a Specific Run#
# List all runs known to the detection layer
curl http://localhost:8080/api/detection/runs
# List all runs the agent-service has processed
curl http://localhost:8080/api/agents/runs
# Get the merged detection + reasoning status/phase for a run
curl http://localhost:8080/api/detection/status/<run_id>
curl http://localhost:8080/api/agents/status/<run_id>
# Get the completed run's result (ticket + agent outputs)
curl http://localhost:8080/api/agents/results/<run_id>
Web UI#
Open http://localhost:8080 in a browser. The dashboard shows:
Run pipeline form (Use Case, Device, or Video).
Detection summary and browsing (
/detections).Run history with status, and generated maintenance tickets (
/results/<run_id>).
Quick Verification Checklist#
Run these commands in order, after the startup to verify each stage:
# 1. All containers healthy?
docker ps --format "table {{.Names}}\t{{.Status}}"
# 2. Detection and agent services reachable?
curl http://localhost:8080/api/detection/runs
curl http://localhost:8080/api/agents/runs
# 3. Trigger one detect-then-reason run
RUN_ID=$(curl -s -X POST http://localhost:8080/api/detection/run \
-H "Content-Type: application/json" \
-d '{"device": "CPU"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['run_id'])")
echo "Run ID: $RUN_ID"
# 4. Poll detection phase until it reaches completed/error
curl http://localhost:8080/api/detection/status/$RUN_ID
# 5. Once detection completes, poll the agent-service for reasoning phase
curl http://localhost:8080/api/agents/status/$RUN_ID
# 6. Check detections stored during the run
curl http://localhost:8080/api/storage/detections/summary
# 7. View the ticket in the run result
curl http://localhost:8080/api/agents/results/$RUN_ID | python3 -m json.tool
Troubleshooting#
Symptom |
Check |
|---|---|
No detections in storage |
|
Run stays in |
|
Run is stuck in |
|
Run reports |
|
UI shows no runs |
|
LLM/OpenVINO model server service is unhealthy |
Use |
|
|
|
|
For data preparation (creating a source video under resources/videos/):
python scripts/download_and_prep_data.py <dataset_url> --use-case pipeline-defect-detection