copilot-swe-agent[bot] CatoG commited on
Commit
3e3d4be
·
1 Parent(s): a94c9f8

Add iterative QA feedback: QA provides per-role targeted feedback to enable role refinement

Browse files
Files changed (1) hide show
  1. app.py +158 -24
app.py CHANGED
@@ -558,6 +558,7 @@ class WorkflowState(TypedDict):
558
  synthesis_output: str # unified summary produced by the Synthesizer after all specialists
559
  draft_output: str # latest specialist/synthesis output forwarded to QA
560
  qa_report: str
 
561
  qa_passed: bool
562
  revision_count: int
563
  final_answer: str
@@ -618,10 +619,15 @@ _TECHNICAL_SYSTEM = (
618
 
619
  _QA_SYSTEM = (
620
  "You are the QA Tester in a multi-role AI workflow.\n"
621
- "Check whether the specialist output satisfies the original request and success criteria.\n\n"
 
 
622
  "Respond in this exact format:\n"
623
  "REQUIREMENTS CHECKED:\n<list each requirement and whether it was met>\n\n"
624
  "ISSUES FOUND:\n<defects or gaps — or 'None' if all requirements are met>\n\n"
 
 
 
625
  "RESULT: <PASS | FAIL>\n\n"
626
  "RECOMMENDED FIXES:\n<specific improvements — or 'None' if result is PASS>"
627
  )
@@ -936,6 +942,40 @@ def _qa_passed_check(qa_text: str) -> bool:
936
  return False
937
 
938
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
939
  # --- Workflow step functions ---
940
  # Each step receives the shared state and an append-only trace list,
941
  # updates state in place, appends log lines, and returns updated state.
@@ -966,7 +1006,11 @@ def _step_creative(chat_model, state: WorkflowState, trace: List[str]) -> Workfl
966
  f"Planner instructions:\n{state['plan']}"
967
  )
968
  if state["revision_count"] > 0:
969
- content += f"\n\nQA feedback to address:\n{state['qa_report']}"
 
 
 
 
970
  text = _llm_call(chat_model, _CREATIVE_SYSTEM, content)
971
  state["creative_output"] = text
972
  state["draft_output"] = text
@@ -983,7 +1027,11 @@ def _step_technical(chat_model, state: WorkflowState, trace: List[str]) -> Workf
983
  f"Planner instructions:\n{state['plan']}"
984
  )
985
  if state["revision_count"] > 0:
986
- content += f"\n\nQA feedback to address:\n{state['qa_report']}"
 
 
 
 
987
  text = _llm_call(chat_model, _TECHNICAL_SYSTEM, content)
988
  state["technical_output"] = text
989
  state["draft_output"] = text
@@ -992,19 +1040,46 @@ def _step_technical(chat_model, state: WorkflowState, trace: List[str]) -> Workf
992
  return state
993
 
994
 
995
- def _step_qa(chat_model, state: WorkflowState, trace: List[str]) -> WorkflowState:
996
- """QA Tester: check the draft against the original request and success criteria."""
 
 
 
 
 
 
 
 
 
 
 
 
997
  trace.append("\n╔══ [QA TESTER] Reviewing output... ══╗")
998
  content = (
999
  f"Original user request: {state['user_request']}\n\n"
1000
  f"Planner's plan and success criteria:\n{state['plan']}\n\n"
1001
- f"Specialist output to review:\n{state['draft_output']}"
1002
  )
 
 
 
 
 
 
 
 
 
1003
  text = _llm_call(chat_model, _QA_SYSTEM, content)
1004
  state["qa_report"] = text
 
1005
  state["qa_passed"] = _qa_passed_check(text)
1006
  result_label = "✅ PASS" if state["qa_passed"] else "❌ FAIL"
1007
  trace.append(text)
 
 
 
 
 
 
1008
  trace.append(f"╚══ [QA TESTER] Result: {result_label} ══╝")
1009
  return state
1010
 
@@ -1054,7 +1129,11 @@ def _step_research(chat_model, state: WorkflowState, trace: List[str]) -> Workfl
1054
  f"Planner instructions:\n{state['plan']}"
1055
  )
1056
  if state["revision_count"] > 0:
