Orpheus Standard Data Models¶
This page is the wire contract: the JSON payloads agents publish to each other, and what every field means. Read it if you are writing an agent, integrating against the bus, or working out what a stored detection actually contains.
Overview¶
Agents never call each other directly. They exchange JSON payloads over the messaging
backplane — NATS with JetStream by default, with mosquitto as the one-line MQTT
fallback. The orpheus/... topic names below are the contract on either backend:
NATS mirrors them as subjects, mosquitto uses them as literal topics.
Detection Events¶
Bird Detection Event¶
Topic: orpheus/detection/bird/events
Producer: orpheus-agent-bird-detection
Consumers: orpheus-agent-crow-detection, orpheus_ui
@dataclass
class BirdDetectionEvent:
event_id: str # "bird_det_YYYYMMDD_HHMMSS_chN_XXXX"
timestamp: str # ISO 8601 UTC
channel_id: str # "1", "2", "3", "4"
detections: List[SpeciesDetection]
audio_clip_path: str # Absolute path on ORPHEUS_DATA_ROOT
@dataclass
class SpeciesDetection:
species_code: str # eBird code: "amecro", "comrav", etc.
species_common: str # "American Crow"
confidence: float # 0.0 - 1.0
start_time: float # Seconds from clip start
end_time: float # Seconds from clip start
JSON Example:
{
"event_id": "bird_det_20251205_143022_ch1_a1b2c3",
"timestamp": "2025-12-05T14:30:22.123456+00:00",
"channel_id": "1",
"detections": [
{
"species_code": "amecro",
"species_common": "American Crow",
"confidence": 0.87,
"start_time": 0.5,
"end_time": 3.2
}
],
"audio_clip_path": "/data/orpheus/audio/audio_motion/1/20251205T143022.flac"
}
Audio Motion Detection Event¶
Topic: orpheus/audio/motion/events
Producer: orpheus-agent-audio-motion
Consumers: orpheus-agent-bird-detection, orpheus-agent-audio-events, orpheus_ui
The audio motion agent publishes detection events using Detection.model_dump(mode="json"), which produces a nested payload structure. The UI backend (diagnostics.py) flattens several fields from metadata to the top level before serving them to the frontend.
Nested payload (as published on the bus)¶
{
"event_id": "audio_det_20251205_143022_ch1_a1b2",
"event_timestamp": "2025-12-05T14:30:22.000000+00:00",
"timestamp": "2025-12-05T14:30:22.000000+00:00",
"detection_type": "audio.motion",
"channel": 1,
"audio_clip_path": "/data/orpheus/audio/audio_motion/1/20251205T143022.flac",
"context": {
"lat": 47.606,
"lon": -122.332,
"sensor_id": "mic-1"
},
"metadata": {
"channel_id": "1",
"duration_seconds": 30.17,
"peak_energy_db": -35.23,
"average_energy_db": -48.09,
"frame_count": 143
}
}
Flattened payload (as served by the UI backend)¶
The on_audio_detection_message handler in the UI backend promotes the following
fields from metadata to the top level before caching:
| Field | Source | Fallback |
|---|---|---|
channel_id |
metadata.channel_id |
str(channel), then "unknown" |
duration_seconds |
metadata.duration_seconds |
(not set) |
peak_energy_db |
metadata.peak_energy_db |
(not set) |
The original metadata dict is preserved alongside the promoted fields.
Note for contributors: If you add new fields to the
Detection.metadatadict in the audio motion agent, you must also update the flattening logic inservices/orpheus_ui/backend/src/orpheus_ui/api/diagnostics.pyif the frontend needs those fields at the top level.
Crow Analysis Event¶
Topic: orpheus/detection/crow/events
Producer: orpheus-agent-crow-detection
Consumers: orpheus_ui, future interaction agents
@dataclass
class CrowAnalysisEvent:
event_id: str # "crow_det_YYYYMMDD_HHMMSS_chN_XXXX"
source_event_id: str # Links to triggering bird detection
timestamp: str # ISO 8601 UTC
channel_id: str
species_code: str # "amecro", "comrav", "fisccr"
species_common: str
birdnet_confidence: float # Original BirdNET confidence
crow_analysis: CrowAnalysis # Detailed analysis
audio_clip_path: str
model_version: str # "crow-tools-v1"
@dataclass
class CrowAnalysis:
crow_count: int # 1=single, 2=pair, 3+=group
crow_age: str # "adult" or "juvenile"
behaviors: CrowBehaviors
quality: int # 1=poor, 2=good
num_seconds_analyzed: int
@dataclass
class CrowBehaviors:
alert: bool # Warning/contact calls
begging: bool # Juvenile food requests
soft_song: bool # Quiet social vocalizations (subsong)
rattle: bool # Aggressive rattling display
mob: bool # Mobbing behavior
JSON Example:
{
"event_id": "crow_det_20251205_143023_ch1_d4e5f6",
"source_event_id": "bird_det_20251205_143022_ch1_a1b2c3",
"timestamp": "2025-12-05T14:30:23.456789+00:00",
"channel_id": "1",
"species_code": "amecro",
"species_common": "American Crow",
"birdnet_confidence": 0.87,
"crow_analysis": {
"crow_count": 1,
"crow_age": "adult",
"behaviors": {
"alert": true,
"begging": false,
"soft_song": false,
"rattle": false,
"mob": false
},
"quality": 2,
"num_seconds_analyzed": 3
},
"audio_clip_path": "/data/orpheus/audio/audio_motion/1/20251205T143022.flac",
"model_version": "crow-tools-v1"
}
Audio Playback¶
Playback Request¶
Topic: orpheus/audio/playback/request
Producers: Any agent needing to play audio
Consumer: orpheus-agent-audio-playback
@dataclass
class PlaybackRequest:
request_id: str # Unique request identifier
timestamp: str # ISO 8601 UTC
source_agent: str # Requesting agent name
audio_source: AudioSource # Where to get audio
playback_options: PlaybackOptions
@dataclass
class AudioSource:
# One of these must be provided:
path: Optional[str] # Absolute path or relative to ORPHEUS_DATA_ROOT
detection_id: Optional[str] # Look up path from DetectionDB
url: Optional[str] # Future: remote audio source
# Optional segment extraction:
start_time: Optional[float] # Seconds (None = start of file)
end_time: Optional[float] # Seconds (None = end of file)
@dataclass
class PlaybackOptions:
volume: float = 1.0 # 0.0 - 1.0
channel: Optional[int] = None # Specific output channel (None = all)
repeat: int = 1 # Number of times to play
delay_between: float = 0.0 # Seconds between repeats
JSON Example - Play by Path:
{
"request_id": "play_20251205_143100_abc123",
"timestamp": "2025-12-05T14:31:00.000000+00:00",
"source_agent": "orpheus-agent-crow-interaction",
"audio_source": {
"path": "/data/orpheus/audio/audio_motion/1/20251205T143022.flac",
"start_time": 0.5,
"end_time": 2.0
},
"playback_options": {
"volume": 0.8,
"repeat": 1
}
}
JSON Example - Play by Detection ID:
{
"request_id": "play_20251205_143200_def456",
"timestamp": "2025-12-05T14:32:00.000000+00:00",
"source_agent": "orpheus_ui",
"audio_source": {
"detection_id": "crow_det_20251205_143023_ch1_d4e5f6"
},
"playback_options": {
"volume": 1.0
}
}
Playback Response¶
Topic: orpheus/audio/playback/response
Producer: orpheus-agent-audio-playback
Consumers: Requesting agents, dashboard
The response is a flat envelope, published once per request. It echoes back whichever
identifier the request used, so a caller can match it up; there is no request_id and
no progress reporting — the agent answers when playback has been started, not when
it finishes.
@dataclass
class PlaybackResponse:
status: str # "success" or "error"
message: Optional[str] # Human-readable status, on success
error: Optional[str] # Error description, when status="error"
sound_name: Optional[str] # Echoed back, when the request named a sound
file_path: Optional[str] # Echoed back, when the request gave a path
detection_id: Optional[str] # Echoed back, when the request gave a detection
JSON Example - Success:
{
"status": "success",
"message": "Playback started",
"detection_id": "crow_det_20251205_143023_ch1_d4e5f6"
}
JSON Example - Error:
Health Status¶
Agent Health¶
Topic: orpheus/system/{agent-name}/health
Producer: Each agent
Consumer: orpheus_ui
@dataclass
class AgentHealth:
status: str # "online", "offline", "degraded"
timestamp: str
version: Optional[str] # Agent/model version
details: Optional[dict] # Agent-specific details
There is no AgentHealth class in the tree, and nothing publishes version or
details. Each agent builds its own health dict in health_payload(): the
default in orpheus_common.actor.base is status plus the ActorStats counters
(events_processed, errors_count, last_error), and every agent overrides it —
bird-detection adds model_loaded, timestamp and detections_found; the
correlator adds its window and feature flags. Read health_payload() in the agent
you care about before consuming this topic.
Database Schema Reference¶
detections table¶
| Column | Type | Description |
|---|---|---|
event_id |
TEXT | Primary identifier |
timestamp |
DATETIME | UTC timestamp |
detection_type |
TEXT | audio.motion, species.detected, crow.analyzed, audio.classified |
channel |
INTEGER | Audio channel 1-4 |
species_code |
TEXT | eBird species code |
species_common |
TEXT | Common name |
confidence |
REAL | Detection confidence |
audio_clip_path |
TEXT | Path to audio file |
metadata |
TEXT | JSON with additional data |
source_event_id |
TEXT | Parent event link |
id |
INTEGER | Primary key, autoincrement |
root_event_id |
TEXT | Root of the lineage chain (ADR 0012) |
created_at |
TEXT | Row insertion time |
event_metadata |
TEXT | The JSON sidecar (ADR 0005) |
intervals_json |
TEXT | Intra-clip localisation (ADR 0011) |
taxonomy_namespace |
TEXT | Label authority (ADR 0011) |
taxonomy_id |
TEXT | Identifier within that authority (ADR 0011) |
ensure_schema_updates() adds the last four to a legacy database on startup, so
an older DB gains them without a migration step.
metadata JSON for crow.analyzed¶
{
"crow_analysis": {
"crow_count": 1,
"crow_age": "adult",
"behaviors": {
"alert": true,
"begging": false,
"soft_song": false,
"rattle": false,
"mob": false
},
"quality": 2,
"num_seconds_analyzed": 3
},
"source_event_id": "bird_det_...",
"model_version": "crow-tools-v1"
}
Species Codes Reference¶
Corvids (Crow Detection Targets)¶
| Code | Common Name | Scientific Name |
|---|---|---|
amecro |
American Crow | Corvus brachyrhynchos |
comrav |
Common Raven | Corvus corax |
fisccr |
Fish Crow | Corvus ossifragus |
Crow Behaviors¶
| Behavior | Description | Audio Characteristics |
|---|---|---|
alert |
Warning/contact calls | Standard "caw" vocalizations |
begging |
Juvenile food requests | Whiny, pleading tones |
soft_song |
Quiet social vocalizations | Low clicking, warbling (subsong) |
rattle |
Aggressive display | Rapid rattling sounds |
mob |
Mobbing behavior | Rapid repeated alarm calls |
Entity Events¶
orpheus/entities/animal — produced by orpheus-agent-event-correlator,
consumed by the dashboard. One message per real animal, carrying every
classifier's evidence.
EntityEvent (orpheus_common.events) is a standalone model, deliberately not
an OrpheusBaseEvent subclass — see ADR 0016.
Its fields: entity_id, species_code, common_name, confidence,
entity_type, context, evidence (a list[EntityEvidence]), also_detected,
event_signature, is_self_generated.
Late-arriving evidence is published separately on
orpheus/entity-updates/animal, a sibling root rather than a child, so a
wildcard subscription to entity creation cannot pick it up by accident. With
publish_entity_type_topics enabled, entities also route by type —
orpheus/entities/animal/bird/crow.
See ADR 0013 for what merge keys on.
Where these live¶
The one shipped model is Detection in orpheus_common.detection.models, and
every agent publishes Detection.model_dump(mode="json") (ADR 0006 §3). Entities
are Entity in the same module and EntityEvent in orpheus_common.events.
There is no orpheus_common.models package. The shapes above describe what lands
inside the Detection envelope's fields — its metadata in particular — not
separate Python classes to import.