fable-engine 1.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. fable_compressor.py +356 -0
  2. fable_engine/__init__.py +1 -0
  3. fable_engine/actions/__init__.py +291 -0
  4. fable_engine/actions/cas.py +182 -0
  5. fable_engine/actions/deliberation.py +523 -0
  6. fable_engine/actions/fleet.py +807 -0
  7. fable_engine/actions/lifecycle.py +298 -0
  8. fable_engine/actions/scrapers.py +116 -0
  9. fable_engine/actions/system3.py +815 -0
  10. fable_engine/browser.py +824 -0
  11. fable_engine/cas.py +974 -0
  12. fable_engine/fable_session.json +510 -0
  13. fable_engine/guards.py +283 -0
  14. fable_engine/schema.py +714 -0
  15. fable_engine/scrapers/__init__.py +32 -0
  16. fable_engine/scrapers/arxiv.py +115 -0
  17. fable_engine/scrapers/base.py +386 -0
  18. fable_engine/scrapers/github.py +129 -0
  19. fable_engine/scrapers/reddit.py +154 -0
  20. fable_engine/scrapers/web.py +120 -0
  21. fable_engine/scrapers/x.py +125 -0
  22. fable_engine/scrapers/youtube.py +132 -0
  23. fable_engine/server.py +414 -0
  24. fable_engine/session.py +1819 -0
  25. fable_engine/test_server.py +1362 -0
  26. fable_engine/updater.py +541 -0
  27. fable_engine-1.3.1.dist-info/LICENSE +22 -0
  28. fable_engine-1.3.1.dist-info/METADATA +173 -0
  29. fable_engine-1.3.1.dist-info/RECORD +104 -0
  30. fable_engine-1.3.1.dist-info/WHEEL +5 -0
  31. fable_engine-1.3.1.dist-info/entry_points.txt +5 -0
  32. fable_engine-1.3.1.dist-info/top_level.txt +6 -0
  33. fable_mode/__init__.py +3 -0
  34. fable_mode/__main__.py +4 -0
  35. fable_mode/adapters.py +1014 -0
  36. fable_mode/installer.py +553 -0
  37. fable_mode/launcher.py +437 -0
  38. fable_mode/manifest.py +142 -0
  39. fable_mode/resources.json +114 -0
  40. fable_mode/safety.py +103 -0
  41. fable_mode_entry.py +10 -0
  42. fable_v2/__init__.py +146 -0
  43. fable_v2/adapters.py +151 -0
  44. fable_v2/coder_fleet/__init__.py +100 -0
  45. fable_v2/coder_fleet/ast_tools.py +158 -0
  46. fable_v2/coder_fleet/compute.py +199 -0
  47. fable_v2/coder_fleet/design_engine.py +1316 -0
  48. fable_v2/coder_fleet/diagnostics.py +293 -0
  49. fable_v2/coder_fleet/fleet_dispatcher.py +214 -0
  50. fable_v2/coder_fleet/mock_auditor.py +306 -0
  51. fable_v2/coder_fleet/mutation.py +216 -0
  52. fable_v2/coder_fleet/property_oracle.py +260 -0
  53. fable_v2/coder_fleet/receipt_attestor.py +122 -0
  54. fable_v2/coder_fleet/red_team_swarm.py +908 -0
  55. fable_v2/coder_fleet/test_harness.py +198 -0
  56. fable_v2/coder_fleet/vector_engine.py +1287 -0
  57. fable_v2/coder_fleet/visual.py +357 -0
  58. fable_v2/coder_fleet/workspace.py +153 -0
  59. fable_v2/cortical/__init__.py +20 -0
  60. fable_v2/cortical/plasticity_engine.py +992 -0
  61. fable_v2/execution_broker.py +811 -0
  62. fable_v2/proof_engine.py +1141 -0
  63. fable_v2/protocol.py +485 -0
  64. fable_v2/runtime.py +1010 -0
  65. fable_v2/system3/__init__.py +204 -0
  66. fable_v2/system3/causal.py +558 -0
  67. fable_v2/system3/dialectical.py +577 -0
  68. fable_v2/system3/evolution.py +503 -0
  69. fable_v2/system3/executive.py +338 -0
  70. fable_v2/system3/free_energy.py +479 -0
  71. fable_v2/system3/hyperbolic.py +555 -0
  72. fable_v2/system3/induction.py +336 -0
  73. fable_v2/system3/kripke.py +548 -0
  74. fable_v2/system3/oracle.py +745 -0
  75. fable_v2/verifiers.py +72 -0
  76. tests/__init__.py +1 -0
  77. tests/test_anti_loop_circuit_breaker.py +64 -0
  78. tests/test_auto_updater.py +407 -0
  79. tests/test_coder_fleet.py +535 -0
  80. tests/test_delegation_compiler.py +54 -0
  81. tests/test_descriptor_boundaries.py +126 -0
  82. tests/test_design_engine.py +603 -0
  83. tests/test_epistemic_evidence_validator.py +66 -0
  84. tests/test_execution_broker.py +233 -0
  85. tests/test_fable_v2.py +406 -0
  86. tests/test_fleet_transitions.py +116 -0
  87. tests/test_fsm_redteam_evolution.py +406 -0
  88. tests/test_goal_rubric_and_pipeline.py +367 -0
  89. tests/test_hebbian_plasticity.py +585 -0
  90. tests/test_packaging_runtime.py +194 -0
  91. tests/test_proof_engine.py +259 -0
  92. tests/test_red_team_swarm.py +645 -0
  93. tests/test_redteam_remediation.py +169 -0
  94. tests/test_registration_transaction.py +375 -0
  95. tests/test_requested_regressions.py +467 -0
  96. tests/test_scrapers.py +370 -0
  97. tests/test_server_actions.py +93 -0
  98. tests/test_server_frontier_actions.py +269 -0
  99. tests/test_server_protocol.py +88 -0
  100. tests/test_stealth_browser.py +970 -0
  101. tests/test_system3.py +381 -0
  102. tests/test_system3_deep_integration.py +385 -0
  103. tests/test_system3_frontier.py +436 -0
  104. tests/test_vector_engine.py +608 -0