1057
- content += f"\n\nQA feedback to address:\n{state['qa_report']}"
 
 
 
 
1058
  text = _llm_call(chat_model, _RESEARCH_SYSTEM, content)
1059
  state["research_output"] = text
1060
  state["draft_output"] = text
@@ -1071,7 +1150,11 @@ def _step_security(chat_model, state: WorkflowState, trace: List[str]) -> Workfl
1071
  f"Planner instructions:\n{state['plan']}"
1072
  )
1073
  if state["revision_count"] > 0:
1074
- content += f"\n\nQA feedback to address:\n{state['qa_report']}"
 
 
 
 
1075
  text = _llm_call(chat_model, _SECURITY_SYSTEM, content)
1076
  state["security_output"] = text
1077
  state["draft_output"] = text
@@ -1088,7 +1171,11 @@ def _step_data_analyst(chat_model, state: WorkflowState, trace: List[str]) -> Wo
1088
  f"Planner instructions:\n{state['plan']}"
1089
  )
1090
  if state["revision_count"] > 0:
1091
- content += f"\n\nQA feedback to address:\n{state['qa_report']}"
 
 
 
 
1092
  text = _llm_call(chat_model, _DATA_ANALYST_SYSTEM, content)
1093
  state["data_analyst_output"] = text
1094
  state["draft_output"] = text
@@ -1105,7 +1192,11 @@ def _step_mad_professor(chat_model, state: WorkflowState, trace: List[str]) -> W
1105
  f"Planner instructions:\n{state['plan']}"
1106
  )
1107
  if state["revision_count"] > 0:
1108
- content += f"\n\nQA feedback to address:\n{state['qa_report']}"
 
 
 
 
1109
  text = _llm_call(chat_model, _MAD_PROFESSOR_SYSTEM, content)
1110
  state["mad_professor_output"] = text
1111
  state["draft_output"] = text
@@ -1122,7 +1213,11 @@ def _step_accountant(chat_model, state: WorkflowState, trace: List[str]) -> Work
1122
  f"Planner instructions:\n{state['plan']}"
1123
  )
1124
  if state["revision_count"] > 0:
1125
- content += f"\n\nQA feedback to address:\n{state['qa_report']}"
 
 
 
 
1126
  text = _llm_call(chat_model, _ACCOUNTANT_SYSTEM, content)
1127
  state["accountant_output"] = text
1128
  state["draft_output"] = text
@@ -1139,7 +1234,11 @@ def _step_artist(chat_model, state: WorkflowState, trace: List[str]) -> Workflow
1139
  f"Planner instructions:\n{state['plan']}"
1140
  )
1141
  if state["revision_count"] > 0:
1142
- content += f"\n\nQA feedback to address:\n{state['qa_report']}"
 
 
 
 
1143
  text = _llm_call(chat_model, _ARTIST_SYSTEM, content)
1144
  state["artist_output"] = text
1145
  state["draft_output"] = text
@@ -1156,7 +1255,11 @@ def _step_lazy_slacker(chat_model, state: WorkflowState, trace: List[str]) -> Wo
1156
  f"Planner instructions:\n{state['plan']}"
1157
  )
1158
  if state["revision_count"] > 0:
1159
- content += f"\n\nQA feedback to address:\n{state['qa_report']}"
 
 
 
 
1160
  text = _llm_call(chat_model, _LAZY_SLACKER_SYSTEM, content)
1161
  state["lazy_slacker_output"] = text
1162
  state["draft_output"] = text
@@ -1173,7 +1276,11 @@ def _step_black_metal_fundamentalist(chat_model, state: WorkflowState, trace: Li
1173
  f"Planner instructions:\n{state['plan']}"
1174
  )
1175
  if state["revision_count"] > 0:
1176
- content += f"\n\nQA feedback to address:\n{state['qa_report']}"
 
 
 
 
1177
  text = _llm_call(chat_model, _BLACK_METAL_FUNDAMENTALIST_SYSTEM, content)
1178
  state["black_metal_fundamentalist_output"] = text
1179
  state["draft_output"] = text
@@ -1190,7 +1297,11 @@ def _step_labour_union_rep(chat_model, state: WorkflowState, trace: List[str]) -
1190
  f"Planner instructions:\n{state['plan']}"
1191
  )
