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.
- {fable_engine-1.3.8/fable_engine.egg-info → fable_engine-1.3.9}/PKG-INFO +6 -6
- {fable_engine-1.3.8 → fable_engine-1.3.9}/README.md +5 -5
- {fable_engine-1.3.8 → fable_engine-1.3.9}/docs/stop-ai-agents-writing-too-early.md +5 -5
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/actions/system3.py +49 -4
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/browser.py +58 -3
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/fable_session.json +19 -2
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/schema.py +19 -2
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/scrapers/base.py +27 -1
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/server.py +1 -1
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/session.py +1 -1
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/updater.py +38 -17
- {fable_engine-1.3.8 → fable_engine-1.3.9/fable_engine.egg-info}/PKG-INFO +6 -6
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_mode/__init__.py +1 -1
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/system3/evolution.py +75 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/pyproject.toml +1 -1
- {fable_engine-1.3.8 → fable_engine-1.3.9}/setup.py +1 -1
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_auto_updater.py +185 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_scrapers.py +20 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_stealth_browser.py +42 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_system3.py +52 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/LICENSE +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/MANIFEST.in +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/docs/ai-evidence-adjudicator.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/docs/fable-v1-v2-migration.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/docs/fable-v2-architecture.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/docs/system3-architecture.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_compressor.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/__init__.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/actions/__init__.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/actions/cas.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/actions/deliberation.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/actions/fleet.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/actions/lifecycle.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/actions/scrapers.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/adjudicator.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/cas.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/guards.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/scrapers/__init__.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/scrapers/arxiv.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/scrapers/github.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/scrapers/reddit.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/scrapers/web.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/scrapers/x.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/scrapers/youtube.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine/test_server.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine.egg-info/SOURCES.txt +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine.egg-info/dependency_links.txt +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine.egg-info/entry_points.txt +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_engine.egg-info/top_level.txt +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_mode/__main__.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_mode/adapters.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_mode/installer.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_mode/launcher.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_mode/manifest.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_mode/resources.json +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_mode/safety.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_mode/skill_bundle.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_mode_entry.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/__init__.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/adapters.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/__init__.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/ast_tools.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/compute.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/design_engine.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/diagnostics.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/fleet_dispatcher.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/mock_auditor.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/mutation.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/property_oracle.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/receipt_attestor.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/red_team_swarm.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/sandbox_executor.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/test_harness.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/vector_engine.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/visual.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/coder_fleet/workspace.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/cortical/__init__.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/cortical/plasticity_engine.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/execution_broker.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/proof_engine.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/protocol.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/runtime.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/system3/__init__.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/system3/causal.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/system3/dialectical.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/system3/executive.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/system3/free_energy.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/system3/hyperbolic.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/system3/induction.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/system3/kripke.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/system3/oracle.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/fable_v2/verifiers.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/rules/AGENTS.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/rules/GEMINI.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/rules/fable-mode.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/setup.cfg +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/SKILL.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/cortex/concurrency.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/cortex/design_3d.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/cortex/frontend_design.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/cortex/process.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/cortex/python.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/cortex/research.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/cortex/rust.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/cortex/security.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/cortex/synaptic_matrix.json +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/cortex/system.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/cortex/target.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/examples/autonomous-agentic-migration.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/examples/breakthrough-algorithm-synthesis.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/examples/deepthink-analysis-proof.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/examples/distributed-system-design.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/examples/swe-bench-pro-debugging.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/examples/weak_model_ollama_setup.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/aaa-threejs-game-engine.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/adversarial-code-review-swarm.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/agentic-execution.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/anti-slop-frontend-architecture.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/architectural-blueprinting.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/cinematic-design-engine.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/cognitive-protocol.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/deepthink-mode.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/design-system.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/design-tokens-and-typographies.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/goal-rubric-and-pipeline-automation.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/hebbian-cortical-plasticity.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/innovation-engine.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/interleaved-verification.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/model-velocity-calibration.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/prompt-scaffolds.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/proof-architecture.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/svg-craft-and-vector-design.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/system2-session-engine.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/system3-meta-cognition.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/visual-imagination-engine.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/skills/fable-mode/references/weak-model-frontier-uplift.md +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/__init__.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_adjudicator.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_anti_loop_circuit_breaker.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_coder_fleet.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_delegation_compiler.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_descriptor_boundaries.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_design_engine.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_epistemic_evidence_validator.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_execution_broker.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_fable_v2.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_fleet_transitions.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_fsm_redteam_evolution.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_goal_rubric_and_pipeline.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_hebbian_plasticity.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_packaging_runtime.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_proof_engine.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_red_team_seal_path.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_red_team_swarm.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_redteam_remediation.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_registration_transaction.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_requested_regressions.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_server_actions.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_server_frontier_actions.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_server_protocol.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_shell_call.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_skill_distribution.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_system3_deep_integration.py +0 -0
- {fable_engine-1.3.8 → fable_engine-1.3.9}/tests/test_system3_frontier.py +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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.
|
|
170
|
+
"args": ["--from", "fable-engine==1.3.9", "fable-engine"]
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
174
|
```
|
|
175
175
|
|
|
176
|
-
[](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.
|
|
176
|
+
[](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.
|
|
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.
|
|
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.
|
|
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.
|
|
127
|
+
"args": ["--from", "fable-engine==1.3.9", "fable-engine"]
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
-
[](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.
|
|
133
|
+
[](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.
|
|
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.
|
|
22
|
-
uvx --from fable-engine==1.3.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
286
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
"
|
|
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.
|
|
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."""
|
|
@@ -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.
|
|
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
|
-
|
|
253
|
-
|
|
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
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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) ->
|
|
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
|
-
|
|
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
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
170
|
+
"args": ["--from", "fable-engine==1.3.9", "fable-engine"]
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
174
|
```
|
|
175
175
|
|
|
176
|
-
[](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.
|
|
176
|
+
[](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.
|
|
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
|