@@ -0,0 +1,807 @@
1
+ """Fleet quality rubrics, red team swarm review, cortical plasticity, and auto-update handlers."""
2
+ from __future__ import annotations
3
+
4
+ import collections
5
+ from dataclasses import asdict
6
+ import json
7
+ import logging
8
+ import os
9
+ import sys
10
+ import time
11
+ from pathlib import Path
12
+ from typing import Any, Dict, List, Optional, Tuple, Union
13
+
14
+ logger = logging.getLogger("fable-engine.actions.fleet")
15
+
16
+ from fable_engine.session import (
17
+ ACTIVE_SESSIONS,
18
+ PHASES,
19
+ SESSIONS_DIR,
20
+ SILENT_DELIBERATION_REMINDER,
21
+ FableSession,
22
+ SessionState,
23
+ _validate_session_name,
24
+ _validate_time_budget,
25
+ get_or_load_session,
26
+ get_plasticity_engine,
27
+ get_red_team_swarm,
28
+ )
29
+ from fable_engine.updater import AutoUpdater
30
+
31
+ def _get_swarm():
32
+ return get_red_team_swarm()
33
+
34
+ def _get_cortex():
35
+ return get_plasticity_engine()
36
+
37
+ def _handle_set_goal_rubric(arguments: Dict[str, Any]) -> str:
38
+ action = arguments.get("action", "").strip().lower()
39
+ session_name = arguments.get("session_name", "").strip()
40
+ session: Optional[FableSession] = None
41
+ if not session_name:
42
+ return "Error: 'session_name' is required for action 'set_goal_rubric'."
43
+ task_objective = arguments.get("task_objective") or arguments.get("objective") or ""
44
+ criteria = arguments.get("criteria") or arguments.get("items") or arguments.get("rubric_items")
45
+ if not criteria:
46
+ return "Error: 'criteria' (list of rubric criteria items/pointers) is required for 'set_goal_rubric'."
47
+ target_score = arguments.get("target_score", 0.95)
48
+ rubric_id = arguments.get("rubric_id")
49
+ meta = arguments.get("metadata")
50
+
51
+ session = get_or_load_session(session_name)
52
+ rubric = session.set_goal_rubric(
53
+ task_objective=task_objective,
54
+ criteria=criteria,
55
+ target_score=target_score,
56
+ rubric_id=rubric_id,
57
+ metadata=meta
58
+ )
59
+ session.save()
60
+
61
+ items_preview = "\n".join([
62
+ f"- `[{it['pointer_id']}]` (wt: {it['weight']:.1f}, score: {it['score']:.2f}, satisfied: {'✅' if it['satisfied'] else '⏳'}): {it['description']}"
63
+ for it in rubric["items"]
64
+ ])
65
+
66
+ return (
67
+ f"### 🎯 Goal Rubric Initialized (`{rubric['rubric_id']}`)\n\n"
68
+ f"- **Session**: `{session.session_name}`\n"
69
+ f"- **Objective**: {rubric['task_objective']}\n"
70
+ f"- **Target Goal Score**: `{rubric['target_score'] * 100:.1f}%` (Strict Threshold: >= 95%)\n"
71
+ f"- **Current Composite Score**: `{rubric['current_score'] * 100:.1f}%`\n"
72
+ f"- **Status**: `{rubric['status'].upper()}`\n"
73
+ f"- **Criteria Pointers Count**: `{len(rubric['items'])}`\n\n"
74
+ f"#### 📋 Criteria Pointers Breakdown:\n"
75
+ f"{items_preview}"
76
+ f"{SILENT_DELIBERATION_REMINDER if session.execution_locked else ''}"
77
+ )
78
+
79
+
80
+ def _handle_evaluate_goal_rubric(arguments: Dict[str, Any]) -> str:
81
+ action = arguments.get("action", "").strip().lower()
82
+ session_name = arguments.get("session_name", "").strip()
83
+ session: Optional[FableSession] = None
84
+ if not session_name:
85
+ return "Error: 'session_name' is required for action 'evaluate_goal_rubric'."
86
+ rubric_id = arguments.get("rubric_id")
87
+ item_evaluations = arguments.get("item_evaluations") or arguments.get("evaluations") or arguments.get("items")
88
+
89
+ session = get_or_load_session(session_name)
90
+ rubric = session.evaluate_goal_rubric(
91
+ rubric_id=rubric_id,
92
+ item_evaluations=item_evaluations
93
+ )
94
+ session.save()
95
+
96
+ status_badge = "🟢 ACHIEVED (>= 95%)" if rubric["status"] == "achieved" else "🟡 IN_PROGRESS (< 95%)"
97
+ items_preview = "\n".join([
98
+ f"- `[{it['pointer_id']}]` ({it['score']*100:.0f}%, {'✅ SATISFIED' if it['satisfied'] else '⏳ PENDING'}): {it['description']}" +
99
+ (f" [Receipt: `{it['evidence_receipt_id']}`]" if it.get('evidence_receipt_id') else "")
100
+ for it in rubric["items"]
101
+ ])
102
+
103
+ return (
104
+ f"### 📈 Goal Rubric Evaluation (`{rubric['rubric_id']}`)\n\n"
105
+ f"- **Session**: `{session.session_name}`\n"
106
+ f"- **Composite Goal Score**: `{rubric['current_score'] * 100:.2f}%`\n"
107
+ f"- **Target Score**: `{rubric['target_score'] * 100:.1f}%`\n"
108
+ f"- **Status**: `{status_badge}`\n\n"
109
+ f"#### 📊 Criteria Pointers Status:\n"
110
+ f"{items_preview}"
111
+ f"{SILENT_DELIBERATION_REMINDER if session.execution_locked else ''}"
112
+ )
113
+
114
+
115
+ def _handle_get_goal_rubric(arguments: Dict[str, Any]) -> str:
116
+ action = arguments.get("action", "").strip().lower()
117
+ session_name = arguments.get("session_name", "").strip()
118
+ session: Optional[FableSession] = None
119
+ if not session_name:
120
+ return "Error: 'session_name' is required for action 'get_goal_rubric'."
121
+ rubric_id = arguments.get("rubric_id")
122
+
123
+ session = get_or_load_session(session_name)
124
+ rubric = session.get_goal_rubric(rubric_id=rubric_id)
125
+ if not rubric:
126
+ return f"### ⚠️ No Goal Rubric Found\n\nSession `{session.session_name}` has no registered goal rubrics."
127
+
128
+ status_badge = "🟢 ACHIEVED" if rubric["status"] == "achieved" else "🟡 IN_PROGRESS"
129
+ items_preview = "\n".join([
130
+ f"- `[{it['pointer_id']}]` (wt: {it['weight']:.1f}, score: {it['score']*100:.0f}%, {'✅' if it['satisfied'] else '⏳'}): {it['description']}" +
131
+ (f" (Verifier: `{it['verifier_command']}`)" if it.get('verifier_command') else "")
132
+ for it in rubric["items"]
133
+ ])
134
+
135
+ return (
136
+ f"### 📋 Goal Rubric Details (`{rubric['rubric_id']}`)\n\n"
137
+ f"- **Session**: `{session.session_name}`\n"
138
+ f"- **Task Objective**: {rubric['task_objective']}\n"
139
+ f"- **Target Score**: `{rubric['target_score'] * 100:.1f}%`\n"
140
+ f"- **Current Score**: `{rubric['current_score'] * 100:.2f}%`\n"
141
+ f"- **Status**: `{status_badge}`\n\n"
142
+ f"#### 📑 Criteria Breakdown:\n"
143
+ f"{items_preview}"
144
+ f"{SILENT_DELIBERATION_REMINDER if session.execution_locked else ''}"
145
+ )
146
+
147
+
148
+ def _handle_register_automation_pipeline(arguments: Dict[str, Any]) -> str:
149
+ action = arguments.get("action", "").strip().lower()
150
+ session_name = arguments.get("session_name", "").strip()
151
+ session: Optional[FableSession] = None
152
+ if not session_name:
153
+ return "Error: 'session_name' is required for action 'register_automation_pipeline'."
154
+ name = arguments.get("name") or arguments.get("pipeline_name") or ""
155
+ if not name:
156
+ return "Error: 'name' is required for 'register_automation_pipeline'."
157
+ pipeline_type = arguments.get("pipeline_type", "closed_loop")
158
+ generator_command = arguments.get("generator_command") or arguments.get("generator_cmd") or ""
159
+ evaluator_command = arguments.get("evaluator_command") or arguments.get("evaluator_cmd") or ""
160
+ target_threshold = arguments.get("target_threshold") if arguments.get("target_threshold") is not None else arguments.get("target_score", 0.95)
161
+ max_iterations = arguments.get("max_iterations", 10)
162
+ meta = arguments.get("metadata")
163
+
164
+ session = get_or_load_session(session_name)
165
+ pipe = session.register_automation_pipeline(
166
+ name=name,
167
+ pipeline_type=pipeline_type,
168
+ generator_command=generator_command,
169
+ evaluator_command=evaluator_command,
170
+ target_threshold=target_threshold,
171
+ max_iterations=max_iterations,
172
+ metadata=meta
173
+ )
174
+ session.save()
175
+
176
+ return (
177
+ f"### ⚙️ Autonomous Pipeline Registered (`{pipe['pipeline_id']}`)\n\n"
178
+ f"- **Session**: `{session.session_name}`\n"
179
+ f"- **Pipeline Name**: `{pipe['name']}`\n"
180
+ f"- **Pipeline Type**: `{pipe['pipeline_type']}`\n"
181
+ f"- **Generator Command**: `{pipe['generator_command'] or 'N/A'}`\n"
182
+ f"- **Evaluator Command**: `{pipe['evaluator_command'] or 'N/A'}`\n"
183
+ f"- **Target Threshold**: `{pipe['target_threshold'] * 100:.1f}%`\n"
184
+ f"- **Max Iterations**: `{pipe['max_iterations']}`\n"
185
+ f"- **Status**: `ACTIVE 🚀`"
186
+ f"{SILENT_DELIBERATION_REMINDER if session.execution_locked else ''}"
187
+ )
188
+
189
+
190
+ def _handle_red_team_code_review(arguments: Dict[str, Any]) -> str:
191
+ action = arguments.get("action", "").strip().lower()
192
+ session_name = arguments.get("session_name", "").strip()
193
+ if not session_name:
194
+ return "Error: 'session_name' is required for action 'red_team_code_review'."
195
+
196
+ target_name = arguments.get("target_name", "system")
197
+ code_snippet = None
198
+ for k in ("target_code", "code_snippet", "code", "target_callable"):
199
+ if k in arguments and arguments[k] is not None:
200
+ code_snippet = arguments[k]
201
+ break
202
+
203
+ if code_snippet is not None and not callable(code_snippet):
204
+ return (
205
+ "Error: Source-code strings cannot be evaluated in-process for security reasons. "
206
+ "Dynamic source-code execution is disabled for public actions until an isolated sandbox executor is configured."
207
+ )
208
+
209
+ custom_hypotheses = arguments.get("custom_hypotheses") or arguments.get("hypotheses")
210
+ output_path = arguments.get("output_path")
211
+
212
+ session = get_or_load_session(session_name)
213
+ try:
214
+ report = _get_swarm().run_full_review_cycle(
215
+ target_callable=code_snippet,
216
+ target_name=target_name,
217
+ custom_hypotheses=custom_hypotheses,
218
+ )
219
+ report_dict = report.to_dict()
220
+ if int(report_dict.get("broken_count", 0)) == 0:
221
+ if not session._fresh_stage_records()[2]:
222
+ return "Error: A current immutable reviewed_change_id is required before accepting a clean review."
223
+ change_id = str(arguments.get("reviewed_change_id") or session.derive_reviewed_change_id()).strip()
224
+ report_dict["report_origin"] = "red_team_swarm"
225
+ report_dict["reviewed_change_id"] = change_id
226
+ report_dict["attack_vector_results"] = session._attack_vector_results(report_dict)
227
+ report_dict["red_team_receipt"] = session.issue_red_team_receipt(report_dict, change_id)
228
+ session.record_breakage_report(report_dict)
229
+ except (TypeError, ValueError) as exc:
230
+ return f"Error: Cannot record red-team report: {exc}"
231
+ session.save()
232
+
233
+ md_report = _get_swarm().document_breakage(report, output_path=output_path)
234
+ return (
235
+ f"{md_report}\n\n"
236
+ f"- **Session Recorded**: `{session.session_name}`\n"
237
+ f"- **Total Breakage Reports in Session**: `{len(session.breakage_reports)}`"
238
+ f"{SILENT_DELIBERATION_REMINDER if session.execution_locked else ''}"
239
+ )
240
+
241
+
242
+ def _handle_record_breakage_report(arguments: Dict[str, Any]) -> str:
243
+ action = arguments.get("action", "").strip().lower()
244
+ session_name = arguments.get("session_name", "").strip()
245
+ session: Optional[FableSession] = None
246
+ if not session_name:
247
+ return "Error: 'session_name' is required for action 'record_breakage_report'."
248
+ report_data = arguments.get("report") or arguments.get("report_data") or {}
249
+ if not report_data and (arguments.get("findings") is not None or arguments.get("broken_scenarios") is not None):
250
+ raw_findings = arguments.get("findings") if arguments.get("findings") is not None else arguments.get("broken_scenarios", [])
251
+ broken_cnt = arguments.get("broken_count")
252
+ if broken_cnt is None:
253
+ broken_cnt = sum(1 for f in raw_findings if (f.get("broken", True) if isinstance(f, dict) else getattr(f, "broken", True)))
254
+ report_data = {
255
+ "report_id": arguments.get("report_id", f"report_{int(time.time())}"),
256
+ "target_name": arguments.get("target_name", "system"),
257
+ "total_probes": arguments.get("total_probes", len(raw_findings)),
258
+ "broken_count": int(broken_cnt),
259
+ "passed": arguments.get("passed", int(broken_cnt) == 0),
260
+ "findings": raw_findings,
261
+ "created_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
262
+ "remediation_directives": arguments.get("remediation_directives", [])
263
+ }
264
+ if not report_data:
265
+ return "Error: 'report', 'report_data', 'findings', or 'broken_scenarios' is required for 'record_breakage_report'."
266
+
267
+ session = get_or_load_session(session_name)
268
+ try:
269
+ session.record_breakage_report(dict(report_data))
270
+ broken_count = int(report_data.get("broken_count", 0))
271
+ except (TypeError, ValueError) as exc:
272
+ return f"Error: Cannot record breakage report: {exc}"
273
+ session.save()
274
+
275
+ if broken_count > 0:
276
+ directives = report_data.get("remediation_directives") or [
277
+ f"Remediate {item.get('hypothesis', item.get('scenario_id', 'breakage'))}"
278
+ for item in session.active_breakages
279
+ ]
280
+ directives_list = "\n".join(f"- {directive}" for directive in directives)
281
+ order_msg = f"TASK REJECTED: {broken_count} breakages detected. Deploy subagent to fix findings."
282
+ escalation = session.current_state == SessionState.ESCALATION_UNRESOLVED_BREAKAGES
283
+ arbitration_message = (
284
+ "> **Human architecture arbitration required: remediation bounds exceeded.**\n"
285
+ if escalation else ""
286
+ )
287
+ return (
288
+ f"### 🚨 {order_msg}\n\n"
289
+ f"> [!CAUTION]\n> **{order_msg}**\n\n"
290
+ f"- **Session**: `{session.session_name}`\n"
291
+ f"- **Current State**: `{session.current_state.value}` 🔴\n"
292
+ f"- **Broken Count**: `{broken_count}`\n"
293
+ f"- **Active Breakages Tracked**: `{len(session.active_breakages)}`\n"
294
+ f"- **Remediation Attempts**: `{session.remediation_attempt_count}`\n"
295
+ f"{arbitration_message}"
296
+ f"\n#### 🛠️ Structured Remediation Directives:\n{directives_list}\n"
297
+ f"{SILENT_DELIBERATION_REMINDER if session.execution_locked else ''}"
298
+ )
299
+
300
+ return (
301
+ "### 🛡️ TASK COMPLETED: 0 breakages remain. Code sealed.\n\n"
302
+ "🟢 **TASK COMPLETED: 0 breakages remain. Code sealed.**\n\n"
303
+ f"- **Session**: `{session.session_name}`\n"
304
+ f"- **Current State**: `{session.current_state.value}` 🟢\n"
305
+ "- **Broken Count**: `0`\n"
306
+ "- **Status**: Verified resilient. Ready for `evolve_cortex`."
307
+ f"{SILENT_DELIBERATION_REMINDER if session.execution_locked else ''}"
308
+ )
309
+
310
+
311
+ def _handle_verify_red_team_remediation(arguments: Dict[str, Any]) -> str:
312
+ action = arguments.get("action", "").strip().lower()
313
+ session_name = arguments.get("session_name", "").strip()
314
+ if not session_name:
315
+ return "Error: 'session_name' is required for action 'verify_red_team_remediation'."
316
+
317
+ remediated_code = None
318
+ for k in ("remediated_code", "target_code", "code_snippet", "code", "target_callable"):
319
+ if k in arguments and arguments[k] is not None:
320
+ remediated_code = arguments[k]
321
+ break
322
+
323
+ if remediated_code is not None and not callable(remediated_code):
324
+ return (
325
+ "Error: Source-code strings cannot be evaluated in-process for security reasons. "
326
+ "Dynamic source-code execution is disabled for public actions until an isolated sandbox executor is configured."
327
+ )
328
+
329
+ session = get_or_load_session(session_name)
330
+
331
+ report_id = arguments.get("report_id")
332
+ prior_report = arguments.get("prior_report")
333
+
334
+ if not prior_report:
335
+ if report_id:
336
+ prior_report = next((r for r in session.breakage_reports if r.get("report_id") == str(report_id).strip()), None)
337
+ elif session.breakage_reports:
338
+ prior_report = session.breakage_reports[-1]
339
+
340
+ if not prior_report:
341
+ return "Error: No prior breakage report found to verify. Provide 'report_id' or 'prior_report'."
342
+ try:
343
+ timeout_sec = float(arguments.get("timeout_seconds", 3.0))
344
+ all_fixed, verification = _get_swarm().verify_remediation(
345
+ target_callable=remediated_code,
346
+ prior_report=prior_report,
347
+ timeout_seconds=timeout_sec,
348
+ )
349
+ report = verification.to_dict()
350
+ # A seal requires a fresh review across all five vectors, not only the
351
+ # scenarios that failed in the prior report.
352
+ if all_fixed and verification.broken_count == 0:
353
+ report = _get_swarm().run_full_review_cycle(
354
+ target_callable=remediated_code,
355
+ target_name=verification.target_name,
356
+ auto_consolidate=False,
357
+ ).to_dict()
358
+ if int(report.get("broken_count", 0)) == 0:
359
+ if not session._fresh_stage_records()[2]:
360
+ return "Error: A current immutable reviewed_change_id is required before accepting a clean remediation."
361
+ change_id = str(
362
+ arguments.get("reviewed_change_id")
363
+ or report.get("reviewed_change_id")
364
+ or session.derive_reviewed_change_id()
365
+ ).strip()
366
+ report["report_origin"] = "red_team_swarm"
367
+ report["reviewed_change_id"] = change_id
368
+ report["attack_vector_results"] = session._attack_vector_results(report)
369
+ report["red_team_receipt"] = session.issue_red_team_receipt(report, change_id)
370
+ session.record_breakage_report(report)
371
+ except (TypeError, ValueError) as exc:
372
+ return f"Error: Cannot record remediation verification: {exc}"
373
+
374
+ session.save()
375
+ broken_count = int(report.get("broken_count", 0))
376
+ if broken_count > 0:
377
+ directives = report.get("remediation_directives", [])
378
+ directives_list = "\n".join(f"- {directive}" for directive in directives)
379
+ order_msg = f"TASK REJECTED: {broken_count} breakages detected. Deploy subagent to fix findings."
380
+ escalation = session.current_state == SessionState.ESCALATION_UNRESOLVED_BREAKAGES
381
+ arbitration_message = (
382
+ "> **Human architecture arbitration required: remediation bounds exceeded.**\n"
383
+ if escalation else ""
384
+ )
385
+ return (
386
+ f"### 🚨 {order_msg}\n\n> [!CAUTION]\n> **{order_msg}**\n\n"
387
+ f"- **Session**: `{session.session_name}`\n"
388
+ f"- **Current State**: `{session.current_state.value}` 🔴 "
389
+ f"(Attempt {session.remediation_attempt_count})\n"
390
+ f"- **Remaining Breakages**: `{broken_count}`\n"
391
+ f"{arbitration_message}\n"
392
+ f"#### 🛠️ Directives for Next Remediation Cycle:\n{directives_list}\n"
393
+ f"{SILENT_DELIBERATION_REMINDER if session.execution_locked else ''}"
394
+ )
395
+
396
+ completed_msg = "TASK COMPLETED: 0 breakages remain. Code sealed."
397
+ return (
398
+ f"### 🛡️ {completed_msg}\n\n🟢 **{completed_msg}**\n\n"
399
+ f"- **Session**: `{session.session_name}`\n"
400
+ f"- **Current State**: `{session.current_state.value}` 🟢\n"
401
+ "- **Broken Count**: `0`\n"
402
+ f"- **Remediation Iterations**: `{session.iteration_count}`\n\n"
403
+ "> [!NOTE]\n> All prior adversarial breakages resolved with zero regressions. Session is in `SEALED` state. Automatically proceed or advance to `EVOLVED` state via `evolve_cortex`."
404
+ f"{SILENT_DELIBERATION_REMINDER if session.execution_locked else ''}"
405
+ )
406
+
407
+
408
+ def _handle_evolve_cortex(arguments: Dict[str, Any]) -> str:
409
+ action = arguments.get("action", "").strip().lower()
410
+ session_name = arguments.get("session_name", "").strip()
411
+ session: Optional[FableSession] = None
412
+ if not session_name:
413
+ return "Error: 'session_name' is required for action 'evolve_cortex'."
414
+ session = get_or_load_session(session_name)
415
+
416
+ if session.current_state not in (SessionState.SEALED, SessionState.EVOLVED):
417
+ return f"Error: evolve_cortex rejected: Session must be in SEALED or EVOLVED state (current state: {session.current_state.value})."
418
+
419
+ domain = arguments.get("domain") or "python"
420
+ task_id = arguments.get("task_id") or session.session_id
421
+
422
+ neutralized_scenarios = arguments.get("broken_scenarios") or []
423
+ if not neutralized_scenarios:
424
+ for rep in session.breakage_reports:
425
+ for f in rep.get("findings", []):
426
+ if isinstance(f, dict) and f.get("broken"):
427
+ neutralized_scenarios.append(f)
428
+ elif hasattr(f, "broken") and f.broken:
429
+ neutralized_scenarios.append(f.to_dict() if hasattr(f, "to_dict") else asdict(f))
430
+ for hist in session.remediation_history:
431
+ for b in hist.get("breakages", []):
432
+ if b not in neutralized_scenarios:
433
+ neutralized_scenarios.append(b)
434
+
435
+ co_activated_nodes = arguments.get("co_activated_nodes") or ["mutation", "test_harness", "red_team_swarm", "property_oracle"]
436
+
437
+ cortex = _get_cortex()
438
+ evo_receipt = cortex.consolidate_task(
439
+ task_id=task_id,
440
+ success=True,
441
+ domain=domain,
442
+ broken_scenarios=neutralized_scenarios,
443
+ co_activated_nodes=co_activated_nodes,
444
+ )
445
+ lobe_path = cortex._get_lobe_path(domain).resolve()
446
+
447
+ session.transition_to(SessionState.EVOLVED, "Cortical evolution consolidation completed")
448
+ session.save()
449
+
450
+ antibodies_list = "\n".join([f"- `ab_{domain}_{s.get('scenario_id', 'unknown')}`: {s.get('hypothesis', 'Neutralized breakage')}" for s in neutralized_scenarios]) if neutralized_scenarios else "- Antibodies consolidated into cortical lobe."
451
+ weights_table = "\n".join([f"| `{k}` | `{v:.4f}` | `+0.10 * A_domain * A_node (LTP)` |" for k, v in evo_receipt.get("synaptic_weights", {}).items()])
452
+
453
+ return (
454
+ f"### 🧬 Cortical Evolution Receipt: EVOLVED\n\n"
455
+ f"- **Session**: `{session.session_name}`\n"
456
+ f"- **Current State**: `EVOLVED` 🌟\n"
457
+ f"- **Domain Lobe**: `{domain}` (`{lobe_path}`)\n"
458
+ f"- **Task ID**: `{task_id}`\n"
459
+ f"- **Plasticity Mode**: `LTP (Long-Term Potentiation)` (Score: +1.0)\n"
460
+ f"- **Antibodies Added**: `{evo_receipt.get('antibodies_added', 0)}`\n"
461
+ f"- **Total Lobe Antibodies**: `{evo_receipt.get('total_antibodies', 0)}`\n"
462
+ f"- **A_domain**: `{evo_receipt.get('A_domain', 0.80)}`\n\n"
463
+ f"#### 🛡️ Synthesized Heuristic Antibodies:\n{antibodies_list}\n\n"
464
+ f"#### ⚡ Potentiated Synaptic Weights:\n"
465
+ f"| Node | Potentiated Weight | Hebbian Rule |\n"
466
+ f"| :--- | :---: | :--- |\n"
467
+ f"{weights_table}\n\n"
468
+ f"> [!TIP]\n"
469
+ f"> Cortical lobe `{lobe_path}` successfully evolved and persisted to disk."
470
+ f"{SILENT_DELIBERATION_REMINDER if session.execution_locked else ''}"
471
+ )
472
+
473
+
474
+ def _handle_cortical_define_lobe(arguments: Dict[str, Any]) -> str:
475
+ action = arguments.get("action", "").strip().lower()
476
+ session_name = arguments.get("session_name", "").strip()
477
+ session: Optional[FableSession] = None
478
+ name = arguments.get("name") or arguments.get("lobe_name") or ""
479
+ if not name:
480
+ return "Error: 'name' or 'lobe_name' is required for action 'cortical_define_lobe'."
481
+ description = arguments.get("description") or arguments.get("desc") or ""
482
+ initial_heuristics = arguments.get("initial_heuristics") or arguments.get("heuristics") or []
483
+ initial_synaptic_weights = arguments.get("initial_synaptic_weights") or arguments.get("synaptic_weights") or {}
484
+
485
+ lobe = _get_cortex().define_cortical_lobe(
486
+ name=str(name),
487
+ description=str(description),
488
+ initial_heuristics=initial_heuristics if isinstance(initial_heuristics, list) else [str(initial_heuristics)],
489
+ initial_synaptic_weights=initial_synaptic_weights if isinstance(initial_synaptic_weights, dict) else {},
490
+ )
491
+ session = get_or_load_session(session_name) if session_name else None
492
+
493
+ md_output = (
494
+ f"### 🧠 Cortical Lobe Sprouted: `{lobe.name}`\n\n"
495
+ f"- **Name**: `{lobe.name}`\n"
496
+ f"- **Description**: {lobe.description}\n"
497
+ f"- **Domain**: `{lobe.domain}`\n"
498
+ f"- **Heuristics Initialized**: `{len(lobe.specialized_heuristics)}`\n"
499
+ f"- **Synaptic Nodes**: `{len(lobe.synaptic_weights)}`\n"
500
+ f"- **File Path**: `skills/fable-mode/cortex/{lobe.name}.md`\n"
501
+ )
502
+ if session and session.execution_locked:
503
+ md_output += SILENT_DELIBERATION_REMINDER
504
+ return md_output
505
+
506
+
507
+ def _handle_cortical_list_lobes(arguments: Dict[str, Any]) -> str:
508
+ action = arguments.get("action", "").strip().lower()
509
+ session_name = arguments.get("session_name", "").strip()
510
+ session: Optional[FableSession] = None
511
+ lobes = _get_cortex().list_cortical_lobes()
512
+ session = get_or_load_session(session_name) if session_name else None
513
+
514
+ lines = [
515
+ "### 🧠 Available Cortical Lobes",
516
+ "",
517
+ f"Total Lobes: `{len(lobes)}`",
518
+ "",
519
+ "| Lobe Name | Description | Activations | Antibodies | Heuristics |",
520
+ "| :--- | :--- | :--- | :--- | :--- |",
521
+ ]
522
+ for l in lobes:
523
+ desc = l['description'][:60] + "..." if len(l['description']) > 60 else (l['description'] or "—")
524
+ lines.append(f"| `{l['name']}` | {desc} | `{l['activation_count']}` | `{l['antibody_count']}` | `{l['heuristic_count']}` |")
525
+
526
+ md_output = "\n".join(lines)
527
+ if session and session.execution_locked:
528
+ md_output += SILENT_DELIBERATION_REMINDER
529
+ return md_output
530
+
531
+
532
+ def _handle_check_auto_update(arguments: Dict[str, Any]) -> str:
533
+ action = arguments.get("action", "").strip().lower()
534
+ session_name = arguments.get("session_name", "").strip()
535
+ session: Optional[FableSession] = None
536
+ if AutoUpdater is None:
537
+ return "Error: AutoUpdater module is unavailable."
538
+ updater = AutoUpdater()
539
+ res = updater.check_for_updates()
540
+ session = get_or_load_session(session_name) if session_name else None
541
+ lines = [
542
+ "### 🔄 Fable Autonomous Auto-Updater Status",
543
+ "",
544
+ f"- **Update Available**: `{res.get('update_available', False)}`",
545
+ f"- **Local Commit**: `{res.get('local_commit', 'unknown')}`",
546
+ f"- **Remote Commit**: `{res.get('remote_commit', 'unknown')}`",
547
+ f"- **Offline / Standalone**: `{res.get('offline', False)}`",
548
+ f"- **Status**: {res.get('message', '')}",
549
+ ]
550
+ md_output = "\n".join(lines)
551
+ if session and session.execution_locked:
552
+ md_output += SILENT_DELIBERATION_REMINDER
553
+ return md_output
554
+
555
+
556
+ def _handle_apply_auto_update(arguments: Dict[str, Any]) -> str:
557
+ action = arguments.get("action", "").strip().lower()
558
+ session_name = arguments.get("session_name", "").strip()
559
+ session: Optional[FableSession] = None
560
+ if AutoUpdater is None:
561
+ return "Error: AutoUpdater module is unavailable."
562
+ preserve_cortex = arguments.get("preserve_cortex", True)
563
+ if isinstance(preserve_cortex, str):
564
+ preserve_cortex = preserve_cortex.lower() not in ("false", "0", "no")
565
+ updater = AutoUpdater()
566
+ res = updater.apply_update(preserve_cortex=preserve_cortex)
567
+ session = get_or_load_session(session_name) if session_name else None
568
+ status_emoji = "✅" if res.get("success") else "⚠️"
569
+ targets = res.get("synced_targets", [])
570
+ synced_str = ", ".join(f"`{t}`" for t in targets) if targets else "None"
571
+ preserved = res.get("preserved_lobes", [])
572
+ pres_str = ", ".join(f"`{p}`" for p in preserved) if preserved else "None"
573
+ lines = [
574
+ f"### {status_emoji} Fable Autonomous Auto-Updater Applied",
575
+ "",
576
+ f"- **Success**: `{res.get('success', False)}`",
577
+ f"- **Updated**: `{res.get('updated', False)}`",
578
+ f"- **Message**: {res.get('message', '')}",
579
+ f"- **Preserved Cortical Lobes**: {pres_str}",
580
+ f"- **Host Targets Synced**: {synced_str}",
581
+ ]
582
+ md_output = "\n".join(lines)
583
+ if session and session.execution_locked:
584
+ md_output += SILENT_DELIBERATION_REMINDER
585
+ return md_output
586
+
587
+
588
+ try:
589
+ from fable_v2.coder_fleet.design_engine import (
590
+ AestheticArchetype,
591
+ DesignDials,
592
+ DesignEngine,
593
+ HAUTE_THEMES,
594
+ )
595
+ except ImportError:
596
+ DesignEngine = None
597
+ HAUTE_THEMES = {}
598
+
599
+ _GLOBAL_DESIGN_ENGINE = None
600
+
601
+ def _get_design_engine():
602
+ global _GLOBAL_DESIGN_ENGINE
603
+ if _GLOBAL_DESIGN_ENGINE is None and DesignEngine is not None:
604
+ _GLOBAL_DESIGN_ENGINE = DesignEngine()
605
+ return _GLOBAL_DESIGN_ENGINE
606
+
607
+
608
+ def _handle_audit_anti_slop(arguments: Dict[str, Any]) -> str:
609
+ code = arguments.get("code") or arguments.get("content") or arguments.get("html") or arguments.get("source") or ""
610
+ file_path = arguments.get("file_path", "")
611
+ session_name = arguments.get("session_name", "").strip()
612
+ session = get_or_load_session(session_name) if session_name else None
613
+ engine = _get_design_engine()
614
+ if engine is None:
615
+ return "Error: DesignEngine module is unavailable."
616
+
617
+ res = engine.audit_anti_slop(code, file_path=file_path)
618
+ status_badge = "🟢 CLEAN (ZERO AI SLOP)" if res["clean"] else f"🔴 FAILED ({res['total_violations']} VIOLATIONS)"
619
+ lines = [
620
+ f"### 🛡️ Anti-Slop Design Audit: {status_badge}",
621
+ "",
622
+ f"- **Anti-Slop Score**: `{res['score'] * 100:.1f}%`",
623
+ f"- **Clean**: `{res['clean']}`",
624
+ f"- **Fatal Violations**: `{res['fatal_count']}`",
625
+ f"- **High Violations**: `{res['high_count']}`",
626
+ f"- **Medium Violations**: `{res['medium_count']}`",
627
+ "",
628
+ ]
629
+ if res["violations"]:
630
+ lines.append("#### ⚠️ Violations Breakdown:")
631
+ for v in res["violations"]:
632
+ lines.append(f"- **[{v['severity']}] `{v['rule_id']}`** (Line {v['line_number'] or 'N/A'}): {v['message']}")
633
+ lines.append(f" - *Snippet*: `{v['snippet']}`")
634
+ lines.append(f" - *Remedy*: {v['remedy']}")
635
+ else:
636
+ lines.append("✅ **All Anti-Slop Gates Passed**: Zero purple blobs, zero centered 3-card boilerplates, zero LLM marketing fluff, zero fake div dots, zero viewport instability.")
637
+
638
+ md_output = "\n".join(lines)
639
+ if session and session.execution_locked:
640
+ md_output += SILENT_DELIBERATION_REMINDER
641
+ return md_output
642
+
643
+
644
+ def _handle_infer_design_brief(arguments: Dict[str, Any]) -> str:
645
+ prompt = arguments.get("prompt") or arguments.get("user_prompt") or arguments.get("brief") or ""
646
+ dials_override = arguments.get("dials_override") or arguments.get("dials")
647
+ archetype_override = arguments.get("archetype_override") or arguments.get("archetype")
648
+ session_name = arguments.get("session_name", "").strip()
649
+ session = get_or_load_session(session_name) if session_name else None
650
+ engine = _get_design_engine()
651
+ if engine is None:
652
+ return "Error: DesignEngine module is unavailable."
653
+
654
+ res = engine.infer_design_brief(prompt, dials_override=dials_override, archetype_override=archetype_override)
655
+ dials = res["dials"]
656
+ palette = res["palette"]
657
+ typo = res["typography"]
658
+ layout = res["layout_blueprint"]
659
+ lines = [
660
+ "### 🎨 Fable Brief Inference & Design Read",
661
+ "",
662
+ f"> **{res['design_read']}**",
663
+ "",
664
+ f"- **Page Kind**: `{res['page_kind']}`",
665
+ f"- **Target Audience**: {res['target_audience']}",
666
+ f"- **Optimal Archetype**: `{res['archetype_title']}` (`{res['archetype']}`)",
667
+ f"- **Aesthetic Vector**: Variance `{dials['variance']}` / Motion `{dials['motion']}` / Density `{dials['density']}`",
668
+ "",
669
+ "#### 🎨 Curated OKLCH Palette:",
670
+ f"- Background Void: `{palette['bg_void']}`",
671
+ f"- Surface Card: `{palette['surface_card']}`",
672
+ f"- Hairline Border: `{palette['border_hairline']}`",
673
+ f"- Primary Accent: `{palette['accent_primary']}`",
674
+ f"- Primary Text: `{palette['text_primary']}`",
675
+ "",
676
+ "#### 🔤 Typographic Pairings:",
677
+ f"- Display: `{typo['display']}`",
678
+ f"- Body: `{typo['body']}`",
679
+ f"- Monospace: `{typo['mono']}`",
680
+ "",
681
+ "#### 📐 Layout Architecture:",
682
+ f"- Hero Stack: Max {layout['hero_stack_max_elements']} text elements, cap at {layout['hero_top_padding_cap']}",
683
+ f"- Navigation: {layout['navigation_height_cap']}",
684
+ f"- Grid: {layout['bento_grid_structure']}",
685
+ f"- CTA Constraint: {layout['desktop_cta_rule']}",
686
+ ]
687
+ md_output = "\n".join(lines)
688
+ if session and session.execution_locked:
689
+ md_output += SILENT_DELIBERATION_REMINDER
690
+ return md_output
691
+
692
+
693
+ def _handle_generate_design_tokens(arguments: Dict[str, Any]) -> str:
694
+ archetype = arguments.get("archetype") or arguments.get("name") or "cyber_obsidian_monolith"
695
+ session_name = arguments.get("session_name", "").strip()
696
+ session = get_or_load_session(session_name) if session_name else None
697
+ engine = _get_design_engine()
698
+ if engine is None:
699
+ return "Error: DesignEngine module is unavailable."
700
+
701
+ data = engine.generate_design_tokens(archetype=archetype)
702
+ lines = [
703
+ f"### 🎛️ Haute Design Tokens: `{data['title']}`",
704
+ "",
705
+ f"> {data['description']}",
706
+ "",
707
+ f"- **Radius Scale**: `{data['border_radius_scale']}`",
708
+ f"- **Spring Physics**: `{data['spring_physics']['name']}` (Stiffness: {data['spring_physics']['stiffness']}, Damping: {data['spring_physics']['damping']}, Mass: {data['spring_physics']['mass']})",
709
+ f"- **WCAG Contrast**: `{data['wcag_aa_contrast']['primary_to_bg_ratio']}:1` ({'✅ Meets AA' if data['wcag_aa_contrast']['meets_wcag_aa'] else '⚠️ Below AA'})",
710
+ "",
711
+ "```css",
712
+ data["tailwind_v4_theme"],
713
+ "```",
714
+ ]
715
+ md_output = "\n".join(lines)
716
+ if session and session.execution_locked:
717
+ md_output += SILENT_DELIBERATION_REMINDER
718
+ return md_output
719
+
720
+
721
+ def _handle_generate_awwwards_scaffold(arguments: Dict[str, Any]) -> str:
722
+ prompt = arguments.get("prompt") or arguments.get("user_prompt") or arguments.get("brief") or "Modern software platform"
723
+ archetype = arguments.get("archetype") or arguments.get("archetype_override") or arguments.get("name")
724
+ session_name = arguments.get("session_name", "").strip()
725
+ session = get_or_load_session(session_name) if session_name else None
726
+ engine = _get_design_engine()
727
+ if engine is None:
728
+ return "Error: DesignEngine module is unavailable."
729
+
730
+ res = engine.generate_awwwards_scaffold(prompt=prompt, archetype_override=archetype)
731
+ lines = [
732
+ f"### 🏆 Awwwards-Caliber Zero-Slop Scaffold Generated",
733
+ "",
734
+ f"> **{res['design_read']}**",
735
+ "",
736
+ f"- **Aesthetic Archetype**: `{res['theme_title']}` (`{res['archetype']}`)",
737
+ f"- **Anti-Slop Verified**: `{'✅ 100% CLEAN' if res['anti_slop_verified'] else '⚠️ VIOLATIONS DETECTED'}` (Score: `{res['audit_result']['score'] * 100:.1f}%`)",
738
+ "",
739
+ "#### 🎨 Tailwind CSS v4 Theme:",
740
+ "```css",
741
+ res["tailwind_v4_theme"],
742
+ "```",
743
+ "",
744
+ "#### 🏗️ Semantic 7-Layer HTML/JSX Layout:",
745
+ "```html",
746
+ res["html_layout"][:2000] + "\n... [Full layout available via code export]",
747
+ "```",
748
+ ]
749
+ md_output = "\n".join(lines)
750
+ if session and session.execution_locked:
751
+ md_output += SILENT_DELIBERATION_REMINDER
752
+ return md_output
753
+
754
+
755
+ def _handle_validate_preflight_design(arguments: Dict[str, Any]) -> str:
756
+ code = arguments.get("code") or arguments.get("content") or arguments.get("html") or arguments.get("source") or ""
757
+ session_name = arguments.get("session_name", "").strip()
758
+ session = get_or_load_session(session_name) if session_name else None
759
+ engine = _get_design_engine()
760
+ if engine is None:
761
+ return "Error: DesignEngine module is unavailable."
762
+
763
+ res = engine.validate_preflight_design(code)
764
+ status_badge = "🟢 PRE-FLIGHT APPROVED" if res["approved"] else "🔴 PRE-FLIGHT REJECTED"
765
+ lines = [
766
+ f"### 🚦 5-Point Pre-Flight Design Gate: {status_badge}",
767
+ "",
768
+ f"- **Composite Score**: `{res['composite_score'] * 100:.1f}%`",
769
+ f"- **Passed Checks**: `{res['passed_checks']}/{res['total_checks']}`",
770
+ f"- **Approved**: `{res['approved']}`",
771
+ "",
772
+ "#### 📋 5-Point Verification Checklist:",
773
+ ]
774
+ for chk in res["checklist"]:
775
+ mark = "✅ PASSED" if chk["passed"] else "❌ FAILED"
776
+ lines.append(f"- **Point {chk['point']} ({chk['name']})**: {mark} — {chk['details']}")
777
+
778
+ md_output = "\n".join(lines)
779
+ if session and session.execution_locked:
780
+ md_output += SILENT_DELIBERATION_REMINDER
781
+ return md_output
782
+
783
+
784
+ def _handle_list_design_archetypes(arguments: Dict[str, Any]) -> str:
785
+ session_name = arguments.get("session_name", "").strip()
786
+ session = get_or_load_session(session_name) if session_name else None
787
+ engine = _get_design_engine()
788
+ if engine is None:
789
+ return "Error: DesignEngine module is unavailable."
790
+
791
+ archetypes = engine.list_design_archetypes()
792
+ lines = [
793
+ "### 🏛️ Haute Aesthetic Archetypes (Anti-Slop Universes)",
794
+ "",
795
+ "| Archetype Key | Title | Dials (V/M/D) | Aesthetic Character |",
796
+ "| :--- | :--- | :--- | :--- |",
797
+ ]
798
+ for arch in archetypes:
799
+ d = arch["dials"]
800
+ lines.append(f"| `{arch['archetype']}` | **{arch['title']}** | `{d['variance']}/{d['motion']}/{d['density']}` | {arch['description']} |")
801
+
802
+ lines.append("")
803
+ lines.append("Use `action: 'generate_design_tokens'` with `archetype: '<key>'` or `action: 'generate_awwwards_scaffold'` to scaffold.")
804
+ md_output = "\n".join(lines)
805
+ if session and session.execution_locked:
806
+ md_output += SILENT_DELIBERATION_REMINDER
807
+ return md_output