1192
  if state["revision_count"] > 0:
1193
- content += f"\n\nQA feedback to address:\n{state['qa_report']}"
 
 
 
 
1194
  text = _llm_call(chat_model, _LABOUR_UNION_REP_SYSTEM, content)
1195
  state["labour_union_rep_output"] = text
1196
  state["draft_output"] = text
@@ -1207,7 +1318,11 @@ def _step_ux_designer(chat_model, state: WorkflowState, trace: List[str]) -> Wor
1207
  f"Planner instructions:\n{state['plan']}"
1208
  )
1209
  if state["revision_count"] > 0:
1210
- content += f"\n\nQA feedback to address:\n{state['qa_report']}"
 
 
 
 
1211
  text = _llm_call(chat_model, _UX_DESIGNER_SYSTEM, content)
1212
  state["ux_designer_output"] = text
1213
  state["draft_output"] = text
@@ -1224,7 +1339,11 @@ def _step_doris(chat_model, state: WorkflowState, trace: List[str]) -> WorkflowS
1224
  f"Planner instructions:\n{state['plan']}"
1225
  )
1226
  if state["revision_count"] > 0:
1227
- content += f"\n\nQA feedback to address:\n{state['qa_report']}"
 
 
 
 
1228
  text = _llm_call(chat_model, _DORIS_SYSTEM, content)
1229
  state["doris_output"] = text
1230
  state["draft_output"] = text
@@ -1241,7 +1360,11 @@ def _step_chairman_of_board(chat_model, state: WorkflowState, trace: List[str])
1241
  f"Planner instructions:\n{state['plan']}"
1242
  )
1243
  if state["revision_count"] > 0:
1244
- content += f"\n\nQA feedback to address:\n{state['qa_report']}"
 
 
 
 
1245
  text = _llm_call(chat_model, _CHAIRMAN_SYSTEM, content)
1246
  state["chairman_of_board_output"] = text
1247
  state["draft_output"] = text
@@ -1258,7 +1381,11 @@ def _step_maga_appointee(chat_model, state: WorkflowState, trace: List[str]) ->
1258
  f"Planner instructions:\n{state['plan']}"
1259
  )
1260
  if state["revision_count"] > 0:
1261
- content += f"\n\nQA feedback to address:\n{state['qa_report']}"
 
 
 
 
1262
  text = _llm_call(chat_model, _MAGA_APPOINTEE_SYSTEM, content)
1263
  state["maga_appointee_output"] = text
1264
  state["draft_output"] = text
@@ -1275,7 +1402,11 @@ def _step_lawyer(chat_model, state: WorkflowState, trace: List[str]) -> Workflow
1275
  f"Planner instructions:\n{state['plan']}"
1276
  )
1277
  if state["revision_count"] > 0:
1278
- content += f"\n\nQA feedback to address:\n{state['qa_report']}"
 
 
 
 
1279
  text = _llm_call(chat_model, _LAWYER_SYSTEM, content)
1280
  state["lawyer_output"] = text
1281
  state["draft_output"] = text
@@ -1346,7 +1477,7 @@ _EMPTY_STATE_BASE: WorkflowState = {
1346
  "labour_union_rep_output": "", "ux_designer_output": "", "doris_output": "",
1347
  "chairman_of_board_output": "", "maga_appointee_output": "", "lawyer_output": "",
1348
  "synthesis_output": "",
1349
- "draft_output": "", "qa_report": "", "qa_passed": False,
1350
  "revision_count": 0, "final_answer": "",
1351
  }
1352
 
