@agentikos/omega-os 0.1.0 → 0.19.5
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.
- package/README.md +56 -14
- package/bootstrap/lib/__pycache__/claude-code-settings.cpython-313.pyc +0 -0
- package/bootstrap/lib/__pycache__/llm-clis.cpython-313.pyc +0 -0
- package/bootstrap/lib/__pycache__/manifest-helpers.cpython-313.pyc +0 -0
- package/bootstrap/lib/claude-code-settings.py +176 -0
- package/bootstrap/lib/common.sh +457 -1
- package/bootstrap/lib/llm-clis.py +341 -0
- package/bootstrap/lib/manifest-helpers.py +384 -0
- package/bootstrap/lib/steps.sh +1000 -26
- package/bootstrap/manifest.example.yaml +93 -2
- package/bootstrap/templates/aisb/CLAUDE.md +305 -0
- package/bootstrap/templates/aisb/architect.md +204 -0
- package/bootstrap/templates/aisb/checkers/CLAUDE.md +9 -0
- package/bootstrap/templates/aisb/checkers/checker-architect.md +151 -0
- package/bootstrap/templates/aisb/checkers/checker-common.md +171 -0
- package/bootstrap/templates/aisb/checkers/checker-construct.md +129 -0
- package/bootstrap/templates/aisb/checkers/checker-keymaker.md +204 -0
- package/bootstrap/templates/aisb/checkers/checker-link.md +205 -0
- package/bootstrap/templates/aisb/checkers/checker-merovingian.md +219 -0
- package/bootstrap/templates/aisb/checkers/checker-morpheus.md +211 -0
- package/bootstrap/templates/aisb/checkers/checker-neo.md +177 -0
- package/bootstrap/templates/aisb/checkers/checker-niobe.md +156 -0
- package/bootstrap/templates/aisb/checkers/checker-oracle.md +164 -0
- package/bootstrap/templates/aisb/checkers/checker-seraph.md +187 -0
- package/bootstrap/templates/aisb/checkers/checker-smith.md +195 -0
- package/bootstrap/templates/aisb/checkers/checker-zion.md +113 -0
- package/bootstrap/templates/aisb/construct.md +135 -0
- package/bootstrap/templates/aisb/keymaker.md +227 -0
- package/bootstrap/templates/aisb/link.md +170 -0
- package/bootstrap/templates/aisb/lmc-protocol.md +57 -0
- package/bootstrap/templates/aisb/merovingian.md +159 -0
- package/bootstrap/templates/aisb/morpheus.md +243 -0
- package/bootstrap/templates/aisb/neo.md +147 -0
- package/bootstrap/templates/aisb/niobe.md +197 -0
- package/bootstrap/templates/aisb/oracle.md +244 -0
- package/bootstrap/templates/aisb/protocols/handoff-templates.md +204 -0
- package/bootstrap/templates/aisb/protocols/shared-protocol.md +248 -0
- package/bootstrap/templates/aisb/pythia.md +153 -0
- package/bootstrap/templates/aisb/seraph.md +315 -0
- package/bootstrap/templates/aisb/smith.md +202 -0
- package/bootstrap/templates/aisb/zion.md +172 -0
- package/bootstrap/templates/autonomous/audit-patrol.yaml +41 -0
- package/bootstrap/templates/autonomous/smith-reflect.yaml +43 -0
- package/bootstrap/templates/autonomous/ssh-key-rotate.yaml +46 -0
- package/bootstrap/templates/autonomous/support-agent.yaml +38 -0
- package/docs/AUDITS.md +85 -0
- package/docs/COMPLETION-PLAN.md +48 -0
- package/docs/GAP-ANALYSIS.md +214 -0
- package/docs/INSTALL.md +47 -9
- package/docs/MCP-AND-PLUGINS.md +31 -4
- package/docs/SIMULATION.md +171 -0
- package/docs/simulate.sh +211 -0
- package/install.sh +164 -17
- package/omega/Agentik_Engine/README.md +27 -10
- package/omega/Agentik_Engine/omega_engine/__init__.py +212 -2
- package/omega/Agentik_Engine/omega_engine/__pycache__/__init__.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/account.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/agent_messages.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/aisb_chat.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/audit_diff.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/audit_gate.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/auto_update.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/autonomous.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/backup.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/cadence.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/classifier.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/cleanup.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/cli.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/completions.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/costs.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/done_signal.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/envelope.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/executor.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/handoff.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/hermes.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/hermes_bootstrap.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/hermes_desktop.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/learning.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/managed_agent.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/memory.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/menu.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/mission.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/plan.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/project.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/prompts.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/provider.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/prune.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/pursue.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/reducer.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/router.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/skill_routing.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/smoke.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/store.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/sync.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/telegram_history.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/tmux.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/tools.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/understand_anything.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/updater.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/validate.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/vault.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/webhooks.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/worker.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/account.py +502 -0
- package/omega/Agentik_Engine/omega_engine/agent_messages.py +167 -0
- package/omega/Agentik_Engine/omega_engine/aisb_chat.py +128 -0
- package/omega/Agentik_Engine/omega_engine/audit_diff.py +99 -0
- package/omega/Agentik_Engine/omega_engine/audit_gate.py +149 -0
- package/omega/Agentik_Engine/omega_engine/audits/__init__.py +60 -0
- package/omega/Agentik_Engine/omega_engine/audits/__pycache__/__init__.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/audits/__pycache__/batcher.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/audits/__pycache__/dispatcher.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/audits/__pycache__/generator.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/audits/__pycache__/history.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/audits/__pycache__/pipeline.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/audits/batcher.py +218 -0
- package/omega/Agentik_Engine/omega_engine/audits/dispatcher.py +92 -0
- package/omega/Agentik_Engine/omega_engine/audits/generator.py +234 -0
- package/omega/Agentik_Engine/omega_engine/audits/history.py +168 -0
- package/omega/Agentik_Engine/omega_engine/audits/pipeline.py +198 -0
- package/omega/Agentik_Engine/omega_engine/auto_update.py +339 -0
- package/omega/Agentik_Engine/omega_engine/autonomous.py +538 -0
- package/omega/Agentik_Engine/omega_engine/backup.py +215 -0
- package/omega/Agentik_Engine/omega_engine/cadence.py +158 -0
- package/omega/Agentik_Engine/omega_engine/classifier.py +215 -0
- package/omega/Agentik_Engine/omega_engine/cleanup.py +673 -0
- package/omega/Agentik_Engine/omega_engine/cli.py +4564 -56
- package/omega/Agentik_Engine/omega_engine/completions.py +260 -0
- package/omega/Agentik_Engine/omega_engine/costs.py +100 -0
- package/omega/Agentik_Engine/omega_engine/daemons/__init__.py +14 -0
- package/omega/Agentik_Engine/omega_engine/daemons/__pycache__/__init__.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/daemons/__pycache__/autonomous.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/daemons/__pycache__/engine.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/daemons/__pycache__/telegram.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/daemons/autonomous.py +56 -0
- package/omega/Agentik_Engine/omega_engine/daemons/engine.py +236 -0
- package/omega/Agentik_Engine/omega_engine/daemons/telegram.py +315 -0
- package/omega/Agentik_Engine/omega_engine/done_signal.py +154 -0
- package/omega/Agentik_Engine/omega_engine/educators/__init__.py +51 -0
- package/omega/Agentik_Engine/omega_engine/educators/__pycache__/__init__.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/educators/__pycache__/artifact.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/educators/__pycache__/automation.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/educators/__pycache__/base.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/educators/__pycache__/claudecode.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/educators/__pycache__/connection.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/educators/__pycache__/coworker.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/educators/__pycache__/loop.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/educators/__pycache__/prompt.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/educators/__pycache__/skill.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/educators/artifact.py +65 -0
- package/omega/Agentik_Engine/omega_engine/educators/automation.py +76 -0
- package/omega/Agentik_Engine/omega_engine/educators/base.py +327 -0
- package/omega/Agentik_Engine/omega_engine/educators/claudecode.py +71 -0
- package/omega/Agentik_Engine/omega_engine/educators/connection.py +75 -0
- package/omega/Agentik_Engine/omega_engine/educators/coworker.py +68 -0
- package/omega/Agentik_Engine/omega_engine/educators/loop.py +82 -0
- package/omega/Agentik_Engine/omega_engine/educators/prompt.py +68 -0
- package/omega/Agentik_Engine/omega_engine/educators/skill.py +69 -0
- package/omega/Agentik_Engine/omega_engine/envelope.py +219 -0
- package/omega/Agentik_Engine/omega_engine/executor.py +195 -16
- package/omega/Agentik_Engine/omega_engine/genesis/__init__.py +134 -0
- package/omega/Agentik_Engine/omega_engine/genesis/__pycache__/__init__.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/genesis/__pycache__/orchestrator.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/genesis/__pycache__/phases.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/genesis/__pycache__/stack.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/genesis/__pycache__/state.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/genesis/orchestrator.py +262 -0
- package/omega/Agentik_Engine/omega_engine/genesis/phases.py +950 -0
- package/omega/Agentik_Engine/omega_engine/genesis/stack.py +324 -0
- package/omega/Agentik_Engine/omega_engine/genesis/state.py +353 -0
- package/omega/Agentik_Engine/omega_engine/handoff.py +459 -0
- package/omega/Agentik_Engine/omega_engine/hermes.py +426 -0
- package/omega/Agentik_Engine/omega_engine/hermes_bootstrap.py +382 -0
- package/omega/Agentik_Engine/omega_engine/hermes_desktop.py +469 -0
- package/omega/Agentik_Engine/omega_engine/integrations/__init__.py +30 -0
- package/omega/Agentik_Engine/omega_engine/integrations/__pycache__/__init__.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/integrations/__pycache__/graphify.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/integrations/graphify.py +234 -0
- package/omega/Agentik_Engine/omega_engine/learning.py +268 -0
- package/omega/Agentik_Engine/omega_engine/managed_agent.py +467 -0
- package/omega/Agentik_Engine/omega_engine/memory.py +271 -0
- package/omega/Agentik_Engine/omega_engine/menu.py +1065 -0
- package/omega/Agentik_Engine/omega_engine/migrations/__init__.py +144 -0
- package/omega/Agentik_Engine/omega_engine/migrations/__pycache__/__init__.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/migrations/__pycache__/v0_14_0.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/migrations/v0_14_0.py +29 -0
- package/omega/Agentik_Engine/omega_engine/mission.py +29 -14
- package/omega/Agentik_Engine/omega_engine/plan.py +846 -0
- package/omega/Agentik_Engine/omega_engine/prompts.py +158 -0
- package/omega/Agentik_Engine/omega_engine/provider.py +408 -13
- package/omega/Agentik_Engine/omega_engine/prune.py +151 -0
- package/omega/Agentik_Engine/omega_engine/pursue.py +205 -0
- package/omega/Agentik_Engine/omega_engine/rag/__init__.py +21 -0
- package/omega/Agentik_Engine/omega_engine/rag/__pycache__/__init__.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/rag/__pycache__/agentic.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/rag/__pycache__/base.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/rag/__pycache__/corrective.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/rag/__pycache__/graph.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/rag/__pycache__/hybrid.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/rag/__pycache__/multimodal.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/rag/__pycache__/router.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/rag/agentic.py +83 -0
- package/omega/Agentik_Engine/omega_engine/rag/base.py +42 -0
- package/omega/Agentik_Engine/omega_engine/rag/corrective.py +119 -0
- package/omega/Agentik_Engine/omega_engine/rag/graph.py +169 -0
- package/omega/Agentik_Engine/omega_engine/rag/hybrid.py +205 -0
- package/omega/Agentik_Engine/omega_engine/rag/multimodal.py +136 -0
- package/omega/Agentik_Engine/omega_engine/rag/router.py +110 -0
- package/omega/Agentik_Engine/omega_engine/reducer.py +21 -3
- package/omega/Agentik_Engine/omega_engine/router.py +28 -0
- package/omega/Agentik_Engine/omega_engine/skill_discovery/__init__.py +48 -0
- package/omega/Agentik_Engine/omega_engine/skill_discovery/__pycache__/__init__.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/skill_discovery/__pycache__/auditor.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/skill_discovery/__pycache__/finder.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/skill_discovery/__pycache__/installer.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/skill_discovery/__pycache__/marketplaces.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/skill_discovery/auditor.py +232 -0
- package/omega/Agentik_Engine/omega_engine/skill_discovery/finder.py +94 -0
- package/omega/Agentik_Engine/omega_engine/skill_discovery/installer.py +129 -0
- package/omega/Agentik_Engine/omega_engine/skill_discovery/marketplaces.py +80 -0
- package/omega/Agentik_Engine/omega_engine/skill_routing.py +388 -0
- package/omega/Agentik_Engine/omega_engine/smoke.py +81 -0
- package/omega/Agentik_Engine/omega_engine/store.py +132 -25
- package/omega/Agentik_Engine/omega_engine/sync.py +445 -0
- package/omega/Agentik_Engine/omega_engine/telegram_history.py +260 -0
- package/omega/Agentik_Engine/omega_engine/tmux.py +526 -0
- package/omega/Agentik_Engine/omega_engine/tools.py +272 -0
- package/omega/Agentik_Engine/omega_engine/understand_anything.py +275 -0
- package/omega/Agentik_Engine/omega_engine/updater.py +70 -0
- package/omega/Agentik_Engine/omega_engine/validate.py +186 -0
- package/omega/Agentik_Engine/omega_engine/vault.py +342 -0
- package/omega/Agentik_Engine/omega_engine/webhooks.py +262 -0
- package/omega/Agentik_Engine/omega_engine/worker.py +526 -0
- package/omega/Agentik_Engine/pyproject.toml +1 -1
- package/omega/Agentik_Engine/tests/__pycache__/test_account.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_account.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_adversarial.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_adversarial.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_agents_envelope.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_agents_envelope.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_audit_arsenal.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_audits_pipeline.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_audits_pipeline.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_auto_update_and_migrations.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_auto_update_and_migrations.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_autonomous.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_autonomous.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_educators.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_educators.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_executor.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_genesis_and_plan.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_genesis_and_plan.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_graphify.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_graphify.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_handoff.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_handoff.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_hermes_and_ua.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_hermes_and_ua.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_hermes_bootstrap_and_desktop.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_hermes_bootstrap_and_desktop.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_install_steps.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_install_steps.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_install_ux.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_install_ux.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_installer_wiring.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_installer_wiring.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_intelligence.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_intelligence.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_llm_clis_and_uninstall.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_llm_clis_and_uninstall.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_managed_agent.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_managed_agent.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_max_provider_and_menu.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_max_provider_and_menu.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_menu_coverage.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_menu_coverage.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_mission.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_progress.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_project.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_pursue_cadence.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_pursue_cadence.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_rag.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_rag.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_reducer.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_report.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_role_aliases_and_ssot.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_role_aliases_and_ssot.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_skill_discovery_and_gate.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_skill_discovery_and_gate.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_skill_power.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_skill_power.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_skill_routing.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_skill_routing.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_snapshot_partial.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_snapshot_partial.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_telegram_history.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_telegram_history.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_tmux_and_aisb_chat.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_tmux_and_aisb_chat.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_tools_and_sync.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_tools_and_sync.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_v06_features.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_v06_features.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_vault.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_vault.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_webhooks_and_readiness.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_webhooks_and_readiness.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_worker_and_cleanup.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_worker_and_cleanup.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/test_account.py +338 -0
- package/omega/Agentik_Engine/tests/test_adversarial.py +351 -0
- package/omega/Agentik_Engine/tests/test_agents_envelope.py +274 -0
- package/omega/Agentik_Engine/tests/test_audits_pipeline.py +348 -0
- package/omega/Agentik_Engine/tests/test_auto_update_and_migrations.py +394 -0
- package/omega/Agentik_Engine/tests/test_autonomous.py +361 -0
- package/omega/Agentik_Engine/tests/test_educators.py +233 -0
- package/omega/Agentik_Engine/tests/test_genesis_and_plan.py +573 -0
- package/omega/Agentik_Engine/tests/test_graphify.py +190 -0
- package/omega/Agentik_Engine/tests/test_handoff.py +311 -0
- package/omega/Agentik_Engine/tests/test_hermes_and_ua.py +387 -0
- package/omega/Agentik_Engine/tests/test_hermes_bootstrap_and_desktop.py +358 -0
- package/omega/Agentik_Engine/tests/test_install_steps.py +359 -0
- package/omega/Agentik_Engine/tests/test_install_ux.py +151 -0
- package/omega/Agentik_Engine/tests/test_installer_wiring.py +496 -0
- package/omega/Agentik_Engine/tests/test_intelligence.py +285 -0
- package/omega/Agentik_Engine/tests/test_llm_clis_and_uninstall.py +228 -0
- package/omega/Agentik_Engine/tests/test_managed_agent.py +363 -0
- package/omega/Agentik_Engine/tests/test_max_provider_and_menu.py +231 -0
- package/omega/Agentik_Engine/tests/test_menu_coverage.py +72 -0
- package/omega/Agentik_Engine/tests/test_pursue_cadence.py +217 -0
- package/omega/Agentik_Engine/tests/test_rag.py +287 -0
- package/omega/Agentik_Engine/tests/test_role_aliases_and_ssot.py +207 -0
- package/omega/Agentik_Engine/tests/test_skill_discovery_and_gate.py +337 -0
- package/omega/Agentik_Engine/tests/test_skill_power.py +259 -0
- package/omega/Agentik_Engine/tests/test_skill_routing.py +189 -0
- package/omega/Agentik_Engine/tests/test_snapshot_partial.py +172 -0
- package/omega/Agentik_Engine/tests/test_telegram_history.py +209 -0
- package/omega/Agentik_Engine/tests/test_tmux_and_aisb_chat.py +223 -0
- package/omega/Agentik_Engine/tests/test_tools_and_sync.py +312 -0
- package/omega/Agentik_Engine/tests/test_v06_features.py +370 -0
- package/omega/Agentik_Engine/tests/test_vault.py +173 -0
- package/omega/Agentik_Engine/tests/test_webhooks_and_readiness.py +277 -0
- package/omega/Agentik_Engine/tests/test_worker_and_cleanup.py +541 -0
- package/omega/Agentik_Extra/etc/secrets/.vault-key +3 -0
- package/omega/Agentik_Extra/etc/secrets/.vault-pub +1 -0
- package/omega/Agentik_Runtime/audits.db +0 -0
- package/omega/Agentik_SSOT/VERSION +1 -1
- package/omega/Agentik_SSOT/claude-plugins/claude-plugins.yaml +100 -0
- package/omega/Agentik_SSOT/docs/LAYERS.md +90 -0
- package/omega/Agentik_SSOT/docs/USER-JOURNEY.md +283 -0
- package/omega/Agentik_SSOT/marketplaces/design-discipline.yaml +86 -0
- package/omega/Agentik_SSOT/skills/a11yaudit/SKILL.md +161 -0
- package/omega/Agentik_SSOT/skills/apiaudit/SKILL.md +157 -0
- package/omega/Agentik_SSOT/skills/automationaudit/SKILL.md +161 -0
- package/omega/Agentik_SSOT/skills/cadence/SKILL.md +76 -0
- package/omega/Agentik_SSOT/skills/codeaudit/SKILL.md +153 -0
- package/omega/Agentik_SSOT/skills/copyaudit/SKILL.md +161 -0
- package/omega/Agentik_SSOT/skills/dataaudit/SKILL.md +157 -0
- package/omega/Agentik_SSOT/skills/debugaudit/SKILL.md +161 -0
- package/omega/Agentik_SSOT/skills/dispatch/SKILL.md +79 -0
- package/omega/Agentik_SSOT/skills/dxaudit/SKILL.md +161 -0
- package/omega/Agentik_SSOT/skills/featureaudit/SKILL.md +161 -0
- package/omega/Agentik_SSOT/skills/flowaudit/SKILL.md +165 -0
- package/omega/Agentik_SSOT/skills/genesis/SKILL.md +116 -0
- package/omega/Agentik_SSOT/skills/handoff/SKILL.md +117 -0
- package/omega/Agentik_SSOT/skills/logicaudit/SKILL.md +165 -0
- package/omega/Agentik_SSOT/skills/motionaudit/SKILL.md +165 -0
- package/omega/Agentik_SSOT/skills/perfaudit/SKILL.md +161 -0
- package/omega/Agentik_SSOT/skills/plan/SKILL.md +127 -0
- package/omega/Agentik_SSOT/skills/pursue/SKILL.md +68 -0
- package/omega/Agentik_SSOT/skills/rag-route.md +82 -0
- package/omega/Agentik_SSOT/skills/refontaudit/SKILL.md +165 -0
- package/omega/Agentik_SSOT/skills/retentionaudit/SKILL.md +165 -0
- package/omega/Agentik_SSOT/skills/secaudit/SKILL.md +157 -0
- package/omega/Agentik_SSOT/skills/seoaudit/SKILL.md +161 -0
- package/omega/Agentik_SSOT/skills/skill-auditor/SKILL.md +83 -0
- package/omega/Agentik_SSOT/skills/skill-finder/SKILL.md +116 -0
- package/omega/Agentik_SSOT/skills/uiuxaudit/SKILL.md +165 -0
- package/package.json +2 -2
|
@@ -0,0 +1,846 @@
|
|
|
1
|
+
"""Plan v7 — FSM-enforced sequential plan executor.
|
|
2
|
+
|
|
3
|
+
What pre-v0.19 got wrong
|
|
4
|
+
------------------------
|
|
5
|
+
|
|
6
|
+
The legacy ``/planner`` skill stored everything in a flat ``tracker.json``
|
|
7
|
+
that the agent read end-to-end at every turn. Three failure modes:
|
|
8
|
+
|
|
9
|
+
1. **Visibility leak.** With 1500 tasks visible, an agent picks by bias
|
|
10
|
+
(short, easy, exciting) and skips ahead — "I'll do T-090 first,
|
|
11
|
+
it's the fun one". The user observed it jumping from T-050 to T-090
|
|
12
|
+
without finishing T-051..T-089.
|
|
13
|
+
2. **Soft constraint.** The order was a PROMPT instruction
|
|
14
|
+
("NEVER skip a step"). No runtime checks. The agent could violate
|
|
15
|
+
and nothing rejected the violation.
|
|
16
|
+
3. **No explicit DAG.** Dependencies were implicit in numbering. Even
|
|
17
|
+
an honest agent couldn't tell that T-007 depended on T-005.
|
|
18
|
+
|
|
19
|
+
What this module does instead
|
|
20
|
+
-----------------------------
|
|
21
|
+
|
|
22
|
+
* **Persistent store.** Every task is a row in SQLite (per-project DB).
|
|
23
|
+
The agent sees one task at a time via ``next_eligible``, never the
|
|
24
|
+
whole list.
|
|
25
|
+
* **Explicit ``depends_on``.** Every task carries its predecessors as
|
|
26
|
+
a list of task ids. The plan refuses to advance a task whose deps
|
|
27
|
+
aren't all VERIFIED.
|
|
28
|
+
* **FSM enforced at engine level.** Transitions are validated by the
|
|
29
|
+
reducer. An attempt to mark T-090 DONE while T-089 is still PENDING
|
|
30
|
+
raises ``IllegalTransition`` — the agent CANNOT bypass it.
|
|
31
|
+
* **Verified completion.** Each task has a ``verify_cmd``. ``mark_done``
|
|
32
|
+
runs it inside the project root and refuses to flip to VERIFIED if
|
|
33
|
+
the command fails.
|
|
34
|
+
|
|
35
|
+
Public API (consumed by ``omega plan`` CLI)
|
|
36
|
+
-------------------------------------------
|
|
37
|
+
|
|
38
|
+
build_plan(slug) — read plan/DAG.json + Genesis features → load store
|
|
39
|
+
next_eligible(slug) — return the next PENDING task with all deps VERIFIED
|
|
40
|
+
mark_in_progress(slug, task_id)
|
|
41
|
+
mark_done(slug, task_id, *, verify=True) — runs verify_cmd, refuses if FAIL
|
|
42
|
+
mark_failed(slug, task_id, reason)
|
|
43
|
+
status(slug) — counts per state + active task
|
|
44
|
+
list_tasks(slug, *, state=None)
|
|
45
|
+
replan(slug) — reload DAG.json (idempotent)
|
|
46
|
+
|
|
47
|
+
The store
|
|
48
|
+
---------
|
|
49
|
+
|
|
50
|
+
One SQLite DB per project at::
|
|
51
|
+
|
|
52
|
+
$OMEGA_HOME/Agentik_Coding/projects/<slug>/plan/plan.db
|
|
53
|
+
|
|
54
|
+
Schema::
|
|
55
|
+
|
|
56
|
+
CREATE TABLE tasks (
|
|
57
|
+
id TEXT PRIMARY KEY, -- F-001, F-002, ...
|
|
58
|
+
slug TEXT NOT NULL,
|
|
59
|
+
title TEXT NOT NULL,
|
|
60
|
+
depends_on TEXT NOT NULL, -- JSON array of task ids
|
|
61
|
+
wave INTEGER NOT NULL,
|
|
62
|
+
estimated_minutes INTEGER NOT NULL,
|
|
63
|
+
files_owned TEXT NOT NULL, -- JSON array
|
|
64
|
+
verify_cmd TEXT NOT NULL,
|
|
65
|
+
skill TEXT NOT NULL,
|
|
66
|
+
state TEXT NOT NULL, -- pending | in_progress | claimed_done | verified | failed
|
|
67
|
+
started_at REAL,
|
|
68
|
+
finished_at REAL,
|
|
69
|
+
last_attempt_at REAL,
|
|
70
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
71
|
+
last_error TEXT
|
|
72
|
+
);
|
|
73
|
+
CREATE INDEX idx_state ON tasks(state);
|
|
74
|
+
CREATE INDEX idx_wave ON tasks(wave);
|
|
75
|
+
|
|
76
|
+
The FSM
|
|
77
|
+
-------
|
|
78
|
+
|
|
79
|
+
pending → in_progress (next_eligible picked it)
|
|
80
|
+
in_progress → claimed_done (worker wrote .done.json; not yet trusted)
|
|
81
|
+
claimed_done → verified (verify_cmd exited 0 — TERMINAL ok)
|
|
82
|
+
in_progress → failed (worker exhausted retries — TERMINAL bad)
|
|
83
|
+
claimed_done → failed (verify_cmd exited != 0 → also TERMINAL bad)
|
|
84
|
+
|
|
85
|
+
The reducer (below) is the ONLY place that touches the ``state`` column.
|
|
86
|
+
Every transition validates the predecessors are all VERIFIED. There is
|
|
87
|
+
no other code path that can change task state.
|
|
88
|
+
"""
|
|
89
|
+
from __future__ import annotations
|
|
90
|
+
|
|
91
|
+
import json
|
|
92
|
+
import os
|
|
93
|
+
import sqlite3
|
|
94
|
+
import subprocess
|
|
95
|
+
import threading
|
|
96
|
+
import time
|
|
97
|
+
from dataclasses import asdict, dataclass, field
|
|
98
|
+
from pathlib import Path
|
|
99
|
+
from typing import Any
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# Valid states + transitions
|
|
103
|
+
PENDING = "pending"
|
|
104
|
+
IN_PROGRESS = "in_progress"
|
|
105
|
+
CLAIMED_DONE = "claimed_done"
|
|
106
|
+
VERIFIED = "verified"
|
|
107
|
+
FAILED = "failed"
|
|
108
|
+
|
|
109
|
+
_TERMINAL = (VERIFIED, FAILED)
|
|
110
|
+
|
|
111
|
+
# Allowed (current, next) transitions. Anything outside this set raises.
|
|
112
|
+
_ALLOWED: set[tuple[str, str]] = {
|
|
113
|
+
(PENDING, IN_PROGRESS),
|
|
114
|
+
(IN_PROGRESS, CLAIMED_DONE),
|
|
115
|
+
(CLAIMED_DONE, VERIFIED),
|
|
116
|
+
(CLAIMED_DONE, FAILED),
|
|
117
|
+
(IN_PROGRESS, FAILED),
|
|
118
|
+
# retry: a FAILED task can be reset to PENDING (operator explicit)
|
|
119
|
+
(FAILED, PENDING),
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class PlanError(RuntimeError):
|
|
124
|
+
"""Raised when an operation violates the plan FSM contract."""
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# ---------------------------------------------------------------------------
|
|
128
|
+
# Dataclass mirror of the row
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@dataclass
|
|
133
|
+
class PlanTask:
|
|
134
|
+
id: str
|
|
135
|
+
slug: str
|
|
136
|
+
title: str
|
|
137
|
+
depends_on: list[str]
|
|
138
|
+
wave: int
|
|
139
|
+
estimated_minutes: int
|
|
140
|
+
files_owned: list[str]
|
|
141
|
+
verify_cmd: str
|
|
142
|
+
skill: str
|
|
143
|
+
state: str = PENDING
|
|
144
|
+
started_at: float | None = None
|
|
145
|
+
finished_at: float | None = None
|
|
146
|
+
last_attempt_at: float | None = None
|
|
147
|
+
attempts: int = 0
|
|
148
|
+
last_error: str = ""
|
|
149
|
+
|
|
150
|
+
def to_dict(self) -> dict[str, Any]:
|
|
151
|
+
return asdict(self)
|
|
152
|
+
|
|
153
|
+
@staticmethod
|
|
154
|
+
def from_row(row: sqlite3.Row) -> "PlanTask":
|
|
155
|
+
return PlanTask(
|
|
156
|
+
id=row["id"], slug=row["slug"], title=row["title"],
|
|
157
|
+
depends_on=json.loads(row["depends_on"]),
|
|
158
|
+
wave=row["wave"], estimated_minutes=row["estimated_minutes"],
|
|
159
|
+
files_owned=json.loads(row["files_owned"]),
|
|
160
|
+
verify_cmd=row["verify_cmd"], skill=row["skill"],
|
|
161
|
+
state=row["state"],
|
|
162
|
+
started_at=row["started_at"], finished_at=row["finished_at"],
|
|
163
|
+
last_attempt_at=row["last_attempt_at"],
|
|
164
|
+
attempts=row["attempts"], last_error=row["last_error"] or "",
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# ---------------------------------------------------------------------------
|
|
169
|
+
# Path resolver — strict per-project isolation
|
|
170
|
+
# ---------------------------------------------------------------------------
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _omega_home(explicit: str | Path | None = None) -> Path:
|
|
174
|
+
if explicit is not None:
|
|
175
|
+
return Path(explicit)
|
|
176
|
+
return Path(os.environ.get("OMEGA_HOME", str(Path.home() / "Omega")))
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def project_root(slug: str, omega_home: str | Path | None = None) -> Path:
|
|
180
|
+
return _omega_home(omega_home) / "Agentik_Coding" / "projects" / slug
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def db_path(slug: str, omega_home: str | Path | None = None) -> Path:
|
|
184
|
+
return project_root(slug, omega_home) / "plan" / "plan.db"
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def dag_path(slug: str, omega_home: str | Path | None = None) -> Path:
|
|
188
|
+
return project_root(slug, omega_home) / "plan" / "DAG.json"
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# ---------------------------------------------------------------------------
|
|
192
|
+
# DB bootstrap
|
|
193
|
+
# ---------------------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
_SCHEMA = """
|
|
197
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
198
|
+
id TEXT PRIMARY KEY,
|
|
199
|
+
slug TEXT NOT NULL,
|
|
200
|
+
title TEXT NOT NULL,
|
|
201
|
+
depends_on TEXT NOT NULL,
|
|
202
|
+
wave INTEGER NOT NULL,
|
|
203
|
+
estimated_minutes INTEGER NOT NULL,
|
|
204
|
+
files_owned TEXT NOT NULL,
|
|
205
|
+
verify_cmd TEXT NOT NULL,
|
|
206
|
+
skill TEXT NOT NULL,
|
|
207
|
+
state TEXT NOT NULL,
|
|
208
|
+
started_at REAL,
|
|
209
|
+
finished_at REAL,
|
|
210
|
+
last_attempt_at REAL,
|
|
211
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
212
|
+
last_error TEXT
|
|
213
|
+
);
|
|
214
|
+
CREATE INDEX IF NOT EXISTS idx_state ON tasks(state);
|
|
215
|
+
CREATE INDEX IF NOT EXISTS idx_wave ON tasks(wave);
|
|
216
|
+
"""
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _connect(slug: str, omega_home: str | Path | None = None) -> sqlite3.Connection:
|
|
220
|
+
p = db_path(slug, omega_home)
|
|
221
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
222
|
+
conn = sqlite3.connect(str(p), isolation_level=None, check_same_thread=False)
|
|
223
|
+
conn.row_factory = sqlite3.Row
|
|
224
|
+
conn.execute("PRAGMA journal_mode=WAL;")
|
|
225
|
+
conn.execute("PRAGMA synchronous=NORMAL;")
|
|
226
|
+
conn.executescript(_SCHEMA)
|
|
227
|
+
return conn
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
# Per-connection lock to serialise writes across threads.
|
|
231
|
+
_locks: dict[str, threading.Lock] = {}
|
|
232
|
+
_locks_lock = threading.Lock()
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _lock_for(slug: str) -> threading.Lock:
|
|
236
|
+
with _locks_lock:
|
|
237
|
+
if slug not in _locks:
|
|
238
|
+
_locks[slug] = threading.Lock()
|
|
239
|
+
return _locks[slug]
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# ---------------------------------------------------------------------------
|
|
243
|
+
# build_plan — load DAG.json into the store
|
|
244
|
+
# ---------------------------------------------------------------------------
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _validate_dag(features: list[dict[str, Any]]) -> None:
|
|
248
|
+
"""Topological soundness + uniqueness check."""
|
|
249
|
+
ids = [f["id"] for f in features]
|
|
250
|
+
if len(ids) != len(set(ids)):
|
|
251
|
+
dupes = [x for x in ids if ids.count(x) > 1]
|
|
252
|
+
raise PlanError(f"duplicate feature ids in DAG: {sorted(set(dupes))}")
|
|
253
|
+
id_set = set(ids)
|
|
254
|
+
for f in features:
|
|
255
|
+
for dep in f.get("depends_on", []):
|
|
256
|
+
if dep not in id_set:
|
|
257
|
+
raise PlanError(
|
|
258
|
+
f"feature {f['id']} depends on unknown {dep!r}"
|
|
259
|
+
)
|
|
260
|
+
# Cycle detection via Kahn's algorithm
|
|
261
|
+
indeg = {fid: 0 for fid in ids}
|
|
262
|
+
for f in features:
|
|
263
|
+
for dep in f.get("depends_on", []):
|
|
264
|
+
indeg[f["id"]] += 1
|
|
265
|
+
queue = [fid for fid, d in indeg.items() if d == 0]
|
|
266
|
+
seen = 0
|
|
267
|
+
edges = {f["id"]: list(f.get("depends_on", [])) for f in features}
|
|
268
|
+
rev = {fid: [] for fid in ids}
|
|
269
|
+
for fid, deps in edges.items():
|
|
270
|
+
for d in deps:
|
|
271
|
+
rev[d].append(fid)
|
|
272
|
+
while queue:
|
|
273
|
+
cur = queue.pop()
|
|
274
|
+
seen += 1
|
|
275
|
+
for nxt in rev[cur]:
|
|
276
|
+
indeg[nxt] -= 1
|
|
277
|
+
if indeg[nxt] == 0:
|
|
278
|
+
queue.append(nxt)
|
|
279
|
+
if seen != len(ids):
|
|
280
|
+
raise PlanError("DAG has a cycle — features must be acyclic")
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def build_plan(
|
|
284
|
+
slug: str,
|
|
285
|
+
*,
|
|
286
|
+
omega_home: str | Path | None = None,
|
|
287
|
+
reset_failed: bool = False,
|
|
288
|
+
) -> dict[str, Any]:
|
|
289
|
+
"""Read ``plan/DAG.json``, validate the DAG, upsert every task.
|
|
290
|
+
|
|
291
|
+
Idempotent: re-running picks up new features and updates titles +
|
|
292
|
+
depends_on for known ones. State is PRESERVED (a task already
|
|
293
|
+
VERIFIED stays VERIFIED). If ``reset_failed=True``, every FAILED task
|
|
294
|
+
is re-set to PENDING.
|
|
295
|
+
"""
|
|
296
|
+
dp = dag_path(slug, omega_home)
|
|
297
|
+
if not dp.exists():
|
|
298
|
+
raise PlanError(
|
|
299
|
+
f"no DAG at {dp} — run `omega genesis run {slug}` to produce it"
|
|
300
|
+
)
|
|
301
|
+
data = json.loads(dp.read_text())
|
|
302
|
+
features = data.get("features") or []
|
|
303
|
+
_validate_dag(features)
|
|
304
|
+
|
|
305
|
+
inserted = 0
|
|
306
|
+
updated = 0
|
|
307
|
+
with _lock_for(slug):
|
|
308
|
+
conn = _connect(slug, omega_home)
|
|
309
|
+
try:
|
|
310
|
+
existing = {
|
|
311
|
+
r["id"]: r for r in conn.execute(
|
|
312
|
+
"SELECT * FROM tasks WHERE slug = ?", (slug,)
|
|
313
|
+
).fetchall()
|
|
314
|
+
}
|
|
315
|
+
for f in features:
|
|
316
|
+
fid = f["id"]
|
|
317
|
+
if fid in existing:
|
|
318
|
+
# Update mutable metadata only; preserve state +
|
|
319
|
+
# timing fields.
|
|
320
|
+
conn.execute(
|
|
321
|
+
"UPDATE tasks SET title=?, depends_on=?, wave=?, "
|
|
322
|
+
"estimated_minutes=?, files_owned=?, verify_cmd=?, "
|
|
323
|
+
"skill=? WHERE id=?",
|
|
324
|
+
(
|
|
325
|
+
f["title"], json.dumps(f.get("depends_on", [])),
|
|
326
|
+
int(f.get("wave", 1)),
|
|
327
|
+
int(f.get("estimated_minutes", 30)),
|
|
328
|
+
json.dumps(f.get("files_owned", [])),
|
|
329
|
+
f.get("verify_cmd", "true"),
|
|
330
|
+
f.get("skill", ""),
|
|
331
|
+
fid,
|
|
332
|
+
),
|
|
333
|
+
)
|
|
334
|
+
updated += 1
|
|
335
|
+
else:
|
|
336
|
+
conn.execute(
|
|
337
|
+
"INSERT INTO tasks(id, slug, title, depends_on, wave, "
|
|
338
|
+
"estimated_minutes, files_owned, verify_cmd, skill, "
|
|
339
|
+
"state, attempts) VALUES (?,?,?,?,?,?,?,?,?,?,0)",
|
|
340
|
+
(
|
|
341
|
+
fid, slug, f["title"],
|
|
342
|
+
json.dumps(f.get("depends_on", [])),
|
|
343
|
+
int(f.get("wave", 1)),
|
|
344
|
+
int(f.get("estimated_minutes", 30)),
|
|
345
|
+
json.dumps(f.get("files_owned", [])),
|
|
346
|
+
f.get("verify_cmd", "true"),
|
|
347
|
+
f.get("skill", ""),
|
|
348
|
+
PENDING,
|
|
349
|
+
),
|
|
350
|
+
)
|
|
351
|
+
inserted += 1
|
|
352
|
+
if reset_failed:
|
|
353
|
+
conn.execute(
|
|
354
|
+
"UPDATE tasks SET state=?, last_error='' "
|
|
355
|
+
"WHERE slug=? AND state=?",
|
|
356
|
+
(PENDING, slug, FAILED),
|
|
357
|
+
)
|
|
358
|
+
finally:
|
|
359
|
+
conn.close()
|
|
360
|
+
|
|
361
|
+
return {"slug": slug, "inserted": inserted, "updated": updated,
|
|
362
|
+
"total": len(features)}
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
# ---------------------------------------------------------------------------
|
|
366
|
+
# State helpers
|
|
367
|
+
# ---------------------------------------------------------------------------
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def _get_task(
|
|
371
|
+
slug: str, task_id: str, *, omega_home: str | Path | None = None,
|
|
372
|
+
) -> PlanTask | None:
|
|
373
|
+
conn = _connect(slug, omega_home)
|
|
374
|
+
try:
|
|
375
|
+
row = conn.execute(
|
|
376
|
+
"SELECT * FROM tasks WHERE id = ? AND slug = ?", (task_id, slug),
|
|
377
|
+
).fetchone()
|
|
378
|
+
finally:
|
|
379
|
+
conn.close()
|
|
380
|
+
if not row:
|
|
381
|
+
return None
|
|
382
|
+
return PlanTask.from_row(row)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def list_tasks(
|
|
386
|
+
slug: str,
|
|
387
|
+
*,
|
|
388
|
+
state: str | None = None,
|
|
389
|
+
omega_home: str | Path | None = None,
|
|
390
|
+
) -> list[PlanTask]:
|
|
391
|
+
"""List every task for a project. Filter by state when provided."""
|
|
392
|
+
conn = _connect(slug, omega_home)
|
|
393
|
+
try:
|
|
394
|
+
if state is None:
|
|
395
|
+
rows = conn.execute(
|
|
396
|
+
"SELECT * FROM tasks WHERE slug = ? "
|
|
397
|
+
"ORDER BY wave, id", (slug,),
|
|
398
|
+
).fetchall()
|
|
399
|
+
else:
|
|
400
|
+
rows = conn.execute(
|
|
401
|
+
"SELECT * FROM tasks WHERE slug = ? AND state = ? "
|
|
402
|
+
"ORDER BY wave, id", (slug, state),
|
|
403
|
+
).fetchall()
|
|
404
|
+
finally:
|
|
405
|
+
conn.close()
|
|
406
|
+
return [PlanTask.from_row(r) for r in rows]
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def status(slug: str, *, omega_home: str | Path | None = None) -> dict[str, Any]:
|
|
410
|
+
"""Per-state counts + the currently-active task (if any) + completion %."""
|
|
411
|
+
conn = _connect(slug, omega_home)
|
|
412
|
+
try:
|
|
413
|
+
counts = {
|
|
414
|
+
r["state"]: r["c"] for r in conn.execute(
|
|
415
|
+
"SELECT state, COUNT(*) AS c FROM tasks WHERE slug = ? "
|
|
416
|
+
"GROUP BY state", (slug,),
|
|
417
|
+
).fetchall()
|
|
418
|
+
}
|
|
419
|
+
active = conn.execute(
|
|
420
|
+
"SELECT id, title FROM tasks WHERE slug = ? AND state = ? "
|
|
421
|
+
"LIMIT 1", (slug, IN_PROGRESS),
|
|
422
|
+
).fetchone()
|
|
423
|
+
total = conn.execute(
|
|
424
|
+
"SELECT COUNT(*) AS c FROM tasks WHERE slug = ?", (slug,),
|
|
425
|
+
).fetchone()["c"]
|
|
426
|
+
finally:
|
|
427
|
+
conn.close()
|
|
428
|
+
verified = counts.get(VERIFIED, 0)
|
|
429
|
+
pct = round(100.0 * verified / total, 1) if total else 0.0
|
|
430
|
+
return {
|
|
431
|
+
"slug": slug,
|
|
432
|
+
"total": total,
|
|
433
|
+
"counts": {
|
|
434
|
+
"pending": counts.get(PENDING, 0),
|
|
435
|
+
"in_progress": counts.get(IN_PROGRESS, 0),
|
|
436
|
+
"claimed_done": counts.get(CLAIMED_DONE, 0),
|
|
437
|
+
"verified": counts.get(VERIFIED, 0),
|
|
438
|
+
"failed": counts.get(FAILED, 0),
|
|
439
|
+
},
|
|
440
|
+
"active": dict(active) if active else None,
|
|
441
|
+
"completion_pct": pct,
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
# ---------------------------------------------------------------------------
|
|
446
|
+
# The reducer — the ONLY place state transitions happen
|
|
447
|
+
# ---------------------------------------------------------------------------
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def _are_deps_verified(
|
|
451
|
+
conn: sqlite3.Connection, slug: str, deps: list[str],
|
|
452
|
+
) -> tuple[bool, list[str]]:
|
|
453
|
+
"""Return (ok, missing). ``missing`` is the list of deps that aren't
|
|
454
|
+
VERIFIED yet — used to compose a clear error message."""
|
|
455
|
+
if not deps:
|
|
456
|
+
return True, []
|
|
457
|
+
placeholders = ",".join("?" * len(deps))
|
|
458
|
+
rows = conn.execute(
|
|
459
|
+
f"SELECT id, state FROM tasks WHERE slug=? AND id IN ({placeholders})",
|
|
460
|
+
(slug, *deps),
|
|
461
|
+
).fetchall()
|
|
462
|
+
have = {r["id"]: r["state"] for r in rows}
|
|
463
|
+
missing = [d for d in deps if have.get(d) != VERIFIED]
|
|
464
|
+
return (len(missing) == 0), missing
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def _transition(
|
|
468
|
+
slug: str, task_id: str, new_state: str,
|
|
469
|
+
*,
|
|
470
|
+
omega_home: str | Path | None = None,
|
|
471
|
+
skip_dep_check: bool = False,
|
|
472
|
+
error: str = "",
|
|
473
|
+
attempts_delta: int = 0,
|
|
474
|
+
) -> PlanTask:
|
|
475
|
+
"""Atomically transition one task. The single funnel for every state change.
|
|
476
|
+
|
|
477
|
+
Validates:
|
|
478
|
+
1. Task exists.
|
|
479
|
+
2. (current → new) is in the ALLOWED set.
|
|
480
|
+
3. When new == IN_PROGRESS, every dependency must be VERIFIED.
|
|
481
|
+
4. When new == VERIFIED, the previous state must be CLAIMED_DONE
|
|
482
|
+
AND every dependency must be VERIFIED (defence in depth).
|
|
483
|
+
|
|
484
|
+
Raises :class:`PlanError` on any violation.
|
|
485
|
+
"""
|
|
486
|
+
with _lock_for(slug):
|
|
487
|
+
conn = _connect(slug, omega_home)
|
|
488
|
+
try:
|
|
489
|
+
row = conn.execute(
|
|
490
|
+
"SELECT * FROM tasks WHERE slug=? AND id=?",
|
|
491
|
+
(slug, task_id),
|
|
492
|
+
).fetchone()
|
|
493
|
+
if not row:
|
|
494
|
+
raise PlanError(f"unknown task {task_id!r} for slug {slug!r}")
|
|
495
|
+
cur_state = row["state"]
|
|
496
|
+
if (cur_state, new_state) not in _ALLOWED:
|
|
497
|
+
raise PlanError(
|
|
498
|
+
f"illegal transition {task_id}: "
|
|
499
|
+
f"{cur_state} → {new_state}"
|
|
500
|
+
)
|
|
501
|
+
# Dep enforcement
|
|
502
|
+
deps = json.loads(row["depends_on"])
|
|
503
|
+
if new_state in (IN_PROGRESS, VERIFIED) and not skip_dep_check:
|
|
504
|
+
ok, missing = _are_deps_verified(conn, slug, deps)
|
|
505
|
+
if not ok:
|
|
506
|
+
raise PlanError(
|
|
507
|
+
f"{task_id} cannot move to {new_state} — "
|
|
508
|
+
f"unverified deps: {missing}"
|
|
509
|
+
)
|
|
510
|
+
now = time.time()
|
|
511
|
+
updates: dict[str, Any] = {"state": new_state}
|
|
512
|
+
if new_state == IN_PROGRESS:
|
|
513
|
+
updates["started_at"] = now
|
|
514
|
+
updates["last_attempt_at"] = now
|
|
515
|
+
elif new_state == VERIFIED:
|
|
516
|
+
updates["finished_at"] = now
|
|
517
|
+
elif new_state == FAILED:
|
|
518
|
+
updates["finished_at"] = now
|
|
519
|
+
if error:
|
|
520
|
+
updates["last_error"] = error
|
|
521
|
+
elif new_state == PENDING:
|
|
522
|
+
# operator-issued retry — clear timing
|
|
523
|
+
updates["started_at"] = None
|
|
524
|
+
updates["finished_at"] = None
|
|
525
|
+
updates["last_error"] = ""
|
|
526
|
+
sets = ", ".join(f"{k}=?" for k in updates.keys())
|
|
527
|
+
args = list(updates.values())
|
|
528
|
+
if attempts_delta:
|
|
529
|
+
sets += ", attempts = attempts + ?"
|
|
530
|
+
args.append(attempts_delta)
|
|
531
|
+
args.append(slug)
|
|
532
|
+
args.append(task_id)
|
|
533
|
+
conn.execute(
|
|
534
|
+
f"UPDATE tasks SET {sets} WHERE slug=? AND id=?",
|
|
535
|
+
args,
|
|
536
|
+
)
|
|
537
|
+
# Re-read the row.
|
|
538
|
+
row = conn.execute(
|
|
539
|
+
"SELECT * FROM tasks WHERE slug=? AND id=?",
|
|
540
|
+
(slug, task_id),
|
|
541
|
+
).fetchone()
|
|
542
|
+
finally:
|
|
543
|
+
conn.close()
|
|
544
|
+
return PlanTask.from_row(row)
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
# ---------------------------------------------------------------------------
|
|
548
|
+
# Public state-change API
|
|
549
|
+
# ---------------------------------------------------------------------------
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def mark_in_progress(
|
|
553
|
+
slug: str, task_id: str, *,
|
|
554
|
+
omega_home: str | Path | None = None,
|
|
555
|
+
) -> PlanTask:
|
|
556
|
+
"""Move PENDING → IN_PROGRESS. Refuses if any dep isn't VERIFIED."""
|
|
557
|
+
return _transition(
|
|
558
|
+
slug, task_id, IN_PROGRESS, omega_home=omega_home,
|
|
559
|
+
attempts_delta=1,
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
def mark_claimed_done(
|
|
564
|
+
slug: str, task_id: str, *,
|
|
565
|
+
omega_home: str | Path | None = None,
|
|
566
|
+
) -> PlanTask:
|
|
567
|
+
"""Worker wrote .done.json → IN_PROGRESS → CLAIMED_DONE.
|
|
568
|
+
|
|
569
|
+
Not verified yet — the next call (``mark_done`` with verify) decides.
|
|
570
|
+
"""
|
|
571
|
+
return _transition(
|
|
572
|
+
slug, task_id, CLAIMED_DONE, omega_home=omega_home,
|
|
573
|
+
skip_dep_check=True, # deps were already checked at IN_PROGRESS
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
def mark_done(
|
|
578
|
+
slug: str, task_id: str, *,
|
|
579
|
+
omega_home: str | Path | None = None,
|
|
580
|
+
verify: bool = True,
|
|
581
|
+
verify_cwd: str | Path | None = None,
|
|
582
|
+
verify_timeout_s: int = 600,
|
|
583
|
+
) -> PlanTask:
|
|
584
|
+
"""Run the verify_cmd; CLAIMED_DONE → VERIFIED on exit 0,
|
|
585
|
+
CLAIMED_DONE → FAILED otherwise.
|
|
586
|
+
|
|
587
|
+
If ``verify=False``, the verification step is skipped (operator
|
|
588
|
+
override — useful when the verify_cmd doesn't yet exist).
|
|
589
|
+
"""
|
|
590
|
+
task = _get_task(slug, task_id, omega_home=omega_home)
|
|
591
|
+
if task is None:
|
|
592
|
+
raise PlanError(f"unknown task {task_id!r}")
|
|
593
|
+
if task.state != CLAIMED_DONE:
|
|
594
|
+
# Allow IN_PROGRESS → VERIFIED only when we explicitly skip verify
|
|
595
|
+
# (the operator knows what they're doing); otherwise demand the
|
|
596
|
+
# claimed_done pit-stop.
|
|
597
|
+
if task.state == IN_PROGRESS and not verify:
|
|
598
|
+
_transition(slug, task_id, CLAIMED_DONE,
|
|
599
|
+
omega_home=omega_home, skip_dep_check=True)
|
|
600
|
+
else:
|
|
601
|
+
raise PlanError(
|
|
602
|
+
f"{task_id} is in state {task.state}; "
|
|
603
|
+
f"expected {CLAIMED_DONE} before mark_done"
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
if verify:
|
|
607
|
+
cwd = Path(verify_cwd) if verify_cwd else project_root(slug, omega_home)
|
|
608
|
+
try:
|
|
609
|
+
proc = subprocess.run(
|
|
610
|
+
["bash", "-c", task.verify_cmd],
|
|
611
|
+
check=False, capture_output=True, text=True,
|
|
612
|
+
timeout=verify_timeout_s, cwd=str(cwd),
|
|
613
|
+
)
|
|
614
|
+
rc = proc.returncode
|
|
615
|
+
tail = (proc.stdout + "\n" + proc.stderr).strip()[-400:]
|
|
616
|
+
except subprocess.TimeoutExpired:
|
|
617
|
+
rc = 124
|
|
618
|
+
tail = f"verify_cmd timed out after {verify_timeout_s}s"
|
|
619
|
+
except OSError as exc:
|
|
620
|
+
rc = 127
|
|
621
|
+
tail = f"verify_cmd exec error: {exc}"
|
|
622
|
+
if rc != 0:
|
|
623
|
+
return _transition(
|
|
624
|
+
slug, task_id, FAILED, omega_home=omega_home,
|
|
625
|
+
error=f"verify_cmd rc={rc}: {tail}",
|
|
626
|
+
skip_dep_check=True,
|
|
627
|
+
)
|
|
628
|
+
|
|
629
|
+
return _transition(
|
|
630
|
+
slug, task_id, VERIFIED, omega_home=omega_home,
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
def mark_failed(
|
|
635
|
+
slug: str, task_id: str, reason: str = "",
|
|
636
|
+
*,
|
|
637
|
+
omega_home: str | Path | None = None,
|
|
638
|
+
) -> PlanTask:
|
|
639
|
+
"""Force-fail a task (operator action — e.g. mission abort)."""
|
|
640
|
+
return _transition(
|
|
641
|
+
slug, task_id, FAILED, omega_home=omega_home,
|
|
642
|
+
error=reason, skip_dep_check=True,
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
def retry(
|
|
647
|
+
slug: str, task_id: str, *,
|
|
648
|
+
omega_home: str | Path | None = None,
|
|
649
|
+
) -> PlanTask:
|
|
650
|
+
"""Reset a FAILED task to PENDING (operator decided to retry)."""
|
|
651
|
+
return _transition(
|
|
652
|
+
slug, task_id, PENDING, omega_home=omega_home,
|
|
653
|
+
skip_dep_check=True,
|
|
654
|
+
)
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
# ---------------------------------------------------------------------------
|
|
658
|
+
# next_eligible — the function workers call to know what to do next
|
|
659
|
+
# ---------------------------------------------------------------------------
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
@dataclass
|
|
663
|
+
class RunResult:
|
|
664
|
+
"""Result of an autonomous ``run`` loop iteration set."""
|
|
665
|
+
|
|
666
|
+
slug: str
|
|
667
|
+
iterations: int = 0
|
|
668
|
+
verified: list[str] = field(default_factory=list)
|
|
669
|
+
failed: list[str] = field(default_factory=list)
|
|
670
|
+
skipped_no_eligible: bool = False
|
|
671
|
+
elapsed_s: float = 0.0
|
|
672
|
+
final_status: dict[str, Any] = field(default_factory=dict)
|
|
673
|
+
|
|
674
|
+
def to_dict(self) -> dict[str, Any]:
|
|
675
|
+
return asdict(self)
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
def run(
|
|
679
|
+
slug: str,
|
|
680
|
+
*,
|
|
681
|
+
omega_home: str | Path | None = None,
|
|
682
|
+
worker_role: str = "worker_ui",
|
|
683
|
+
max_iterations: int | None = None,
|
|
684
|
+
dispatch: Any | None = None,
|
|
685
|
+
spawn_cmd: list[str] | None = None,
|
|
686
|
+
on_iteration: Any | None = None,
|
|
687
|
+
sleep_between_s: float = 0.0,
|
|
688
|
+
) -> RunResult:
|
|
689
|
+
"""Autonomous multi-day plan loop with FSM enforcement.
|
|
690
|
+
|
|
691
|
+
Walks ``pending → in_progress → claimed_done → verified`` for every
|
|
692
|
+
eligible task in turn, using ``next_eligible`` to pick the single
|
|
693
|
+
next task. Built so it can run UNATTENDED for hours / days inside a
|
|
694
|
+
detached tmux session.
|
|
695
|
+
|
|
696
|
+
Dispatch path
|
|
697
|
+
-------------
|
|
698
|
+
|
|
699
|
+
For each task:
|
|
700
|
+
|
|
701
|
+
1. ``mark_in_progress`` (FAILS via reducer if any dep isn't VERIFIED —
|
|
702
|
+
that's the cardinal invariant that fixes the v6 skip bug).
|
|
703
|
+
2. Call the ``dispatch`` callable (or run ``spawn_cmd`` via subprocess
|
|
704
|
+
if no callable was passed) with the task object. The callable
|
|
705
|
+
is responsible for invoking the worker (omega worker spawn / a
|
|
706
|
+
Hermes call / a /team mission / whatever) AND waiting for the
|
|
707
|
+
worker's ``.done.json``.
|
|
708
|
+
3. ``mark_claimed_done`` once the worker reported done.
|
|
709
|
+
4. ``mark_done`` which runs ``verify_cmd`` inside the project root.
|
|
710
|
+
If verify_cmd exits 0 → VERIFIED. Otherwise → FAILED.
|
|
711
|
+
|
|
712
|
+
The loop continues until ``next_eligible`` returns None OR
|
|
713
|
+
``max_iterations`` is hit. Failed tasks do NOT unlock their
|
|
714
|
+
dependants (correct behaviour — the loop falls naturally to
|
|
715
|
+
no-eligible after one failure cascade).
|
|
716
|
+
|
|
717
|
+
Parameters
|
|
718
|
+
----------
|
|
719
|
+
dispatch : callable(task: PlanTask) -> None
|
|
720
|
+
How to spawn the worker. None ⇒ use ``spawn_cmd``. If both are
|
|
721
|
+
None, the loop ONLY runs the engine's verify_cmd (useful for
|
|
722
|
+
dry-running a plan whose tasks are already implemented).
|
|
723
|
+
spawn_cmd : list[str]
|
|
724
|
+
Shell tokens to run for each task. ``{task_id}``, ``{slug}``,
|
|
725
|
+
``{title}``, ``{verify}``, ``{files}``, ``{skill}`` are substituted.
|
|
726
|
+
on_iteration : callable(task: PlanTask, result: str) -> None
|
|
727
|
+
Optional hook for telemetry / Telegram updates after each task.
|
|
728
|
+
"""
|
|
729
|
+
import time as _t
|
|
730
|
+
started = _t.time()
|
|
731
|
+
out = RunResult(slug=slug)
|
|
732
|
+
iters = 0
|
|
733
|
+
while True:
|
|
734
|
+
if max_iterations is not None and iters >= max_iterations:
|
|
735
|
+
break
|
|
736
|
+
task = next_eligible(slug, omega_home=omega_home)
|
|
737
|
+
if task is None:
|
|
738
|
+
out.skipped_no_eligible = True
|
|
739
|
+
break
|
|
740
|
+
iters += 1
|
|
741
|
+
# Step 1: PENDING → IN_PROGRESS (reducer-enforced dep check)
|
|
742
|
+
try:
|
|
743
|
+
mark_in_progress(slug, task.id, omega_home=omega_home)
|
|
744
|
+
except PlanError as exc:
|
|
745
|
+
# next_eligible promised this would work; if it didn't, the
|
|
746
|
+
# store is in a bad state. Skip + record + continue.
|
|
747
|
+
out.failed.append(task.id)
|
|
748
|
+
if on_iteration:
|
|
749
|
+
on_iteration(task, f"in_progress failed: {exc}")
|
|
750
|
+
continue
|
|
751
|
+
|
|
752
|
+
# Step 2: dispatch the worker
|
|
753
|
+
dispatch_err: str | None = None
|
|
754
|
+
try:
|
|
755
|
+
if dispatch is not None:
|
|
756
|
+
dispatch(task)
|
|
757
|
+
elif spawn_cmd:
|
|
758
|
+
substituted = [
|
|
759
|
+
seg.format(
|
|
760
|
+
task_id=task.id, slug=task.slug, title=task.title,
|
|
761
|
+
verify=task.verify_cmd, skill=task.skill,
|
|
762
|
+
files=",".join(task.files_owned),
|
|
763
|
+
) for seg in spawn_cmd
|
|
764
|
+
]
|
|
765
|
+
subprocess.run(substituted, check=False, timeout=3600)
|
|
766
|
+
# No dispatch + no spawn_cmd = test/dry mode → straight to claimed.
|
|
767
|
+
except Exception as exc: # noqa: BLE001
|
|
768
|
+
dispatch_err = f"dispatch raised: {exc!r}"
|
|
769
|
+
|
|
770
|
+
# Step 3: mark_claimed_done (engine-side bookkeeping)
|
|
771
|
+
try:
|
|
772
|
+
mark_claimed_done(slug, task.id, omega_home=omega_home)
|
|
773
|
+
except PlanError as exc:
|
|
774
|
+
mark_failed(slug, task.id,
|
|
775
|
+
reason=f"could not move to claimed_done: {exc}",
|
|
776
|
+
omega_home=omega_home)
|
|
777
|
+
out.failed.append(task.id)
|
|
778
|
+
if on_iteration:
|
|
779
|
+
on_iteration(task, f"claimed failed: {exc}")
|
|
780
|
+
continue
|
|
781
|
+
|
|
782
|
+
# Step 4: verify_cmd runs (engine, not worker) → VERIFIED or FAILED
|
|
783
|
+
try:
|
|
784
|
+
t = mark_done(slug, task.id, omega_home=omega_home, verify=True)
|
|
785
|
+
if t.state == VERIFIED:
|
|
786
|
+
out.verified.append(task.id)
|
|
787
|
+
if on_iteration:
|
|
788
|
+
on_iteration(task, "verified")
|
|
789
|
+
else:
|
|
790
|
+
out.failed.append(task.id)
|
|
791
|
+
if on_iteration:
|
|
792
|
+
on_iteration(task, f"failed: {t.last_error}")
|
|
793
|
+
except PlanError as exc:
|
|
794
|
+
mark_failed(slug, task.id, reason=str(exc),
|
|
795
|
+
omega_home=omega_home)
|
|
796
|
+
out.failed.append(task.id)
|
|
797
|
+
if on_iteration:
|
|
798
|
+
on_iteration(task, f"verify raised: {exc}")
|
|
799
|
+
|
|
800
|
+
if dispatch_err and task.id not in out.failed:
|
|
801
|
+
# Worker side blew up after engine already verified — record
|
|
802
|
+
# the warning in last_error but don't override state.
|
|
803
|
+
out.failed.append(task.id)
|
|
804
|
+
|
|
805
|
+
if sleep_between_s > 0:
|
|
806
|
+
_t.sleep(sleep_between_s)
|
|
807
|
+
|
|
808
|
+
out.iterations = iters
|
|
809
|
+
out.elapsed_s = round(_t.time() - started, 2)
|
|
810
|
+
out.final_status = status(slug, omega_home=omega_home)
|
|
811
|
+
return out
|
|
812
|
+
|
|
813
|
+
|
|
814
|
+
def next_eligible(
|
|
815
|
+
slug: str,
|
|
816
|
+
*,
|
|
817
|
+
omega_home: str | Path | None = None,
|
|
818
|
+
) -> PlanTask | None:
|
|
819
|
+
"""Return the single next task the agent should work on.
|
|
820
|
+
|
|
821
|
+
Eligibility rule: state=PENDING AND every dep is VERIFIED.
|
|
822
|
+
Ordering: lowest wave first, then lowest id (lexicographic).
|
|
823
|
+
|
|
824
|
+
The worker MUST be handed ONLY this object — never the full task
|
|
825
|
+
list. That's how we close the visibility leak from the v6 planner.
|
|
826
|
+
"""
|
|
827
|
+
with _lock_for(slug):
|
|
828
|
+
conn = _connect(slug, omega_home)
|
|
829
|
+
try:
|
|
830
|
+
rows = conn.execute(
|
|
831
|
+
"SELECT * FROM tasks WHERE slug = ? AND state = ? "
|
|
832
|
+
"ORDER BY wave, id", (slug, PENDING),
|
|
833
|
+
).fetchall()
|
|
834
|
+
finally:
|
|
835
|
+
conn.close()
|
|
836
|
+
for row in rows:
|
|
837
|
+
task = PlanTask.from_row(row)
|
|
838
|
+
with _lock_for(slug):
|
|
839
|
+
conn = _connect(slug, omega_home)
|
|
840
|
+
try:
|
|
841
|
+
ok, _ = _are_deps_verified(conn, slug, task.depends_on)
|
|
842
|
+
finally:
|
|
843
|
+
conn.close()
|
|
844
|
+
if ok:
|
|
845
|
+
return task
|
|
846
|
+
return None
|