Spaces:
Runtime error
Runtime error
File size: 15,043 Bytes
290ff9e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 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 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 | # Ergo-Agentic: Architecture
## Tech Stack
- **Orchestration**: LangGraph (Python)
- **AI Models**: Multi-model (Claude, GPT-4V, Gemini β configurable)
- **Rules Engine**: Static JSON datasources (no database for rules)
- **Report Generation**: Deterministic code (no AI)
## High-Level Pipeline
```
Input (images + metadata)
β
βΌ
inspect_workspace_image ββββ per image, parallel
cv_extract ββββββββββββββββ per image, parallel
β
βΌ
build_routing_manifest βββββ deterministic join of semantic manifest + CV + metadata
β
βΌ
choose_applicable_parameters ββ deterministic
β
βΌ
assess_body_and_chair / assess_desk_and_arms / assess_screens
ββββ per relevant image Γ per model, parallel
β
βΌ
aggregate_findings_by_assessment ββ deterministic, assessment-scoped
β
βΌ
confidence_gate βββββββββββ deterministic accept / targeted-review split
β
βΌ
targeted_review_evidence ββ model review over weak/conflicting request batches
β
βΌ
global_consistency_audit ββ deterministic flags only, does not change outcomes
β
βΌ
build_final_assessment_matrix ββ deterministic
```
## Pipeline Nodes
### 1. inspect_workspace_image
**Type**: AI (vision LLM)
**Runs**: Once per uploaded image, in parallel
**Input**: Single image
**Output**: Workspace semantic manifest
```
WorkspaceSemanticManifest:
image_id: str
work_location: str | null # homeOffice, couch, bed, diningTable, floorSitting
person_visible: bool
body_coverage: str # none | partial | posture_coverage_sufficient
visible_body_regions: list[str] # head, torso, hips, legs, hands, feet, etc.
posture_context_hint: str # sitting | standing | mixed | unknown
chair_visible: bool
chair_type: str | null # office chair, dining chair, stool, etc.
desk_visible: bool
feet_visible: bool
screens: list # type, coarse_layout_position, display_visible_state
keyboard_mouse_visible: bool
detected_accessories: list # ergonomic objects: footrest, laptop stand,
# sit-stand converter, Wacom tablet,
# document reader, wrist rest, etc.
notes: str # anything unusual
```
**Purpose**: Understand what each image contains so downstream routing can avoid images that cannot answer certain questions. This node does not judge posture quality, screen arrangement, primary screen intent, or standing desk presence from a standing person. It only reports visible scene contents and coverage.
### 2. build_routing_manifest
**Type**: Deterministic code (no AI)
**Input**: `WorkspaceSemanticManifest[]` + `cv_results[]` + user metadata
**Output**: `RoutingManifest` + `scene_config`
`RoutingManifest` is the planner's truth source. It merges semantic coverage,
CV availability, pose/detection hints, and metadata into stable routing facts:
```
RoutingManifest:
body_and_chair_coverage: FocusGroupCoverage
desk_and_arms_coverage: FocusGroupCoverage
screens_coverage: FocusGroupCoverage
standing_work_possible: bool
standing_desk_equipment_visible: bool
posture_context: sitting | standing | mixed
image_focus_coverage: dict[focus_group, list[image_id]]
warnings: list[RoutingWarning]
evidence_sources: list[EvidenceSourceRef]
```
Standing desk equipment is true only when sit/stand equipment is visible or
metadata says it exists. A standing person can set `standing_work_possible`,
but does not enable standing desk equipment parameters by itself.
### 3. choose_applicable_parameters
**Type**: Deterministic code (no AI)
**Input**: `RoutingManifest` + `common-assessment-parameters.json`
**Output**: Execution plan
```
ExecutionPlan:
assessable_parameters: list[str] # human-readable labels with image coverage
assessable_parameter_ids: list[str] # parameter IDs used as stable join keys
assessable_parameter_details: list[dict] # id, label, group, question for traces
skipped_parameters: list[str] # no image can answer these
skipped_parameter_details: list[dict] # id, label, group, question for traces
focus_group_assignments:
"body_and_chair": list[image_id] # which images to send
"desk_and_arms": list[image_id]
"screens": list[image_id]
scene_config:
screen_count: int
has_standing_desk: bool
work_location: str
```
**Logic**:
- If routing has no `posture_coverage_sufficient`, skip body/chair parameters
- If no image has `feet_visible`, skip feet grounding
- If `screen_count == 1`, skip screen arrangement parameters
- If `standing_desk_equipment_visible == false`, skip standing desk parameters
- Screen type logic: single monitor β monitor params; single laptop β laptop params; mixed/multiple β include arrangement rubrics, but the screen pass still judges the outcome
- Filters `common-assessment-parameters.json` to only relevant parameters
### 4. assess_body_and_chair / assess_desk_and_arms / assess_screens
**Type**: AI (vision LLM)
**Runs**: Per image Γ per model Γ per focus group (all parallel)
**Input**: Image(s) + parameter rubric for the focus group
Three focus groups, each assessing 3-4 parameters:
#### Pass 1: Body & Chair
**Visual focus**: The person's body and chair
**Parameters**:
- Back posture (slouching / sitting back / perched / leaning forward)
- Sitting height (hips vs knees)
- Seat pan depth (gap behind knee)
- Feet grounding (feet flat, dangling, etc.)
#### Pass 2: Desk & Arms
**Visual focus**: Desk surface and arm positioning
**Parameters**:
- Desk height relative to elbow (sitting)
- Standing desk height relative to elbow (if applicable)
- Armrest height relative to elbow
- Keyboard & mouse placement (distance from desk edge)
#### Pass 3: Screens
**Visual focus**: Monitor/laptop area
**Parameters**:
- Screen/laptop height relative to eye level
- Screen/laptop distance from user
- Screen arrangement β only if multiple screens (is person centered on primary?)
**Screen logic**: Single monitor β assess monitor only. Single laptop β assess laptop only. Mixed setup β monitor is primary. Agents simply assess what they see; mapping to specific outcome key variants (twoMulti-*, twoScreens-*, etc.) happens in the aggregator.
**Output per parameter**:
```
Observation:
parameter_id: str
outcome: str | null # the option key, or null if not determinable
visibility: clear | partial | not_visible
confidence: "high" | "medium" | "low"
evidence_note: str # what the model actually observed (specific visual cues)
```
**Prompt design**:
- Each pass receives ONLY its 3-4 parameters plus a **visual cue guide** describing what to look at
- The parameter rubric includes the reference images (`optionImage`) as visual anchors
- The model is asked: "Which of these options best matches what you see?"
- Evidence notes must reference specific observable features, not subjective impressions
- Explicitly told: "If you cannot clearly determine this parameter from the image, return null"
See [agent-orchestration.md](architecture/agent-orchestration.md) for the full visual cue guide per focus group.
### 5. Assessment-Scoped Worst-Case Aggregator
**Type**: Deterministic code (no AI)
**Input**: All observations from all vision passes
**Output**: Aggregated results + conflicts
This is still one deterministic reducer node in the graph, but it does not
blend unrelated tasks together. It first buckets observations by
`assessment_id` and then applies worst-case aggregation inside each bucket.
**Logic per assessment**:
1. **Across images**: If image 1 shows `chair-posture-sittingBack` but image 2 shows `chair-posture-slouching`, take the **worst** outcome (highest risk). This follows the "worst observed" rule.
2. **Across models**: Collect votes. If all 3 models agree, high confidence in the result. If they disagree, flag as a conflict.
3. **Risk ranking**: Uses `issue-based-outcomes.json` to rank outcomes by risk level (high > medium > low > null/good habit) and `postureScore` (lower score = worse).
```
AggregatedResult:
parameter_id: str
worst_outcome: str
model_votes: dict[str, str] # model_name -> outcome
agreement: bool # all models agree?
confidence: str # derived from vote agreement
Conflict:
parameter_id: str
disagreements: dict[str, str] # model_name -> outcome
images_analyzed: list[str]
```
### 6. Layered Review
**Type**: deterministic gate + targeted AI review + deterministic audit
**Runs**: Gate once, targeted review only for weak/conflicting findings, audit once
**Input**: Candidate findings, conflicts, relevant images, routing summary
**Output**: Final confirmed outcome list plus audit flags
**Purpose**:
- Accept strong, uncontested findings without another model call
- Resolve conflicts or weak evidence using only relevant images and rubrics
- Batch related weak findings into one review request when they share focus
group, posture context, and image subset
- Catch any obvious errors (e.g., model said "slouching" but person is clearly standing)
- Check matrix consistency without directly overriding outcomes
- Produce the definitive list of outcome keys
**Output**:
```
ReviewResult:
final_outcomes: list[str] # confirmed outcome keys
overrides: list[Override] # where review agent changed the aggregated result
skipped_parameters: list[str] # insufficient evidence
global_audit_flags: list[GlobalAuditFlag]
```
### 7. Outcome Matrix Builder
**Type**: Deterministic code (no AI)
**Input**: Final outcomes + evidence trail + scene config
**Output**: Outcome matrix (parameter x outcome x evidence)
The **primary output** of the agentic pipeline. A structured matrix of which parameters were assessed, which outcome(s) were selected, evidence summaries, and review decisions. This is the core deliverable β everything downstream is deterministic transformation.
### 8. Report Builder (optional, separate concern)
**Type**: Deterministic code (no AI)
**Input**: Outcome matrix + all datasource JSONs + metadata
**Output**: Complete report JSON (matching `report-sample.json` structure)
Can run inside or outside the LangGraph pipeline. The agentic work is done once the outcome matrix is built.
**Logic**:
1. For each outcome in the matrix:
- Look up in `issue-based-outcomes.json` β get `isGoodHabit`, `riskLevel`, `currentHabit`, `recommendation`, `postureScore`
- Look up in `body-based-outcomes.json` β get affected body parts, conditions, body-part-specific recommendations
2. Build `goodHabits` list from outcomes where `isGoodHabit == true`
3. Build `issueBasedReport` from outcomes where `isGoodHabit == false`
4. Build `potentialRiskPart` by grouping issues per body part, taking worst condition per body part
5. Build `actionPlans` from recommendations, ordered by priority (points)
6. Match `mergedProducts` from product datasource based on outcomes
7. Calculate `ergoPostureScore` from posture scores
## Anti-Hallucination Strategy
| Layer | Mechanism |
|---|---|
| **Image Analyzer** | Produces a visibility/scene manifest only; no ergonomic judgments |
| **Routing Manifest** | Deterministically maps semantic/CV/metadata facts to focus-group coverage |
| **Focused passes** | 3-4 params per call, not 12+; model stays grounded on one visual region |
| **Visual cue guides** | Each pass includes specific visual cues (what to look at, what angles to check) |
| **Reference images** | Option images from rubric serve as visual anchors ("does it look like A or B?") |
| **Observable evidence** | Models must cite specific visual features, not subjective impressions |
| **Null allowed** | Models can say "not determinable" β no pressure to fill every field |
| **Multi-model voting** | Single-model hallucination caught by disagreement with other models |
| **Layered review** | Strong findings are accepted deterministically; only weak/conflicting findings get targeted image review |
## Model Routing
Models are configurable per role:
```
model_config:
image_analyzer: "claude-sonnet"
vision_passes:
- "claude-sonnet"
- "gpt-4o"
- "gemini-pro-vision"
review_agent: "claude-opus"
```
This is designed for experimentation β swap models, compare accuracy, find the best combination.
## LangGraph State Schema
```python
class ErgoState(TypedDict):
# Input
images: list[ImageInput]
metadata: dict
# Image Analyzer output
image_manifests: list[WorkspaceSemanticManifest]
cv_results: list[ImageCVResult]
routing_manifest: RoutingManifest
work_location: str
scene_config: SceneConfig
# Planner output
assessable_parameters: list[str] # human-readable labels
assessable_parameter_ids: list[str] # stable IDs for internal joins
assessable_parameter_details: list[dict]
skipped_parameters: list[str] # stable IDs
skipped_parameter_details: list[dict]
execution_plan: ExecutionPlan
# Vision pass outputs
observations: list[Observation]
# Aggregator output
aggregated_findings: dict[str, AggregatedFinding]
candidate_findings: dict[str, CandidateFinding]
conflicts: list[Conflict]
# Review output
review_requests: list[ReviewRequest]
review_decisions: list[ReviewDecision]
global_audit_flags: list[GlobalAuditFlag]
final_outcomes: list[str]
evidence_trail: dict[str, Evidence]
# Primary output
outcome_matrix: OutcomeMatrix # parameter Γ outcome Γ evidence
# Optional report
report: dict | None
```
## Scalability Considerations
- Vision passes are embarrassingly parallel: `images Γ models Γ focus_groups` can all run concurrently
- For 3 images, 3 models, 3 focus groups = 27 parallel calls + 3 image analyzer calls + 1 review call = 31 total LLM calls
- Report building is sub-second (JSON lookups)
- New parameters/groups only require datasource updates; pipeline auto-adapts via Parameter Planner
## See Also
For detailed implementation specs, see `docs/architecture/`:
- [Domain Model](architecture/domain-model.md) β canonical entities, enums, relationships, open decisions
- [Datasource Contracts](architecture/datasource-contracts.md) β schemas, validation rules, migration notes
- [Agent Orchestration](architecture/agent-orchestration.md) β LangGraph state, nodes, routing, conflict resolution
- [Report Contract](architecture/report-contract.md) β exact output schema, scoring, rendering
|