@@ -1526,9 +1657,11 @@ def run_multi_role_workflow(
1526
  1. Planner (if active) analyses the task and picks a primary specialist.
1527
  2. ALL active specialists run and contribute their own perspective.
1528
  3. Synthesizer summarises all perspectives, finds common ground, and produces a unified recommendation.
1529
- 4. QA Tester (if active) reviews the synthesized output.
 
1530
  5. Planner (if active) reviews QA result and either approves or requests a revision.
1531
- 6. Repeat from step 2 if QA fails and retries remain.
 
1532
  7. If max retries are reached, return best attempt with QA concerns.
1533
 
1534
  Args:
@@ -1586,6 +1719,7 @@ def run_multi_role_workflow(
1586
  "synthesis_output": "",
1587
  "draft_output": "",
1588
  "qa_report": "",
 
1589
  "qa_passed": False,
1590
  "revision_count": 0,
1591
  "final_answer": "",
@@ -1649,7 +1783,7 @@ def run_multi_role_workflow(
1649
 
1650
  # Step 3: QA reviews the specialist's draft (if enabled)
1651
  if qa_active:
1652
- state = _step_qa(chat_model, state, trace)
1653
  else:
1654
  state["qa_passed"] = True
1655
  state["qa_report"] = "QA Tester is disabled — skipping quality review."
 
558
  synthesis_output: str # unified summary produced by the Synthesizer after all specialists
559
  draft_output: str # latest specialist/synthesis output forwarded to QA
560
  qa_report: str
561
+ qa_role_feedback: Dict[str, str] # role key → targeted QA feedback for that specific role
562
  qa_passed: bool
563
  revision_count: int
564
  final_answer: str
 
619
 
620
  _QA_SYSTEM = (
621
  "You are the QA Tester in a multi-role AI workflow.\n"
622
+ "Check whether the output satisfies the original request and success criteria.\n"
623
+ "When individual specialist contributions are provided, give targeted feedback for each role\n"
624
+ "so they can refine their specific propositions in the next iteration.\n\n"
625
  "Respond in this exact format:\n"
626
  "REQUIREMENTS CHECKED:\n<list each requirement and whether it was met>\n\n"
627
  "ISSUES FOUND:\n<defects or gaps — or 'None' if all requirements are met>\n\n"
628
+ "ROLE-SPECIFIC FEEDBACK:\n"
629
+ "<one bullet per specialist role that contributed, with targeted feedback on their contribution:\n"
630
+ " • Role Name: <specific feedback for that role to refine their proposition, or 'Satisfactory' if no issues>>\n\n"
631
  "RESULT: <PASS | FAIL>\n\n"
632
  "RECOMMENDED FIXES:\n<specific improvements — or 'None' if result is PASS>"
633
  )
 
942
  return False
943
 
944
 
945
+ def _parse_qa_role_feedback(qa_text: str) -> Dict[str, str]:
946
+ """Extract per-role targeted feedback from a QA report.
947
+
948
+ Looks for the ROLE-SPECIFIC FEEDBACK section produced by the QA Tester
949
+ and parses bullet entries of the form '• Role Name: <feedback>'.
950
+ Returns a dict mapping role keys (e.g. 'creative', 'technical') to the
951
+ feedback string targeted at that role.
952
+ """
953
+ feedback: Dict[str, str] = {}
954
+ if "ROLE-SPECIFIC FEEDBACK:" not in qa_text:
955
+ return feedback
956
+
957
+ # Extract the section between ROLE-SPECIFIC FEEDBACK: and the next header
958
+ section = qa_text.split("ROLE-SPECIFIC FEEDBACK:", 1)[1]
959
+ for header in ("RESULT:", "RECOMMENDED FIXES:"):
960
+ if header in section:
961
+ section = section.split(header, 1)[0]
962
+ break
963
+
964
+ # Parse bullet lines: • Role Name: <feedback text>
965
+ for line in section.strip().splitlines():
966
+ line = line.strip().lstrip("•-* ")
967
+ if ":" not in line:
968
+ continue
969
+ role_label, _, role_feedback = line.partition(":")
970
+ role_label = role_label.strip()
971
+ role_feedback = role_feedback.strip()
972
+ role_key = _ROLE_LABEL_TO_KEY.get(role_label)
973
+ if role_key and role_feedback:
974
+ feedback[role_key] = role_feedback
975
+
976
+ return feedback
977
+
978
+
979
  # --- Workflow step functions ---
980
  # Each step receives the shared state and an append-only trace list,
981
  # updates state in place, appends log lines, and returns updated state.
 
1006
  f"Planner instructions:\n{state['plan']}"
1007
  )
1008
  if state["revision_count"] > 0:
1009
+ role_feedback = state["qa_role_feedback"].get("creative", "")
1010
+ if role_feedback:
1011
+ content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
1012
+ else:
1013
+ content += f"\n\nQA feedback to address:\n{state['qa_report']}"
1014
  text = _llm_call(chat_model, _CREATIVE_SYSTEM, content)
1015
  state["creative_output"] = text
1016
  state["draft_output"] = text
 
1027
  f"Planner instructions:\n{state['plan']}"
1028
  )
1029
  if state["revision_count"] > 0:
1030
+ role_feedback = state["qa_role_feedback"].get("technical", "")
1031
+ if role_feedback:
1032
+ content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
1033
+ else:
1034
+ content += f"\n\nQA feedback to address:\n{state['qa_report']}"
1035
  text = _llm_call(chat_model, _TECHNICAL_SYSTEM, content)
1036
  state["technical_output"] = text
1037
  state["draft_output"] = text
 
1040
  return state
1041
 
1042
 
1043
+ def _step_qa(
1044
+ chat_model,
1045
+ state: WorkflowState,
1046
+ trace: List[str],
1047
+ all_outputs: Optional[List[Tuple[str, str]]] = None,
1048
+ ) -> WorkflowState:
1049
+ """QA Tester: check the draft against the original request and success criteria.
1050
+
1051
+ When *all_outputs* is provided (list of (role_key, output) pairs from this
1052
+ iteration), each specialist's individual contribution is included in the
1053
+ review prompt so the QA can supply targeted, per-role feedback. This
1054
+ feedback is stored in ``state['qa_role_feedback']`` and consumed by the
1055
+ specialist step functions on the next revision pass.
1056
+ """
1057
  trace.append("\n╔══ [QA TESTER] Reviewing output... ══╗")
1058
  content = (
1059
  f"Original user request: {state['user_request']}\n\n"
1060
  f"Planner's plan and success criteria:\n{state['plan']}\n\n"
 
1061
  )
1062
+ if all_outputs:
1063
+ # Include each specialist's individual output so QA can give role-specific feedback
1064
+ content += "Individual specialist contributions:\n\n"
1065
+ for r_key, r_output in all_outputs:
1066
+ r_label = AGENT_ROLES.get(r_key, r_key)
1067
+ content += f"=== {r_label} ===\n{r_output}\n\n"
1068
+ content += f"Synthesized unified output:\n{state['draft_output']}"
1069
+ else:
1070
+ content += f"Specialist output to review:\n{state['draft_output']}"
1071
  text = _llm_call(chat_model, _QA_SYSTEM, content)
1072
  state["qa_report"] = text
1073
+ state["qa_role_feedback"] = _parse_qa_role_feedback(text)
1074
  state["qa_passed"] = _qa_passed_check(text)
1075
  result_label = "✅ PASS" if state["qa_passed"] else "❌ FAIL"
1076
  trace.append(text)
1077
+ if state["qa_role_feedback"]:
1078
+ feedback_summary = ", ".join(
1079
+ f"{AGENT_ROLES.get(k, k)}: {v[:60]}{'…' if len(v) > 60 else ''}"
1080
+ for k, v in state["qa_role_feedback"].items()
1081
+ )
1082
+ trace.append(f" ℹ Role-specific feedback dispatched → {feedback_summary}")
1083
  trace.append(f"╚══ [QA TESTER] Result: {result_label} ══╝")
1084
  return state
1085
 
 
1129
  f"Planner instructions:\n{state['plan']}"
1130
  )
