fable-engine 1.3.8__tar.gz → 1.3.9__tar.gz

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 (165) hide show
  1. {fable_engine-1.3.8/fable_engine.egg-info → fable_engine-1.3.9}/PKG-INFO +6 -6
  2. {fable_engine-1.3.8 → fable_engine-1.3.9}/README.md +5 -5
  3. {fable_engine-1.3.8 → fable_engine-1.3.9}/docs/stop-ai-agents-writing-too-early.md +5 -5
  4. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/actions/system3.py +49 -4
  5. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/browser.py +58 -3
  6. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/fable_session.json +19 -2
  7. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/schema.py +19 -2
  8. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/scrapers/base.py +27 -1
  9. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/server.py +1 -1
  10. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/session.py +1 -1
  11. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/updater.py +38 -17
  12. {fable_engine-1.3.8 → fable_engine-1.3.9/fable_engine.egg-info}/PKG-INFO +6 -6
  13. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_mode/__init__.py +1 -1
  14. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/system3/evolution.py +75 -0
  15. {fable_engine-1.3.8 → fable_engine-1.3.9}/pyproject.toml +1 -1
  16. {fable_engine-1.3.8 → fable_engine-1.3.9}/setup.py +1 -1
  17. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_auto_updater.py +185 -0
  18. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_scrapers.py +20 -0
  19. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_stealth_browser.py +42 -0
  20. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_system3.py +52 -0
  21. {fable_engine-1.3.8 → fable_engine-1.3.9}/LICENSE +0 -0
  22. {fable_engine-1.3.8 → fable_engine-1.3.9}/MANIFEST.in +0 -0
  23. {fable_engine-1.3.8 → fable_engine-1.3.9}/docs/ai-evidence-adjudicator.md +0 -0
  24. {fable_engine-1.3.8 → fable_engine-1.3.9}/docs/fable-v1-v2-migration.md +0 -0
  25. {fable_engine-1.3.8 → fable_engine-1.3.9}/docs/fable-v2-architecture.md +0 -0
  26. {fable_engine-1.3.8 → fable_engine-1.3.9}/docs/system3-architecture.md +0 -0
  27. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_compressor.py +0 -0
  28. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/__init__.py +0 -0
  29. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/actions/__init__.py +0 -0
  30. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/actions/cas.py +0 -0
  31. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/actions/deliberation.py +0 -0
  32. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/actions/fleet.py +0 -0
  33. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/actions/lifecycle.py +0 -0
  34. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/actions/scrapers.py +0 -0
  35. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/adjudicator.py +0 -0
  36. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/cas.py +0 -0
  37. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/guards.py +0 -0
  38. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/scrapers/__init__.py +0 -0
  39. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/scrapers/arxiv.py +0 -0
  40. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/scrapers/github.py +0 -0
  41. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/scrapers/reddit.py +0 -0
  42. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/scrapers/web.py +0 -0
  43. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/scrapers/x.py +0 -0
  44. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/scrapers/youtube.py +0 -0
  45. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/test_server.py +0 -0
  46. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine.egg-info/SOURCES.txt +0 -0
  47. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine.egg-info/dependency_links.txt +0 -0
  48. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine.egg-info/entry_points.txt +0 -0
  49. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine.egg-info/top_level.txt +0 -0
  50. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_mode/__main__.py +0 -0
  51. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_mode/adapters.py +0 -0
  52. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_mode/installer.py +0 -0
  53. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_mode/launcher.py +0 -0
  54. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_mode/manifest.py +0 -0
  55. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_mode/resources.json +0 -0
  56. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_mode/safety.py +0 -0
  57. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_mode/skill_bundle.py +0 -0
  58. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_mode_entry.py +0 -0
  59. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/__init__.py +0 -0
  60. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/adapters.py +0 -0
  61. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/__init__.py +0 -0
  62. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/ast_tools.py +0 -0
  63. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/compute.py +0 -0
  64. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/design_engine.py +0 -0
  65. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/diagnostics.py +0 -0
  66. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/fleet_dispatcher.py +0 -0
  67. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/mock_auditor.py +0 -0
  68. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/mutation.py +0 -0
  69. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/property_oracle.py +0 -0
  70. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/receipt_attestor.py +0 -0
  71. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/red_team_swarm.py +0 -0
  72. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/sandbox_executor.py +0 -0
  73. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/test_harness.py +0 -0
  74. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/vector_engine.py +0 -0
  75. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/visual.py +0 -0
  76. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/workspace.py +0 -0
  77. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/cortical/__init__.py +0 -0
  78. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/cortical/plasticity_engine.py +0 -0
  79. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/execution_broker.py +0 -0
  80. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/proof_engine.py +0 -0
  81. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/protocol.py +0 -0
  82. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/runtime.py +0 -0
  83. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/system3/__init__.py +0 -0
  84. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/system3/causal.py +0 -0
  85. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/system3/dialectical.py +0 -0
  86. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/system3/executive.py +0 -0
  87. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/system3/free_energy.py +0 -0
  88. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/system3/hyperbolic.py +0 -0
  89. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/system3/induction.py +0 -0
  90. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/system3/kripke.py +0 -0
  91. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/system3/oracle.py +0 -0
  92. {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/verifiers.py +0 -0
  93. {fable_engine-1.3.8 → fable_engine-1.3.9}/rules/AGENTS.md +0 -0
  94. {fable_engine-1.3.8 → fable_engine-1.3.9}/rules/GEMINI.md +0 -0
  95. {fable_engine-1.3.8 → fable_engine-1.3.9}/rules/fable-mode.md +0 -0
  96. {fable_engine-1.3.8 → fable_engine-1.3.9}/setup.cfg +0 -0
  97. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/SKILL.md +0 -0
  98. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/cortex/concurrency.md +0 -0
  99. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/cortex/design_3d.md +0 -0
  100. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/cortex/frontend_design.md +0 -0
  101. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/cortex/process.md +0 -0
  102. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/cortex/python.md +0 -0
  103. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/cortex/research.md +0 -0
  104. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/cortex/rust.md +0 -0
  105. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/cortex/security.md +0 -0
  106. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/cortex/synaptic_matrix.json +0 -0
  107. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/cortex/system.md +0 -0
  108. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/cortex/target.md +0 -0
  109. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/examples/autonomous-agentic-migration.md +0 -0
  110. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/examples/breakthrough-algorithm-synthesis.md +0 -0
  111. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/examples/deepthink-analysis-proof.md +0 -0
  112. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/examples/distributed-system-design.md +0 -0
  113. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/examples/swe-bench-pro-debugging.md +0 -0
  114. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/examples/weak_model_ollama_setup.md +0 -0
  115. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/aaa-threejs-game-engine.md +0 -0
  116. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/adversarial-code-review-swarm.md +0 -0
  117. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/agentic-execution.md +0 -0
  118. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/anti-slop-frontend-architecture.md +0 -0
  119. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/architectural-blueprinting.md +0 -0
  120. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/cinematic-design-engine.md +0 -0
  121. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/cognitive-protocol.md +0 -0
  122. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/deepthink-mode.md +0 -0
  123. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/design-system.md +0 -0
  124. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/design-tokens-and-typographies.md +0 -0
  125. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/goal-rubric-and-pipeline-automation.md +0 -0
  126. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/hebbian-cortical-plasticity.md +0 -0
  127. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/innovation-engine.md +0 -0
  128. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/interleaved-verification.md +0 -0
  129. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/model-velocity-calibration.md +0 -0
  130. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/prompt-scaffolds.md +0 -0
  131. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/proof-architecture.md +0 -0
  132. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/svg-craft-and-vector-design.md +0 -0
  133. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/system2-session-engine.md +0 -0
  134. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/system3-meta-cognition.md +0 -0
  135. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/visual-imagination-engine.md +0 -0
  136. {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/weak-model-frontier-uplift.md +0 -0
  137. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/__init__.py +0 -0
  138. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_adjudicator.py +0 -0
  139. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_anti_loop_circuit_breaker.py +0 -0
  140. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_coder_fleet.py +0 -0
  141. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_delegation_compiler.py +0 -0
  142. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_descriptor_boundaries.py +0 -0
  143. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_design_engine.py +0 -0
  144. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_epistemic_evidence_validator.py +0 -0
  145. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_execution_broker.py +0 -0
  146. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_fable_v2.py +0 -0
  147. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_fleet_transitions.py +0 -0
  148. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_fsm_redteam_evolution.py +0 -0
  149. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_goal_rubric_and_pipeline.py +0 -0
  150. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_hebbian_plasticity.py +0 -0
  151. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_packaging_runtime.py +0 -0
  152. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_proof_engine.py +0 -0
  153. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_red_team_seal_path.py +0 -0
  154. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_red_team_swarm.py +0 -0
  155. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_redteam_remediation.py +0 -0
  156. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_registration_transaction.py +0 -0
  157. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_requested_regressions.py +0 -0
  158. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_server_actions.py +0 -0
  159. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_server_frontier_actions.py +0 -0
  160. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_server_protocol.py +0 -0
  161. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_shell_call.py +0 -0
  162. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_skill_distribution.py +0 -0
  163. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_system3_deep_integration.py +0 -0
  164. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_system3_frontier.py +0 -0
  165. {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_vector_engine.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: fable-engine
3
- Version: 1.3.8
3
+ Version: 1.3.9
4
4
  Summary: Independent deterministic System 2 cognitive engine and mechanical time-lock MCP server
5
5
  Author: REX-codebase
6
6
  License: MIT License
@@ -142,7 +142,7 @@ server never silently activates workspace instructions.
142
142
  Run setup from the project the agent will work in:
143
143
 
144
144
  ```bash
145
- uvx --from fable-engine==1.3.8 fable-mode setup --yes
145
+ uvx --from fable-engine==1.3.9 fable-mode setup --yes
146
146
  ```
147
147
 
148
148
  This resolves the pinned package in an isolated uv environment and copies the
@@ -161,19 +161,19 @@ Then choose only the invocation that matches the agent environment.
161
161
  Run `fable-engine` as the stdio server. For example:
162
162
 
163
163
  ```jsonc
164
- // Claude Code: claude mcp add fable-engine -- uvx --from fable-engine==1.3.8 fable-engine
164
+ // Claude Code: claude mcp add fable-engine -- uvx --from fable-engine==1.3.9 fable-engine
165
165
  // Cursor or another JSON-configured client:
166
166
  {
167
167
  "mcpServers": {
168
168
  "fable-engine": {
169
169
  "command": "uvx",
170
- "args": ["--from", "fable-engine==1.3.8", "fable-engine"]
170
+ "args": ["--from", "fable-engine==1.3.9", "fable-engine"]
171
171
  }
172
172
  }
173
173
  }
174
174
  ```
175
175
 
176
- [![Install MCP server in VS Code](https://img.shields.io/badge/VS_Code-Install_MCP_server-007ACC?logo=visualstudiocode&logoColor=white)](vscode:mcp/install?%7B%22name%22%3A%22fable-engine%22%2C%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22fable-engine%3D%3D1.3.8%22%2C%22fable-engine%22%5D%7D)
176
+ [![Install MCP server in VS Code](https://img.shields.io/badge/VS_Code-Install_MCP_server-007ACC?logo=visualstudiocode&logoColor=white)](vscode:mcp/install?%7B%22name%22%3A%22fable-engine%22%2C%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22fable-engine%3D%3D1.3.9%22%2C%22fable-engine%22%5D%7D)
177
177
 
178
178
  #### Shell sandbox with internet, no MCP host
179
179
 
@@ -182,7 +182,7 @@ argument object to `fable-mode call`:
182
182
 
183
183
  ```bash
184
184
  printf '%s\n' '{"action":"create_session","session_name":"demo","objective":"Verify this change","time_budget_minutes":2}' \
185
- | uvx --from fable-engine==1.3.8 fable-mode call
185
+ | uvx --from fable-engine==1.3.9 fable-mode call
186
186
  ```
187
187
 
188
188
  The command uses JSON Lines: one `fable_session` argument object per input line
@@ -99,7 +99,7 @@ server never silently activates workspace instructions.
99
99
  Run setup from the project the agent will work in:
100
100
 
101
101
  ```bash
102
- uvx --from fable-engine==1.3.8 fable-mode setup --yes
102
+ uvx --from fable-engine==1.3.9 fable-mode setup --yes
103
103
  ```
104
104
 
105
105
  This resolves the pinned package in an isolated uv environment and copies the
@@ -118,19 +118,19 @@ Then choose only the invocation that matches the agent environment.
118
118
  Run `fable-engine` as the stdio server. For example:
119
119
 
120
120
  ```jsonc
121
- // Claude Code: claude mcp add fable-engine -- uvx --from fable-engine==1.3.8 fable-engine
121
+ // Claude Code: claude mcp add fable-engine -- uvx --from fable-engine==1.3.9 fable-engine
122
122
  // Cursor or another JSON-configured client:
123
123
  {
124
124
  "mcpServers": {
125
125
  "fable-engine": {
126
126
  "command": "uvx",
127
- "args": ["--from", "fable-engine==1.3.8", "fable-engine"]
127
+ "args": ["--from", "fable-engine==1.3.9", "fable-engine"]
128
128
  }
129
129
  }
130
130
  }
131
131
  ```
132
132
 
133
- [![Install MCP server in VS Code](https://img.shields.io/badge/VS_Code-Install_MCP_server-007ACC?logo=visualstudiocode&logoColor=white)](vscode:mcp/install?%7B%22name%22%3A%22fable-engine%22%2C%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22fable-engine%3D%3D1.3.8%22%2C%22fable-engine%22%5D%7D)
133
+ [![Install MCP server in VS Code](https://img.shields.io/badge/VS_Code-Install_MCP_server-007ACC?logo=visualstudiocode&logoColor=white)](vscode:mcp/install?%7B%22name%22%3A%22fable-engine%22%2C%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22fable-engine%3D%3D1.3.9%22%2C%22fable-engine%22%5D%7D)
134
134
 
135
135
  #### Shell sandbox with internet, no MCP host
136
136
 
@@ -139,7 +139,7 @@ argument object to `fable-mode call`:
139
139
 
140
140
  ```bash
141
141
  printf '%s\n' '{"action":"create_session","session_name":"demo","objective":"Verify this change","time_budget_minutes":2}' \
142
- | uvx --from fable-engine==1.3.8 fable-mode call
142
+ | uvx --from fable-engine==1.3.9 fable-mode call
143
143
  ```
144
144
 
145
145
  The command uses JSON Lines: one `fable_session` argument object per input line
@@ -18,8 +18,8 @@ You need Python 3.10+ and either [uv](https://docs.astral.sh/uv/getting-started/
18
18
  From the project the agent will work in:
19
19
 
20
20
  ```bash
21
- uvx --from fable-engine==1.3.8 fable-mode setup --dry-run
22
- uvx --from fable-engine==1.3.8 fable-mode setup --yes
21
+ uvx --from fable-engine==1.3.9 fable-mode setup --dry-run
22
+ uvx --from fable-engine==1.3.9 fable-mode setup --yes
23
23
  ```
24
24
 
25
25
  `setup` copies the complete bundled skill to `.agents/skills/fable-mode`. Use
@@ -40,7 +40,7 @@ Run `fable-engine` as its stdio MCP server.
40
40
  ### Claude Code
41
41
 
42
42
  ```bash
43
- claude mcp add fable-engine -- uvx --from fable-engine==1.3.8 fable-engine
43
+ claude mcp add fable-engine -- uvx --from fable-engine==1.3.9 fable-engine
44
44
  ```
45
45
 
46
46
  ### Cursor or another JSON-configured MCP client
@@ -50,7 +50,7 @@ claude mcp add fable-engine -- uvx --from fable-engine==1.3.8 fable-engine
50
50
  "mcpServers": {
51
51
  "fable-engine": {
52
52
  "command": "uvx",
53
- "args": ["--from", "fable-engine==1.3.8", "fable-engine"]
53
+ "args": ["--from", "fable-engine==1.3.9", "fable-engine"]
54
54
  }
55
55
  }
56
56
  }
@@ -64,7 +64,7 @@ Pipe one `fable_session` argument object to `fable-mode call`:
64
64
 
65
65
  ```bash
66
66
  printf '%s\n' '{"action":"create_session","session_name":"parser-refactor","objective":"Refactor the parser without changing its public behavior","time_budget_minutes":2}' \
67
- | uvx --from fable-engine==1.3.8 fable-mode call
67
+ | uvx --from fable-engine==1.3.9 fable-mode call
68
68
  ```
69
69
 
70
70
  The command uses JSON Lines and passes each object to the same Fable handler used
@@ -282,11 +282,53 @@ def _handle_system3_evolve_paradigms(arguments: Dict[str, Any]) -> str:
282
282
  )
283
283
  pool.initialize_population(seed_paradigms=seed_paradigms)
284
284
 
285
- for _ in range(generations):
286
- pool.evolve_generation()
285
+ observed_scores = arguments.get("observed_fitness")
286
+ if isinstance(observed_scores, str):
287
+ try:
288
+ observed_scores = json.loads(observed_scores)
289
+ except Exception:
290
+ return "Error: 'observed_fitness' must be a JSON object keyed by genome ID or '*' default."
291
+
292
+ evolution_receipt = None
293
+ if observed_scores is not None:
294
+ if not isinstance(observed_scores, dict) or not observed_scores:
295
+ return "Error: 'observed_fitness' must be a non-empty object keyed by genome ID or '*' default."
296
+
297
+ def observed_evaluator(genome: CognitiveGenome) -> Dict[str, float]:
298
+ raw = observed_scores.get(genome.genome_id, observed_scores.get("*"))
299
+ if not isinstance(raw, dict):
300
+ raise ValueError(f"Missing observed fitness for genome '{genome.genome_id}' and no '*' default was supplied.")
301
+ normalized: Dict[str, float] = {}
302
+ for dim in pool.population[0].fitness_scores.keys() or []:
303
+ if dim in raw:
304
+ normalized[dim] = max(0.0, min(1.0, float(raw[dim])))
305
+ if not normalized:
306
+ from fable_v2.system3 import PARETO_DIMENSIONS
307
+ for dim in PARETO_DIMENSIONS:
308
+ if dim in raw:
309
+ normalized[dim] = max(0.0, min(1.0, float(raw[dim])))
310
+ if not normalized:
311
+ raise ValueError("Observed fitness rows must contain at least one recognized Pareto dimension.")
312
+ return normalized
313
+
314
+ try:
315
+ evolution_receipt = pool.evolve_until_stable(
316
+ observed_evaluator,
317
+ max_generations=generations,
318
+ patience=int(arguments.get("stagnation_patience", min(4, max(1, generations)))),
319
+ min_improvement=float(arguments.get("min_improvement", 0.001)),
320
+ weights=weights,
321
+ )
322
+ except (TypeError, ValueError) as exc:
323
+ return f"Error: Invalid observed fitness: {exc}"
324
+ else:
325
+ for _ in range(generations):
326
+ pool.evolve_generation()
287
327
 
288
328
  pareto_frontier = pool.get_pareto_frontier()
289
- best_genome = pool.get_best_genome(weights)
329
+ best_genome = pool.get_best_genome(weights) if evolution_receipt is None else max(
330
+ pool.population, key=lambda g: g.compute_scalar_fitness(weights)
331
+ )
290
332
 
291
333
  session = get_or_load_session(session_name)
292
334
  session.system3_gene_pools.append(pool.to_dict())
@@ -316,7 +358,10 @@ def _handle_system3_evolve_paradigms(arguments: Dict[str, Any]) -> str:
316
358
  f"- **Session**: `{session.session_name}`\n"
317
359
  f"- **Generations Evolved**: `{pool.generation_count}` (Population: `{len(pool.population)}`)\n"
318
360
  f"- **Rank 1 Pareto Frontier Size**: `{len(pareto_frontier)}` non-dominated solutions\n"
319
- f"- **Top Archetype**: **{best_genome.paradigm_name}** (`{best_genome.genome_id}`)\n\n"
361
+ f"- **Top Archetype**: **{best_genome.paradigm_name}** (`{best_genome.genome_id}`)\n"
362
+ f"- **Measured Loop**: `{'ACTIVE - external acceptance required' if evolution_receipt else 'structural heuristic mode'}`\n"
363
+ + (f"- **Stop Reason**: `{evolution_receipt['stop_reason']}`\n" if evolution_receipt else "")
364
+ + "\n"
320
365
  f"#### 🏆 Top Rank 1 Non-Dominated Pareto Frontier:\n"
321
366
  f"| ID | Paradigm | Lat | Tput | Mem | Fault | Mod | Sec | Token | Score |\n"
322
367
  f"|---|---|---|---|---|---|---|---|---|---|\n"
@@ -571,18 +571,69 @@ class StealthBrowserSession:
571
571
  self.scroll_y = max(0, min(self.scroll_y + delta_y, self.document_height - self.viewport_height))
572
572
  return self._build_status()
573
573
 
574
+ def _ancestor_form(self, elem: DOMElement) -> Optional[DOMElement]:
575
+ current = elem.parent
576
+ while current is not None:
577
+ if current.tag == "form":
578
+ return current
579
+ current = current.parent
580
+ return None
581
+
582
+ def _submit_get_form(self, form: DOMElement) -> Dict[str, Any]:
583
+ """Submit a GET form through normal navigation; never synthesize a write request."""
584
+ method = form.attrs.get("method", "get").strip().lower() or "get"
585
+ if method != "get":
586
+ return {
587
+ "status": "error",
588
+ "error": f"Form method '{method.upper()}' is unsupported because it may mutate remote state",
589
+ "element_id": form.element_id,
590
+ }
591
+ action = urllib.parse.urljoin(self.url, form.attrs.get("action") or self.url)
592
+ pairs: List[Tuple[str, str]] = []
593
+ stack = list(reversed(form.children))
594
+ while stack:
595
+ node = stack.pop()
596
+ stack.extend(reversed(node.children))
597
+ name = node.attrs.get("name", "").strip()
598
+ if not name or node.attrs.get("disabled") is not None:
599
+ continue
600
+ if node.tag == "textarea":
601
+ pairs.append((name, node.attrs.get("value", node.text)))
602
+ elif node.tag == "input":
603
+ input_type = node.attrs.get("type", "text").lower()
604
+ if input_type == "password":
605
+ return {
606
+ "status": "error",
607
+ "error": "GET form submission with a password field is blocked to prevent credential leakage",
608
+ "element_id": form.element_id,
609
+ }
610
+ if input_type in ("submit", "button", "reset", "file", "image"):
611
+ continue
612
+ if input_type in ("checkbox", "radio") and "checked" not in node.attrs:
613
+ continue
614
+ pairs.append((name, node.attrs.get("value", "on" if input_type in ("checkbox", "radio") else "")))
615
+ parsed = urllib.parse.urlsplit(action)
616
+ query = urllib.parse.parse_qsl(parsed.query, keep_blank_values=True) + pairs
617
+ target = urllib.parse.urlunsplit((parsed.scheme, parsed.netloc, parsed.path, urllib.parse.urlencode(query), parsed.fragment))
618
+ return self.open(target)
619
+
574
620
  def click(self, element_id: str) -> Dict[str, Any]:
575
- """Follow an element's link target; non-link controls are unsupported."""
621
+ """Follow a link or activate a safe GET form control."""
576
622
  elem = self.elements_by_id.get(element_id)
577
623
  if not elem:
578
- return {"error": f"Element '{element_id}' not found"}
624
+ return {"status": "error", "error": f"Element '{element_id}' not found"}
579
625
  href = elem.attrs.get("href")
580
626
  if href is not None:
581
627
  target_url = urllib.parse.urljoin(self.url, href)
582
628
  return self.open(target_url)
629
+ input_type = elem.attrs.get("type", "").lower()
630
+ if elem.tag == "button" or (elem.tag == "input" and input_type in ("submit", "image")):
631
+ form = self._ancestor_form(elem)
632
+ if form is not None:
633
+ return self._submit_get_form(form)
583
634
  return {
584
635
  "status": "error",
585
- "error": f"Click is unsupported for <{elem.tag}> elements without a link target",
636
+ "error": f"Click is unsupported for <{elem.tag}> elements without a link or safe GET form target",
586
637
  "element_id": element_id,
587
638
  }
588
639
 
@@ -672,6 +723,10 @@ class StealthBrowserSession:
672
723
  updated_value = current_value + " "
673
724
  elif key == "Enter" and target.tag == "textarea":
674
725
  updated_value = current_value + "\n"
726
+ elif key == "Enter" and target.tag == "input":
727
+ form = self._ancestor_form(target)
728
+ if form is not None:
729
+ return self._submit_get_form(form)
675
730
  elif len(key) == 1:
676
731
  updated_value = current_value + key
677
732
  if updated_value is not None and updated_value != current_value:
@@ -156,7 +156,7 @@
156
156
  "type": "number"
157
157
  },
158
158
  "curvature": {
159
- "description": "Sectional curvature c > 0 of Poincar\u00e9 manifold (default 1.0).",
159
+ "description": "Sectional curvature c > 0 of Poincaré manifold (default 1.0).",
160
160
  "type": "number"
161
161
  },
162
162
  "d_prior": {
@@ -167,7 +167,7 @@
167
167
  ]
168
168
  },
169
169
  "dimension": {
170
- "description": "Dimension of Poincar\u00e9 ball manifold (default 2).",
170
+ "description": "Dimension of Poincaré ball manifold (default 2).",
171
171
  "type": "integer"
172
172
  },
173
173
  "domain": {
@@ -597,6 +597,23 @@
597
597
  "user_prompt": {
598
598
  "description": "Alias for prompt: high-level design prompt describing desired web experience.",
599
599
  "type": "string"
600
+ },
601
+ "observed_fitness": {
602
+ "description": "Optional measured fitness object keyed by genome ID, with '*' as a default row. Enables bounded evidence-fed evolution and does not authorize deployment.",
603
+ "type": [
604
+ "object",
605
+ "string"
606
+ ]
607
+ },
608
+ "stagnation_patience": {
609
+ "description": "Measured generations without improvement before automatic evolution stops.",
610
+ "type": "integer",
611
+ "minimum": 1
612
+ },
613
+ "min_improvement": {
614
+ "description": "Minimum scalar measured-fitness gain required to reset stagnation.",
615
+ "type": "number",
616
+ "minimum": 0
600
617
  }
601
618
  },
602
619
  "required": [
@@ -306,6 +306,20 @@ TOOL_SCHEMA = {
306
306
  "description": "Optional dictionary of weights across the 10 Pareto dimensions.",
307
307
  "type": ["object", "string"]
308
308
  },
309
+ "observed_fitness": {
310
+ "description": "Optional measured fitness object keyed by genome ID, with '*' as a default row. Supplying it enables bounded evidence-fed evolution with stagnation stopping; values are clamped to [0, 1] and never authorize deployment.",
311
+ "type": ["object", "string"]
312
+ },
313
+ "stagnation_patience": {
314
+ "description": "Measured generations without improvement before automatic evolution stops (default min(4, generations)).",
315
+ "type": "integer",
316
+ "minimum": 1
317
+ },
318
+ "min_improvement": {
319
+ "description": "Minimum scalar measured-fitness gain required to reset stagnation (default 0.001).",
320
+ "type": "number",
321
+ "minimum": 0
322
+ },
309
323
  "task_complexity": {
310
324
  "type": "number",
311
325
  "description": "Task complexity index [0.0, 1.0] for tri-level cognitive arbitration."
@@ -625,11 +639,14 @@ BROWSER_TOOL_SCHEMAS = [
625
639
  },
626
640
  {
627
641
  "name": "browser_click",
628
- "description": "Navigates to the href of a link element by its stable element ID. Elements without an href are unsupported.",
642
+ "title": "Activate a safe browser target",
643
+ "annotations": {"title": "Activate a safe browser target", "readOnlyHint": False, "destructiveHint": False, "idempotentHint": False, "openWorldHint": True},
644
+ "outputSchema": {"type": "object", "additionalProperties": True},
645
+ "description": "Activates an href-bearing link or submits a GET form through normal browser navigation. POST and other form methods are refused, and GET forms containing password fields are blocked to prevent credential leakage. Returns the resulting page status or a bounded error without bypassing existing navigation controls.",
629
646
  "inputSchema": {
630
647
  "type": "object",
631
648
  "properties": {
632
- "element_id": {"type": "string", "description": "Stable element ID of an href-bearing link."},
649
+ "element_id": {"type": "string", "description": "Stable element ID of an href-bearing link or submit control inside a safe GET form."},
633
650
  "session_id": {"type": "string", "description": "Optional browser tab/session identifier."}
634
651
  },
635
652
  "required": ["element_id"]
@@ -8,6 +8,7 @@ clamped retry/backoff, response bounds, and explicit untrusted external content
8
8
  from __future__ import annotations
9
9
 
10
10
  import datetime
11
+ import hashlib
11
12
  import http.client
12
13
  import ipaddress
13
14
  import json
@@ -25,7 +26,7 @@ from typing import Any, Dict, List, Optional, Tuple
25
26
 
26
27
  logger = logging.getLogger("fable-engine.scrapers.base")
27
28
 
28
- USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 FableResearch/1.3.8"
29
+ USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 FableResearch/1.3.9"
29
30
 
30
31
  MAX_RETRY_DELAY_SECONDS = 10.0
31
32
 
@@ -173,6 +174,23 @@ class DomainRateLimiter:
173
174
  GLOBAL_RATE_LIMITER = DomainRateLimiter(min_interval_seconds=0.3)
174
175
 
175
176
 
177
+ def _canonical_origin(url: str) -> str:
178
+ """Return a stable, credential-free origin for provenance metadata."""
179
+ try:
180
+ parsed = urllib.parse.urlsplit(url)
181
+ if parsed.scheme.lower() not in ("http", "https") or not parsed.hostname:
182
+ return ""
183
+ host = parsed.hostname.lower().rstrip(".")
184
+ if ":" in host:
185
+ host = f"[{host}]"
186
+ port = parsed.port
187
+ default_port = 443 if parsed.scheme.lower() == "https" else 80
188
+ authority = host if port in (None, default_port) else f"{host}:{port}"
189
+ return f"{parsed.scheme.lower()}://{authority}"
190
+ except (TypeError, ValueError):
191
+ return ""
192
+
193
+
176
194
  @dataclass
177
195
  class ResearchResult:
178
196
  """Structured research retrieval result."""
@@ -189,6 +207,14 @@ class ResearchResult:
189
207
  def __post_init__(self):
190
208
  if "trust_level" not in self.metadata:
191
209
  self.metadata["trust_level"] = "untrusted_external_content"
210
+ # Every successful extraction carries deterministic integrity/provenance facts.
211
+ # This does not make external content trusted; it lets callers detect repeated,
212
+ # changed, or accidentally truncated evidence without source-specific code.
213
+ if self.ok:
214
+ encoded = self.content.encode("utf-8", errors="replace")
215
+ self.metadata.setdefault("content_bytes", len(encoded))
216
+ self.metadata.setdefault("content_sha256", hashlib.sha256(encoded).hexdigest())
217
+ self.metadata.setdefault("canonical_origin", _canonical_origin(self.canonical_url))
192
218
 
193
219
  def to_markdown(self) -> str:
194
220
  """Formats the structured research result as readable Markdown with explicit untrusted content boundaries."""
@@ -237,7 +237,7 @@ def main():
237
237
  },
238
238
  "serverInfo": {
239
239
  "name": "fable-engine",
240
- "version": "1.3.8"
240
+ "version": "1.3.9"
241
241
  }
242
242
  }
243
243
  })
@@ -1567,7 +1567,7 @@ class FableSession:
1567
1567
  def to_dict(self) -> Dict[str, Any]:
1568
1568
  """Serializes session to dictionary."""
1569
1569
  return {
1570
- "version": "1.3.8",
1570
+ "version": "1.3.9",
1571
1571
  "session_name": self.session_name,
1572
1572
  "session_id": self.session_id,
1573
1573
  "objective": self.objective,
@@ -10,6 +10,7 @@ import json
10
10
  import logging
11
11
  import os
12
12
  from pathlib import Path
13
+ import copy
13
14
  import shutil
14
15
  import subprocess
15
16
  import threading
@@ -248,9 +249,9 @@ class AutoUpdater:
248
249
  try:
249
250
  pulled = CorticalLobe.load_from_disk(lobe_file)
250
251
  local = local_lobes[name]
251
- self._merge_lobe_data(pulled, local)
252
- pulled.save_to_disk(lobe_file)
253
- preserved_names.append(name)
252
+ if self._merge_lobe_data(pulled, local):
253
+ pulled.save_to_disk(lobe_file)
254
+ preserved_names.append(name)
254
255
  except Exception as e:
255
256
  logger.debug(f"Error merging custom lobe {name}: {e}")
256
257
 
@@ -262,9 +263,12 @@ class AutoUpdater:
262
263
  try:
263
264
  pulled = CorticalLobe.load_from_disk(lobe_file)
264
265
  local = local_lobes[base_name]
265
- self._merge_lobe_data(pulled, local)
266
- pulled.save_to_disk(lobe_file)
267
- preserved_names.append(base_name)
266
+ # Bundled seeds are read-only: rewrite only when the merge
267
+ # actually folded preserved experience into the lobe. A
268
+ # no-op update must leave seed bytes untouched.
269
+ if self._merge_lobe_data(pulled, local):
270
+ pulled.save_to_disk(lobe_file)
271
+ preserved_names.append(base_name)
268
272
  except Exception as e:
269
273
  logger.debug(f"Error merging baseline lobe {base_name}: {e}")
270
274
 
@@ -278,6 +282,7 @@ class AutoUpdater:
278
282
  pulled_matrix = json.loads(matrix_file.read_text(encoding="utf-8"))
279
283
  except Exception:
280
284
  pulled_matrix = {}
285
+ pre_merge_matrix = copy.deepcopy(pulled_matrix)
281
286
  # Non-destructively union local matrix weights with pulled matrix
282
287
  for src, targets in local_matrix.items():
283
288
  if src not in pulled_matrix:
@@ -293,21 +298,29 @@ class AutoUpdater:
293
298
  )
294
299
  except (ValueError, TypeError):
295
300
  pass
296
- try:
297
- matrix_file.write_text(json.dumps(pulled_matrix, indent=2), encoding="utf-8")
298
- except Exception as e:
299
- logger.debug(f"Error writing synaptic matrix: {e}")
301
+ if pulled_matrix != pre_merge_matrix:
302
+ try:
303
+ matrix_file.write_text(json.dumps(pulled_matrix, indent=2), encoding="utf-8")
304
+ except Exception as e:
305
+ logger.debug(f"Error writing synaptic matrix: {e}")
300
306
 
301
307
  return preserved_names
302
308
 
303
- def _merge_lobe_data(self, target_lobe: Any, source_lobe: Any) -> None:
304
- """Merge source antibodies, synaptic weights, and heuristics into target."""
309
+ def _merge_lobe_data(self, target_lobe: Any, source_lobe: Any) -> bool:
310
+ """Merge source antibodies, synaptic weights, and heuristics into target.
311
+
312
+ Returns True only when the merge actually changed the target, so callers
313
+ can skip rewriting files whose content is already up to date.
314
+ """
315
+ changed = False
316
+
305
317
  # 1. Merge Antibodies by antibody_id
306
318
  target_ids = {ab.antibody_id for ab in getattr(target_lobe, "antibodies", [])}
307
319
  for ab in getattr(source_lobe, "antibodies", []):
308
320
  if ab.antibody_id not in target_ids:
309
321
  target_lobe.antibodies.append(ab)
310
322
  target_ids.add(ab.antibody_id)
323
+ changed = True
311
324
  else:
312
325
  # If both have antibody, adopt source counterfactual/defense if target is missing it
313
326
  for idx, t_ab in enumerate(target_lobe.antibodies):
@@ -316,6 +329,7 @@ class AutoUpdater:
316
329
  t_ab, "verified_counterfactual", ""
317
330
  ):
318
331
  target_lobe.antibodies[idx] = ab
332
+ changed = True
319
333
  break
320
334
 
321
335
  # 2. Merge Synaptic Weights (max retention)
@@ -324,17 +338,21 @@ class AutoUpdater:
324
338
  for node, weight in source_weights.items():
325
339
  if node not in target_weights:
326
340
  target_weights[node] = weight
341
+ changed = True
327
342
  else:
328
343
  try:
329
- target_weights[node] = max(float(weight), float(target_weights[node]))
344
+ merged_weight = max(float(weight), float(target_weights[node]))
345
+ if merged_weight != target_weights[node]:
346
+ target_weights[node] = merged_weight
347
+ changed = True
330
348
  except (ValueError, TypeError):
331
349
  pass
332
350
 
333
351
  # 3. Merge Activation Count
334
- target_lobe.activation_count = max(
335
- getattr(target_lobe, "activation_count", 0),
336
- getattr(source_lobe, "activation_count", 0),
337
- )
352
+ source_activation = getattr(source_lobe, "activation_count", 0)
353
+ if source_activation > getattr(target_lobe, "activation_count", 0):
354
+ target_lobe.activation_count = source_activation
355
+ changed = True
338
356
 
339
357
  # 4. Merge Specialized Heuristics
340
358
  target_heuristics = set(getattr(target_lobe, "specialized_heuristics", []))
@@ -342,6 +360,9 @@ class AutoUpdater:
342
360
  if h not in target_heuristics:
343
361
  target_lobe.specialized_heuristics.append(h)
344
362
  target_heuristics.add(h)
363
+ changed = True
364
+
365
+ return changed
345
366
 
346
367
  def _sync_host_targets(self) -> list[str]:
347
368
  """Hot-sync skills, rules, and MCP session manifests into host environments."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: fable-engine
3
- Version: 1.3.8
3
+ Version: 1.3.9
4
4
  Summary: Independent deterministic System 2 cognitive engine and mechanical time-lock MCP server
5
5
  Author: REX-codebase
6
6
  License: MIT License
@@ -142,7 +142,7 @@ server never silently activates workspace instructions.
142
142
  Run setup from the project the agent will work in:
143
143
 
144
144
  ```bash
145
- uvx --from fable-engine==1.3.8 fable-mode setup --yes
145
+ uvx --from fable-engine==1.3.9 fable-mode setup --yes
146
146
  ```
147
147
 
148
148
  This resolves the pinned package in an isolated uv environment and copies the
@@ -161,19 +161,19 @@ Then choose only the invocation that matches the agent environment.
161
161
  Run `fable-engine` as the stdio server. For example:
162
162
 
163
163
  ```jsonc
164
- // Claude Code: claude mcp add fable-engine -- uvx --from fable-engine==1.3.8 fable-engine
164
+ // Claude Code: claude mcp add fable-engine -- uvx --from fable-engine==1.3.9 fable-engine
165
165
  // Cursor or another JSON-configured client:
166
166
  {
167
167
  "mcpServers": {
168
168
  "fable-engine": {
169
169
  "command": "uvx",
170
- "args": ["--from", "fable-engine==1.3.8", "fable-engine"]
170
+ "args": ["--from", "fable-engine==1.3.9", "fable-engine"]
171
171
  }
172
172
  }
173
173
  }
174
174
  ```
175
175
 
176
- [![Install MCP server in VS Code](https://img.shields.io/badge/VS_Code-Install_MCP_server-007ACC?logo=visualstudiocode&logoColor=white)](vscode:mcp/install?%7B%22name%22%3A%22fable-engine%22%2C%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22fable-engine%3D%3D1.3.8%22%2C%22fable-engine%22%5D%7D)
176
+ [![Install MCP server in VS Code](https://img.shields.io/badge/VS_Code-Install_MCP_server-007ACC?logo=visualstudiocode&logoColor=white)](vscode:mcp/install?%7B%22name%22%3A%22fable-engine%22%2C%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22fable-engine%3D%3D1.3.9%22%2C%22fable-engine%22%5D%7D)
177
177
 
178
178
  #### Shell sandbox with internet, no MCP host
179
179
 
@@ -182,7 +182,7 @@ argument object to `fable-mode call`:
182
182
 
183
183
  ```bash
184
184
  printf '%s\n' '{"action":"create_session","session_name":"demo","objective":"Verify this change","time_budget_minutes":2}' \
185
- | uvx --from fable-engine==1.3.8 fable-mode call
185
+ | uvx --from fable-engine==1.3.9 fable-mode call
186
186
  ```
187
187
 
188
188
  The command uses JSON Lines: one `fable_session` argument object per input line
@@ -1,3 +1,3 @@
1
1
  """Fable Mode portable runtime and transactional installer."""
2
2
 
3
- __version__ = "1.3.8"
3
+ __version__ = "1.3.9"