1131
  if state["revision_count"] > 0:
1132
+ role_feedback = state["qa_role_feedback"].get("research", "")
1133
+ if role_feedback:
1134
+ content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
1135
+ else:
1136
+ content += f"\n\nQA feedback to address:\n{state['qa_report']}"
1137
  text = _llm_call(chat_model, _RESEARCH_SYSTEM, content)
1138
  state["research_output"] = text
1139
  state["draft_output"] = text
 
1150
  f"Planner instructions:\n{state['plan']}"
1151
  )
1152
  if state["revision_count"] > 0:
1153
+ role_feedback = state["qa_role_feedback"].get("security", "")
1154
+ if role_feedback:
1155
+ content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
1156
+ else:
1157
+ content += f"\n\nQA feedback to address:\n{state['qa_report']}"
1158
  text = _llm_call(chat_model, _SECURITY_SYSTEM, content)
1159
  state["security_output"] = text
1160
  state["draft_output"] = text
 
1171
  f"Planner instructions:\n{state['plan']}"
1172
  )
1173
  if state["revision_count"] > 0:
1174
+ role_feedback = state["qa_role_feedback"].get("data_analyst", "")
1175
+ if role_feedback:
1176
+ content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
1177
+ else:
1178
+ content += f"\n\nQA feedback to address:\n{state['qa_report']}"
1179
  text = _llm_call(chat_model, _DATA_ANALYST_SYSTEM, content)
1180
  state["data_analyst_output"] = text
1181
  state["draft_output"] = text
 
1192
  f"Planner instructions:\n{state['plan']}"
1193
  )
1194
  if state["revision_count"] > 0:
1195
+ role_feedback = state["qa_role_feedback"].get("mad_professor", "")
1196
+ if role_feedback:
1197
+ content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
1198
+ else:
1199
+ content += f"\n\nQA feedback to address:\n{state['qa_report']}"
1200
  text = _llm_call(chat_model, _MAD_PROFESSOR_SYSTEM, content)
1201
  state["mad_professor_output"] = text
1202
  state["draft_output"] = text
 
1213
  f"Planner instructions:\n{state['plan']}"
1214
  )
1215
  if state["revision_count"] > 0:
1216
+ role_feedback = state["qa_role_feedback"].get("accountant", "")
1217
+ if role_feedback:
1218
+ content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
1219
+ else:
1220
+ content += f"\n\nQA feedback to address:\n{state['qa_report']}"
1221
  text = _llm_call(chat_model, _ACCOUNTANT_SYSTEM, content)
1222
  state["accountant_output"] = text
1223
  state["draft_output"] = text
 
1234
  f"Planner instructions:\n{state['plan']}"
1235
  )
1236
  if state["revision_count"] > 0:
1237
+ role_feedback = state["qa_role_feedback"].get("artist", "")
1238
+ if role_feedback:
1239
+ content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
1240
+ else:
1241
+ content += f"\n\nQA feedback to address:\n{state['qa_report']}"
1242
  text = _llm_call(chat_model, _ARTIST_SYSTEM, content)
1243
  state["artist_output"] = text
1244
  state["draft_output"] = text
 
1255
  f"Planner instructions:\n{state['plan']}"
1256
  )
1257
  if state["revision_count"] > 0:
1258
+ role_feedback = state["qa_role_feedback"].get("lazy_slacker", "")
1259
+ if role_feedback:
1260
+ content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
1261
+ else:
1262
+ content += f"\n\nQA feedback to address:\n{state['qa_report']}"
1263
  text = _llm_call(chat_model, _LAZY_SLACKER_SYSTEM, content)
1264
  state["lazy_slacker_output"] = text
1265
  state["draft_output"] = text
 
1276
  f"Planner instructions:\n{state['plan']}"
1277
  )
1278
  if state["revision_count"] > 0:
1279
+ role_feedback = state["qa_role_feedback"].get("black_metal_fundamentalist", "")
1280
+ if role_feedback:
1281
+ content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
1282
+ else:
1283
+ content += f"\n\nQA feedback to address:\n{state['qa_report']}"
1284
  text = _llm_call(chat_model, _BLACK_METAL_FUNDAMENTALIST_SYSTEM, content)
1285
  state["black_metal_fundamentalist_output"] = text
1286
  state["draft_output"] = text
 
1297
  f"Planner instructions:\n{state['plan']}"
1298
  )
1299
  if state["revision_count"] > 0:
1300
+ role_feedback = state["qa_role_feedback"].get("labour_union_rep", "")
1301
+ if role_feedback:
1302
+ content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
1303
+ else:
1304
+ content += f"\n\nQA feedback to address:\n{state['qa_report']}"
1305
  text = _llm_call(chat_model, _LABOUR_UNION_REP_SYSTEM, content)
1306
  state["labour_union_rep_output"] = text
1307
  state["draft_output"] = text
 
1318
  f"Planner instructions:\n{state['plan']}"
1319
  )
1320
  if state["revision_count"] > 0:
1321
+ role_feedback = state["qa_role_feedback"].get("ux_designer", "")
1322
+ if role_feedback:
1323
+ content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
1324
+ else:
1325
+ content += f"\n\nQA feedback to address:\n{state['qa_report']}"
1326
  text = _llm_call(chat_model, _UX_DESIGNER_SYSTEM, content)
1327
  state["ux_designer_output"] = text
1328
  state["draft_output"] = text
 
1339
  f"Planner instructions:\n{state['plan']}"
1340
  )
1341
  if state["revision_count"] > 0:
1342
+ role_feedback = state["qa_role_feedback"].get("doris", "")
1343
+ if role_feedback:
1344
+ content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
1345
+ else:
1346
+ content += f"\n\nQA feedback to address:\n{state['qa_report']}"
1347
  text = _llm_call(chat_model, _DORIS_SYSTEM, content)
1348
  state["doris_output"] = text
1349
  state["draft_output"] = text
 
1360
  f"Planner instructions:\n{state['plan']}"
1361
  )
1362
  if state["revision_count"] > 0:
1363
+ role_feedback = state["qa_role_feedback"].get("chairman_of_board", "")
1364
+ if role_feedback:
1365
+ content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
1366
+ else:
1367
+ content += f"\n\nQA feedback to address:\n{state['qa_report']}"
1368
  text = _llm_call(chat_model, _CHAIRMAN_SYSTEM, content)
1369
  state["chairman_of_board_output"] = text
1370
  state["draft_output"] = text
 
1381
  f"Planner instructions:\n{state['plan']}"
1382
  )
1383
  if state["revision_count"] > 0:
1384
+ role_feedback = state["qa_role_feedback"].get("maga_appointee", "")
1385
+ if role_feedback:
1386
+ content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
1387
+ else:
1388
+ content += f"\n\nQA feedback to address:\n{state['qa_report']}"
1389
  text = _llm_call(chat_model, _MAGA_APPOINTEE_SYSTEM, content)
1390
  state["maga_appointee_output"] = text
1391
  state["draft_output"] = text
 
1402
  f"Planner instructions:\n{state['plan']}"
1403
  )
1404
  if state["revision_count"] > 0:
1405
+ role_feedback = state["qa_role_feedback"].get("lawyer", "")
1406
+ if role_feedback:
1407
+ content += f"\n\nQA feedback specific to your contribution:\n{role_feedback}"
1408
+ else:
1409
+ content += f"\n\nQA feedback to address:\n{state['qa_report']}"
1410
  text = _llm_call(chat_model, _LAWYER_SYSTEM, content)
1411
  state["lawyer_output"] = text
1412
  state["draft_output"] = text
 
1477
  "labour_union_rep_output": "", "ux_designer_output": "", "doris_output": "",
1478
  "chairman_of_board_output": "", "maga_appointee_output": "", "lawyer_output": "",
1479
  "synthesis_output": "",
1480
+ "draft_output": "", "qa_report": "", "qa_role_feedback": {}, "qa_passed": False,
1481
  "revision_count": 0, "final_answer": "",
1482
  }
1483
 
 
1657
  1. Planner (if active) analyses the task and picks a primary specialist.
1658
  2. ALL active specialists run and contribute their own perspective.
1659
  3. Synthesizer summarises all perspectives, finds common ground, and produces a unified recommendation.
1660
+ 4. QA Tester (if active) reviews the synthesized output and provides targeted, per-role feedback
1661
+ so each specialist knows exactly what to improve in the next iteration.
1662
  5. Planner (if active) reviews QA result and either approves or requests a revision.
1663
+ 6. Repeat from step 2 if QA fails and retries remain — each specialist now receives their own
1664
+ targeted QA feedback and refines their proposition accordingly (iterative approach).
1665
  7. If max retries are reached, return best attempt with QA concerns.
1666
 
1667
  Args:
 
1719
  "synthesis_output": "",
1720
  "draft_output": "",
1721
  "qa_report": "",
1722
+ "qa_role_feedback": {},
1723
  "qa_passed": False,
1724
  "revision_count": 0,
1725
  "final_answer": "",
 
1783
 
1784
  # Step 3: QA reviews the specialist's draft (if enabled)
1785
  if qa_active:
1786
+ state = _step_qa(chat_model, state, trace, all_outputs)
1787
  else:
1788
  state["qa_passed"] = True
1789
  state["qa_report"] = "QA Tester is disabled — skipping quality review."