apsimo 1.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- apsimo/__init__.py +38 -0
- apsimo/__main__.py +6 -0
- apsimo/agent/__init__.py +6 -0
- apsimo/agent/client.py +276 -0
- apsimo/agent/models.py +46 -0
- apsimo/agents/__init__.py +20 -0
- apsimo/agents/models.py +264 -0
- apsimo/agents/store.py +861 -0
- apsimo/agents/websocket.py +522 -0
- apsimo/api/__init__.py +1 -0
- apsimo/api/auth_telemetry.py +287 -0
- apsimo/api/authority.py +1203 -0
- apsimo/api/contact_grants.py +347 -0
- apsimo/api/middleware.py +483 -0
- apsimo/api/routers/__init__.py +1 -0
- apsimo/api/routers/commitment_work.py +265 -0
- apsimo/api/routers/context_gate.py +123 -0
- apsimo/api/routers/executions.py +140 -0
- apsimo/api/routers/followup_plans.py +147 -0
- apsimo/api/routers/governed_actions.py +162 -0
- apsimo/api/routers/host.py +14473 -0
- apsimo/api/routers/initiative_work.py +115 -0
- apsimo/api/routers/mining.py +104 -0
- apsimo/api/routers/observations.py +110 -0
- apsimo/api/routers/social_state.py +225 -0
- apsimo/api/routers/task_queue.py +2715 -0
- apsimo/api/routers/temporal_followups.py +251 -0
- apsimo/api/routers/transport.py +110 -0
- apsimo/api/routers/transport_ingress_api.py +240 -0
- apsimo/api/schemas/__init__.py +1 -0
- apsimo/api/schemas/host.py +1949 -0
- apsimo/autonomy/cli.py +110 -0
- apsimo/autonomy/condition_worker.py +437 -0
- apsimo/autonomy/config.py +424 -0
- apsimo/autonomy/loop.py +4316 -0
- apsimo/autonomy/registry.py +339 -0
- apsimo/autonomy/scheduler.py +1822 -0
- apsimo/autonomy/synthesis.py +449 -0
- apsimo/backup.py +962 -0
- apsimo/beliefs/__init__.py +23 -0
- apsimo/beliefs/contradictions.py +109 -0
- apsimo/beliefs/decay.py +61 -0
- apsimo/beliefs/engine.py +479 -0
- apsimo/beliefs/models.py +67 -0
- apsimo/beliefs/promotion.py +41 -0
- apsimo/beliefs/resolve.py +58 -0
- apsimo/beliefs/source_claims.py +690 -0
- apsimo/beliefs/source_projection.py +883 -0
- apsimo/beliefs/source_time.py +208 -0
- apsimo/beliefs/store.py +133 -0
- apsimo/briefings/aggregators.py +824 -0
- apsimo/briefings/composer.py +420 -0
- apsimo/briefings/config.py +55 -0
- apsimo/briefings/delivery.py +439 -0
- apsimo/briefings/engagement.py +97 -0
- apsimo/briefings/engine.py +274 -0
- apsimo/briefings/enhancer.py +99 -0
- apsimo/briefings/models.py +183 -0
- apsimo/briefings/scheduler.py +382 -0
- apsimo/briefings/store.py +435 -0
- apsimo/chain/__init__.py +48 -0
- apsimo/chain/block.py +100 -0
- apsimo/chain/cli.py +704 -0
- apsimo/chain/genesis.py +443 -0
- apsimo/chain/identity.py +416 -0
- apsimo/chain/keys.py +1025 -0
- apsimo/chain/local_keys.py +187 -0
- apsimo/chain/manager.py +290 -0
- apsimo/chain/node.py +163 -0
- apsimo/chain/plugin_transactions.py +371 -0
- apsimo/chain/protocol.py +220 -0
- apsimo/chain/state_machine.py +676 -0
- apsimo/chain/storage.py +503 -0
- apsimo/chain/transactions.py +250 -0
- apsimo/chain/validation.py +397 -0
- apsimo/channels/__init__.py +1 -0
- apsimo/channels/manifest.py +31 -0
- apsimo/channels/migrations/001_channels_schema.sql +12 -0
- apsimo/channels/phone_gateways.py +42 -0
- apsimo/channels/presence.py +188 -0
- apsimo/channels/router.py +235 -0
- apsimo/channels/store.py +231 -0
- apsimo/cli.py +2688 -0
- apsimo/cognition/__init__.py +11 -0
- apsimo/cognition/charter.py +398 -0
- apsimo/cognition/drive_governance.py +3530 -0
- apsimo/cognition/evidence_pipeline.py +1627 -0
- apsimo/cognition/external_events.py +932 -0
- apsimo/cognition/goal_spine.py +3488 -0
- apsimo/cognition/introspection.py +214 -0
- apsimo/cognition/prompt.py +150 -0
- apsimo/cognition/runtime.py +108 -0
- apsimo/cognition/trigger.py +154 -0
- apsimo/commitments/__init__.py +18 -0
- apsimo/commitments/local_work.py +355 -0
- apsimo/commitments/store.py +1052 -0
- apsimo/commitments/work.py +91 -0
- apsimo/compat.py +53 -0
- apsimo/compression/__init__.py +467 -0
- apsimo/connectors/__init__.py +21 -0
- apsimo/connectors/base.py +152 -0
- apsimo/connectors/caldav_calendar.py +125 -0
- apsimo/connectors/fs_documents.py +85 -0
- apsimo/connectors/imap_email.py +138 -0
- apsimo/connectors/manager.py +218 -0
- apsimo/connectors/webhook_pull.py +88 -0
- apsimo/contacts/__init__.py +33 -0
- apsimo/contacts/comms.py +357 -0
- apsimo/contacts/config.py +79 -0
- apsimo/contacts/exporters/__init__.py +1 -0
- apsimo/contacts/exporters/vcard.py +71 -0
- apsimo/contacts/identity_links.py +251 -0
- apsimo/contacts/importer.py +280 -0
- apsimo/contacts/importers/__init__.py +1 -0
- apsimo/contacts/importers/batch.py +43 -0
- apsimo/contacts/importers/macos_contacts.py +101 -0
- apsimo/contacts/migrations/001_contacts_schema.sql +141 -0
- apsimo/contacts/migrations/002_trust_scopes.sql +36 -0
- apsimo/contacts/migrations/003_open_gateway_enum.sql +32 -0
- apsimo/contacts/migrations/004_contact_provision_operations.sql +18 -0
- apsimo/contacts/migrations/005_identity_links.sql +27 -0
- apsimo/contacts/models.py +308 -0
- apsimo/contacts/scoring.py +16 -0
- apsimo/contacts/store.py +1623 -0
- apsimo/contacts/transport_ingress.py +252 -0
- apsimo/contacts/world_bridge.py +314 -0
- apsimo/contextgate/__init__.py +69 -0
- apsimo/contextgate/chunker.py +169 -0
- apsimo/contextgate/estimate.py +54 -0
- apsimo/contextgate/gate.py +313 -0
- apsimo/contextgate/retrieve.py +115 -0
- apsimo/delivery/__init__.py +16 -0
- apsimo/delivery/bridge.py +1260 -0
- apsimo/delivery/channels.py +526 -0
- apsimo/delivery/classification.py +50 -0
- apsimo/delivery/rate_limiter.py +268 -0
- apsimo/delivery/reachout_policy.py +206 -0
- apsimo/directed/__init__.py +22 -0
- apsimo/directed/audit.py +167 -0
- apsimo/directed/intake.py +95 -0
- apsimo/directed/models.py +191 -0
- apsimo/directed/service.py +509 -0
- apsimo/directives/__init__.py +25 -0
- apsimo/directives/evidence.py +87 -0
- apsimo/directives/extractor.py +188 -0
- apsimo/directives/guard.py +364 -0
- apsimo/directives/models.py +206 -0
- apsimo/directives/service.py +372 -0
- apsimo/directives/store.py +167 -0
- apsimo/doctor.py +2173 -0
- apsimo/environment.py +43 -0
- apsimo/events/__init__.py +33 -0
- apsimo/events/broadcaster.py +98 -0
- apsimo/events/bus.py +217 -0
- apsimo/events/journal.py +863 -0
- apsimo/events/stream.py +131 -0
- apsimo/events/types.py +150 -0
- apsimo/execution_results.py +357 -0
- apsimo/feedback/__init__.py +5 -0
- apsimo/feedback/store.py +76 -0
- apsimo/feeds/__init__.py +19 -0
- apsimo/feeds/cli.py +84 -0
- apsimo/feeds/engine.py +437 -0
- apsimo/feeds/example-feed.yaml +77 -0
- apsimo/feeds/hermes_cron.py +126 -0
- apsimo/feeds/manager.py +235 -0
- apsimo/feeds/spec.py +250 -0
- apsimo/feeds/template.py +202 -0
- apsimo/gate/__init__.py +18 -0
- apsimo/gate/audit.py +61 -0
- apsimo/gate/communication_policy.py +166 -0
- apsimo/gate/config.py +72 -0
- apsimo/gate/context_provenance.py +170 -0
- apsimo/gate/env_risk.py +226 -0
- apsimo/gate/guard_audit.py +353 -0
- apsimo/gate/layers/__init__.py +1 -0
- apsimo/gate/layers/base.py +15 -0
- apsimo/gate/layers/l1_recipient.py +66 -0
- apsimo/gate/layers/l2_pii.py +134 -0
- apsimo/gate/layers/l3_cross_context.py +50 -0
- apsimo/gate/layers/l4_trust_tier.py +78 -0
- apsimo/gate/layers/l5_injection.py +199 -0
- apsimo/gate/layers/l6_review.py +86 -0
- apsimo/gate/layers/l7_delay.py +100 -0
- apsimo/gate/layers/tom2_epistemic.py +185 -0
- apsimo/gate/models.py +64 -0
- apsimo/gate/pending_dispatch.py +5 -0
- apsimo/gate/pipeline.py +206 -0
- apsimo/gate/rejection.py +259 -0
- apsimo/gate/response_guard.py +700 -0
- apsimo/gate/rulesets/injection_v1.yaml +51 -0
- apsimo/gate/surface_policy.py +189 -0
- apsimo/gate/taint.py +226 -0
- apsimo/genesis.json +9 -0
- apsimo/goals/__init__.py +100 -0
- apsimo/goals/config.py +38 -0
- apsimo/goals/decomposer.py +421 -0
- apsimo/goals/engine.py +617 -0
- apsimo/goals/inference.py +354 -0
- apsimo/goals/models.py +302 -0
- apsimo/goals/priority.py +270 -0
- apsimo/goals/queue_bridge.py +149 -0
- apsimo/goals/replan.py +450 -0
- apsimo/goals/schema.sql +89 -0
- apsimo/goals/store.py +692 -0
- apsimo/governed_actions.py +1708 -0
- apsimo/harness_integration/__init__.py +45 -0
- apsimo/harness_integration/context.py +41 -0
- apsimo/harness_integration/skills.py +231 -0
- apsimo/identity/__init__.py +26 -0
- apsimo/identity/participants.py +181 -0
- apsimo/identity/resolver.py +329 -0
- apsimo/identity_bootstrap/__init__.py +5 -0
- apsimo/identity_bootstrap/builder.py +208 -0
- apsimo/identity_bootstrap/corpus.py +443 -0
- apsimo/identity_bootstrap/models.py +54 -0
- apsimo/identity_bootstrap/runner.py +353 -0
- apsimo/identity_bootstrap/seeders/__init__.py +25 -0
- apsimo/identity_bootstrap/seeders/briefings.py +109 -0
- apsimo/identity_bootstrap/seeders/chain.py +57 -0
- apsimo/identity_bootstrap/seeders/goals.py +128 -0
- apsimo/identity_bootstrap/seeders/memory.py +191 -0
- apsimo/identity_bootstrap/seeders/neo4j_cognition.py +79 -0
- apsimo/identity_bootstrap/seeders/relationship.py +152 -0
- apsimo/identity_bootstrap/seeders/sessions.py +67 -0
- apsimo/identity_bootstrap/seeders/skills.py +92 -0
- apsimo/identity_bootstrap/seeders/task_queue.py +72 -0
- apsimo/identity_bootstrap/seeders/world_model.py +143 -0
- apsimo/identity_bootstrap/self_query.py +92 -0
- apsimo/identity_bootstrap/self_reflection.py +155 -0
- apsimo/identity_bootstrap/skill.py +37 -0
- apsimo/identity_bootstrap/verifier.py +436 -0
- apsimo/initiatives/__init__.py +20 -0
- apsimo/initiatives/action_registry.py +454 -0
- apsimo/initiatives/approval_authority.py +2105 -0
- apsimo/initiatives/approval_policy.py +123 -0
- apsimo/initiatives/assignment.py +263 -0
- apsimo/initiatives/backup_evidence.py +100 -0
- apsimo/initiatives/context_freshness.py +103 -0
- apsimo/initiatives/models.py +318 -0
- apsimo/initiatives/native_work.py +270 -0
- apsimo/initiatives/standing_approvals.py +232 -0
- apsimo/initiatives/store.py +1081 -0
- apsimo/initiatives/temporal_followup.py +410 -0
- apsimo/intelligence/__init__.py +1 -0
- apsimo/intelligence/cognition/__init__.py +24 -0
- apsimo/intelligence/cognition/gap_detector.py +148 -0
- apsimo/intelligence/cognition/metalearner.py +547 -0
- apsimo/intelligence/cognition/metrics_collector.py +217 -0
- apsimo/intelligence/cognition/performance_index.py +299 -0
- apsimo/intelligence/cognition/registry.py +192 -0
- apsimo/intelligence/cognition/strategy_adjuster.py +222 -0
- apsimo/intelligence/cognition/types.py +16 -0
- apsimo/intelligence/components/__init__.py +66 -0
- apsimo/intelligence/components/anomaly_detector.py +413 -0
- apsimo/intelligence/components/initiative_engine.py +2643 -0
- apsimo/intelligence/components/preference_learner.py +521 -0
- apsimo/intelligence/components/research_orchestrator.py +358 -0
- apsimo/intelligence/components/self_directed_thinker.py +221 -0
- apsimo/intelligence/components/self_reflector.py +252 -0
- apsimo/intelligence/components/session_continuity.py +154 -0
- apsimo/intelligence/components/task_planner.py +320 -0
- apsimo/intelligence/components/tool_learner.py +217 -0
- apsimo/intelligence/graph/__init__.py +79 -0
- apsimo/intelligence/graph/client.py +2483 -0
- apsimo/intelligence/graph/consolidator.py +405 -0
- apsimo/intelligence/graph/distiller.py +312 -0
- apsimo/intelligence/graph/migrations.py +129 -0
- apsimo/intelligence/graph/queries.py +248 -0
- apsimo/intelligence/graph/recall.py +281 -0
- apsimo/intelligence/graph/reconciler.py +144 -0
- apsimo/intelligence/graph/schema.py +337 -0
- apsimo/intelligence/graph/selection.py +252 -0
- apsimo/intelligence/learning/__init__.py +17 -0
- apsimo/intelligence/learning/continuous_learner.py +245 -0
- apsimo/intelligence/learning/feedback_store.py +321 -0
- apsimo/intelligence/mind_model/__init__.py +1 -0
- apsimo/intelligence/mind_model/graph_baseline.py +136 -0
- apsimo/intelligence/mind_model/signal_collector.py +361 -0
- apsimo/intelligence/relationships/__init__.py +11 -0
- apsimo/intelligence/relationships/profiler.py +389 -0
- apsimo/intelligence/relationships/scorer.py +560 -0
- apsimo/intelligence/relationships/signal_floor.py +66 -0
- apsimo/intelligence/relationships/trust_tiers.py +300 -0
- apsimo/intelligence/synthesis/__init__.py +40 -0
- apsimo/intelligence/synthesis/connection_discoverer.py +379 -0
- apsimo/intelligence/synthesis/cross_domain_analyzer.py +287 -0
- apsimo/intelligence/synthesis/insight_deliverer.py +171 -0
- apsimo/intelligence/synthesis/insight_store.py +79 -0
- apsimo/intelligence/synthesis/insight_validator.py +183 -0
- apsimo/intelligence/synthesis/novelty_scorer.py +267 -0
- apsimo/intelligence/turn_middleware/__init__.py +15 -0
- apsimo/intelligence/turn_middleware/memory_sync.py +119 -0
- apsimo/mcp/__init__.py +41 -0
- apsimo/mcp/__main__.py +6 -0
- apsimo/mcp/config.py +287 -0
- apsimo/mcp/server.py +501 -0
- apsimo/migrations.py +187 -0
- apsimo/mining/__init__.py +27 -0
- apsimo/mining/corpus.py +239 -0
- apsimo/mining/escalations.py +289 -0
- apsimo/mining/models.py +169 -0
- apsimo/mining/store.py +210 -0
- apsimo/models/__init__.py +30 -0
- apsimo/models/memory.py +80 -0
- apsimo/models/mesh.py +72 -0
- apsimo/models/person.py +104 -0
- apsimo/models/signal.py +108 -0
- apsimo/observations/__init__.py +15 -0
- apsimo/observations/store.py +277 -0
- apsimo/patterns/__init__.py +6 -0
- apsimo/patterns/extract.py +187 -0
- apsimo/patterns/store.py +227 -0
- apsimo/persona/__init__.py +1 -0
- apsimo/persona/engine.py +611 -0
- apsimo/persona/manifest.py +140 -0
- apsimo/projects/__init__.py +28 -0
- apsimo/projects/engine.py +1681 -0
- apsimo/projects/event_outbox.py +188 -0
- apsimo/projects/models.py +216 -0
- apsimo/projects/planner.py +181 -0
- apsimo/projects/store.py +1446 -0
- apsimo/proposals/__init__.py +12 -0
- apsimo/proposals/engine.py +114 -0
- apsimo/proposals/models.py +207 -0
- apsimo/qualification/__init__.py +1 -0
- apsimo/qualification/cases.py +75 -0
- apsimo/qualification/cli.py +51 -0
- apsimo/qualification/memory_cases.py +209 -0
- apsimo/qualification/records.py +92 -0
- apsimo/qualification/report.py +87 -0
- apsimo/qualification/runner.py +311 -0
- apsimo/qualification/structured_cases.py +131 -0
- apsimo/reasoning/__init__.py +13 -0
- apsimo/reasoning/executor.py +506 -0
- apsimo/reasoning/loop.py +373 -0
- apsimo/reasoning/native_tools/__init__.py +16 -0
- apsimo/reasoning/native_tools/calculate.py +141 -0
- apsimo/reasoning/native_tools/file_ops.py +150 -0
- apsimo/reasoning/native_tools/web_search.py +49 -0
- apsimo/reasoning/tool_policy.py +182 -0
- apsimo/redact/__init__.py +176 -0
- apsimo/repos/__init__.py +5 -0
- apsimo/repos/mirrors.py +204 -0
- apsimo/research/__init__.py +41 -0
- apsimo/research/artifact.py +482 -0
- apsimo/research/gatherer.py +387 -0
- apsimo/research/pipeline.py +513 -0
- apsimo/research/search/__init__.py +7 -0
- apsimo/research/search/base.py +41 -0
- apsimo/research/search/brave.py +59 -0
- apsimo/research/search/cache.py +51 -0
- apsimo/research/search/duckduckgo.py +103 -0
- apsimo/research/search/orchestrator.py +119 -0
- apsimo/research/search/serpapi.py +59 -0
- apsimo/research/search/tavily.py +59 -0
- apsimo/research/synthesizer.py +309 -0
- apsimo/router/__init__.py +30 -0
- apsimo/router/complexity_scorer.py +148 -0
- apsimo/router/endpoints.py +153 -0
- apsimo/router/fallback.py +58 -0
- apsimo/router/functions.py +243 -0
- apsimo/router/native_policy.py +52 -0
- apsimo/router/router.py +762 -0
- apsimo/router/self_learning.py +174 -0
- apsimo/router/tiers.py +677 -0
- apsimo/sandbox/__init__.py +21 -0
- apsimo/sandbox/backend.py +195 -0
- apsimo/sandbox/manager.py +173 -0
- apsimo/scope_bounds.py +7 -0
- apsimo/secrets/__init__.py +6 -0
- apsimo/secrets/backends/__init__.py +8 -0
- apsimo/secrets/backends/base.py +42 -0
- apsimo/secrets/backends/env.py +110 -0
- apsimo/secrets/backends/keyring.py +72 -0
- apsimo/secrets/backends/onepassword.py +232 -0
- apsimo/secrets/cli.py +191 -0
- apsimo/secrets/manager.py +160 -0
- apsimo/secrets/migration.py +101 -0
- apsimo/secrets/types.py +98 -0
- apsimo/seed.py +41 -0
- apsimo/self_model/__init__.py +37 -0
- apsimo/self_model/appraisals.py +673 -0
- apsimo/self_model/benchmark.py +1314 -0
- apsimo/self_model/brief.py +40 -0
- apsimo/self_model/event_concerns.py +1128 -0
- apsimo/self_model/execution_forecasts.py +353 -0
- apsimo/self_model/expectations.py +1595 -0
- apsimo/self_model/experiments.py +1150 -0
- apsimo/self_model/journal.py +148 -0
- apsimo/self_model/judgments.py +705 -0
- apsimo/self_model/native_outcomes.py +55 -0
- apsimo/self_model/params.py +220 -0
- apsimo/self_model/perspective.py +246 -0
- apsimo/self_model/reconcile.py +183 -0
- apsimo/self_model/reply_forecasts.py +381 -0
- apsimo/self_model/runtime_forecasts.py +296 -0
- apsimo/self_model/runtime_models.py +67 -0
- apsimo/self_model/settlement.py +207 -0
- apsimo/self_model/situation.py +1731 -0
- apsimo/self_model/store.py +883 -0
- apsimo/self_model/supervised.py +137 -0
- apsimo/self_model/thinker.py +99 -0
- apsimo/self_model/trust.py +388 -0
- apsimo/self_model/workspace.py +2388 -0
- apsimo/server.py +4197 -0
- apsimo/services/__init__.py +1 -0
- apsimo/services/agent_bridge.py +474 -0
- apsimo/services/initiative_executor.py +914 -0
- apsimo/services/instance.py +297 -0
- apsimo/sessions/__init__.py +22 -0
- apsimo/sessions/config.py +13 -0
- apsimo/sessions/context_loader.py +88 -0
- apsimo/sessions/federation_session.py +75 -0
- apsimo/sessions/isolated_session.py +98 -0
- apsimo/sessions/reports.py +84 -0
- apsimo/sessions/store.py +148 -0
- apsimo/setup.py +2818 -0
- apsimo/setup_hermes.py +879 -0
- apsimo/setup_local_work.py +218 -0
- apsimo/setup_native_goals.py +134 -0
- apsimo/setup_native_reviews.py +115 -0
- apsimo/skills/__init__.py +10 -0
- apsimo/skills/base.py +108 -0
- apsimo/skills/budget.py +28 -0
- apsimo/skills/executor.py +493 -0
- apsimo/skills/executors/__init__.py +1 -0
- apsimo/skills/executors/behavioral_correction.py +75 -0
- apsimo/skills/executors/capability_gap.py +38 -0
- apsimo/skills/executors/data_quality.py +163 -0
- apsimo/skills/executors/knowledge_acquisition.py +41 -0
- apsimo/skills/executors/operational_hygiene.py +185 -0
- apsimo/skills/executors/subsystem_health.py +169 -0
- apsimo/skills/hermes_export.py +431 -0
- apsimo/skills/index.py +123 -0
- apsimo/skills/learning/__init__.py +21 -0
- apsimo/skills/learning/novelty_detector.py +206 -0
- apsimo/skills/learning/pattern_extractor.py +199 -0
- apsimo/skills/learning/triggers.py +159 -0
- apsimo/skills/loader.py +246 -0
- apsimo/skills/migrations/002_progressive_loading.sql +6 -0
- apsimo/skills/migrations/backfill_triggers.py +20 -0
- apsimo/skills/models.py +202 -0
- apsimo/skills/packager.py +128 -0
- apsimo/skills/protocols.py +70 -0
- apsimo/skills/registry.py +191 -0
- apsimo/skills/runtime.py +58 -0
- apsimo/skills/sandbox_runner.py +229 -0
- apsimo/skills/scheduler.py +129 -0
- apsimo/skills/schema.py +79 -0
- apsimo/skills/security/__init__.py +12 -0
- apsimo/skills/security/guards.py +53 -0
- apsimo/skills/security/scanner.py +223 -0
- apsimo/skills_memory/__init__.py +26 -0
- apsimo/skills_memory/distill.py +159 -0
- apsimo/skills_memory/models.py +85 -0
- apsimo/skills_memory/retrieve.py +62 -0
- apsimo/skills_memory/store.py +172 -0
- apsimo/surprise/__init__.py +6 -0
- apsimo/surprise/accumulation.py +57 -0
- apsimo/surprise/scorer.py +102 -0
- apsimo/surprise/store.py +203 -0
- apsimo/task_queue/__init__.py +69 -0
- apsimo/task_queue/action_receipts.py +148 -0
- apsimo/task_queue/approval_relay_canary.py +108 -0
- apsimo/task_queue/config.py +85 -0
- apsimo/task_queue/contract.py +361 -0
- apsimo/task_queue/events.py +130 -0
- apsimo/task_queue/governor.py +1031 -0
- apsimo/task_queue/handlers/__init__.py +16 -0
- apsimo/task_queue/handlers/base.py +37 -0
- apsimo/task_queue/handlers/inference.py +640 -0
- apsimo/task_queue/handlers/monitoring.py +116 -0
- apsimo/task_queue/handlers/registry.py +75 -0
- apsimo/task_queue/handlers/subtask_handler.py +173 -0
- apsimo/task_queue/handlers/system_maintenance.py +147 -0
- apsimo/task_queue/mesh_integration.py +111 -0
- apsimo/task_queue/models.py +317 -0
- apsimo/task_queue/queue_manager.py +8286 -0
- apsimo/task_queue/routing.py +287 -0
- apsimo/task_queue/scheduler.py +252 -0
- apsimo/task_queue/schema.sql +197 -0
- apsimo/task_queue/work_control.py +342 -0
- apsimo/task_queue/worker.py +993 -0
- apsimo/telemetry.py +145 -0
- apsimo/tom/__init__.py +6 -0
- apsimo/tom/affect.py +387 -0
- apsimo/tom/approvals.py +171 -0
- apsimo/tom/arcs.py +896 -0
- apsimo/tom/asymmetry.py +131 -0
- apsimo/tom/eligibility.py +248 -0
- apsimo/tom/engagement.py +214 -0
- apsimo/tom/exposure.py +214 -0
- apsimo/tom/extractor.py +306 -0
- apsimo/tom/fact_adapters.py +144 -0
- apsimo/tom/facts.py +326 -0
- apsimo/tom/integration.py +592 -0
- apsimo/tom/leveled.py +118 -0
- apsimo/tom/levels.py +247 -0
- apsimo/tom/recipient_audit.py +995 -0
- apsimo/tom/recipient_simulator.py +593 -0
- apsimo/tom/source_lineage.py +93 -0
- apsimo/tom/tom2.py +277 -0
- apsimo/tom/visibility.py +559 -0
- apsimo/tom/visibility_store.py +414 -0
- apsimo/tools/__init__.py +0 -0
- apsimo/tools/definitions.py +740 -0
- apsimo/tools/handlers.py +943 -0
- apsimo/toolsmith/__init__.py +26 -0
- apsimo/toolsmith/authority.py +166 -0
- apsimo/toolsmith/engine.py +559 -0
- apsimo/toolsmith/integrity.py +100 -0
- apsimo/toolsmith/miner.py +145 -0
- apsimo/toolsmith/policy.py +110 -0
- apsimo/toolsmith/registry.py +635 -0
- apsimo/turns/__init__.py +17 -0
- apsimo/turns/audio.py +134 -0
- apsimo/turns/documents.py +235 -0
- apsimo/turns/executions.py +486 -0
- apsimo/turns/hermes_history.py +245 -0
- apsimo/turns/hermes_kanban.py +268 -0
- apsimo/turns/hermes_work.py +96 -0
- apsimo/turns/idempotency.py +752 -0
- apsimo/turns/local_work.py +115 -0
- apsimo/turns/media.py +581 -0
- apsimo/turns/reported_workers.py +196 -0
- apsimo/turns/source_annotations.py +283 -0
- apsimo/turns/source_attribution.py +154 -0
- apsimo/turns/source_read.py +351 -0
- apsimo/turns/source_vectors.py +263 -0
- apsimo/turns/video.py +210 -0
- apsimo/util/autonomy_preset.py +220 -0
- apsimo/util/instance.py +92 -0
- apsimo/util/model_output.py +25 -0
- apsimo/util/quiet_hours.py +27 -0
- apsimo/util/session_safety.py +37 -0
- apsimo/util/temporal.py +343 -0
- apsimo/vector/__init__.py +75 -0
- apsimo/vector/backfill.py +171 -0
- apsimo/vector/caption.py +114 -0
- apsimo/vector/collections.py +51 -0
- apsimo/vector/config.py +102 -0
- apsimo/vector/embedder.py +670 -0
- apsimo/vector/image_preprocess.py +406 -0
- apsimo/vector/image_store.py +296 -0
- apsimo/vector/indexes.py +162 -0
- apsimo/vector/migrate.py +334 -0
- apsimo/vector/multimodal_provider.py +417 -0
- apsimo/vector/multimodal_types.py +87 -0
- apsimo/vector/openai_provider.py +119 -0
- apsimo/vector/query.py +49 -0
- apsimo/vector/reranker.py +565 -0
- apsimo/vector/safety_image.py +159 -0
- apsimo/vector/scanner.py +197 -0
- apsimo/vector/setup.py +289 -0
- apsimo/vector/store.py +533 -0
- apsimo/vector/tiers.py +263 -0
- apsimo/work_orders.py +925 -0
- apsimo/workers/__init__.py +21 -0
- apsimo/workers/agent_bridge.py +640 -0
- apsimo/workers/colony_worker.py +382 -0
- apsimo/workers/queue_worker.py +441 -0
- apsimo/workers/skills_sync.py +152 -0
- apsimo/world_model/__init__.py +71 -0
- apsimo/world_model/causal_maintenance.py +131 -0
- apsimo/world_model/causal_policy.py +43 -0
- apsimo/world_model/causal_query.py +125 -0
- apsimo/world_model/confidence.py +54 -0
- apsimo/world_model/config.py +64 -0
- apsimo/world_model/constants.py +97 -0
- apsimo/world_model/entities.py +145 -0
- apsimo/world_model/expectation_resolvers.py +177 -0
- apsimo/world_model/extraction/__init__.py +7 -0
- apsimo/world_model/extraction/base.py +62 -0
- apsimo/world_model/extraction/conversation_extractor.py +262 -0
- apsimo/world_model/extraction/detector.py +74 -0
- apsimo/world_model/extraction/document_extractor.py +78 -0
- apsimo/world_model/extraction/formats/__init__.py +24 -0
- apsimo/world_model/extraction/formats/csv_fmt.py +68 -0
- apsimo/world_model/extraction/formats/html_fmt.py +72 -0
- apsimo/world_model/extraction/formats/json_fmt.py +68 -0
- apsimo/world_model/extraction/formats/pdf.py +43 -0
- apsimo/world_model/extraction/formats/text.py +27 -0
- apsimo/world_model/extraction/llm_extractor.py +164 -0
- apsimo/world_model/extraction/pipeline.py +73 -0
- apsimo/world_model/integrations/__init__.py +5 -0
- apsimo/world_model/integrations/mind_model_bridge.py +115 -0
- apsimo/world_model/integrations/social_intel_bridge.py +120 -0
- apsimo/world_model/jobs/__init__.py +4 -0
- apsimo/world_model/jobs/extraction_job.py +168 -0
- apsimo/world_model/llm_extract.py +572 -0
- apsimo/world_model/neo4j/__init__.py +5 -0
- apsimo/world_model/neo4j/backend.py +654 -0
- apsimo/world_model/observations.py +155 -0
- apsimo/world_model/populator.py +307 -0
- apsimo/world_model/postgres/__init__.py +1 -0
- apsimo/world_model/postgres/backend.py +683 -0
- apsimo/world_model/relationships.py +25 -0
- apsimo/world_model/resolution/__init__.py +13 -0
- apsimo/world_model/resolution/entity_resolver.py +232 -0
- apsimo/world_model/resolution/merge_audit.py +16 -0
- apsimo/world_model/resolution/merge_workflow.py +117 -0
- apsimo/world_model/source_reports.py +121 -0
- apsimo/world_model/sqlite/__init__.py +4 -0
- apsimo/world_model/sqlite/backend.py +855 -0
- apsimo/world_model/sqlite/schema.sql +132 -0
- apsimo/world_model/store.py +545 -0
- apsimo-1.3.0.dist-info/METADATA +78 -0
- apsimo-1.3.0.dist-info/RECORD +614 -0
- apsimo-1.3.0.dist-info/WHEEL +5 -0
- apsimo-1.3.0.dist-info/entry_points.txt +11 -0
- apsimo-1.3.0.dist-info/licenses/LICENSE +21 -0
- apsimo-1.3.0.dist-info/top_level.txt +2 -0
- colony_sidecar/__init__.py +4 -0
apsimo/goals/replan.py
ADDED
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
"""ReplanEngine — adaptive replanning with failure strategies.
|
|
2
|
+
|
|
3
|
+
Classifies failures and selects the appropriate recovery strategy:
|
|
4
|
+
TRANSIENT → RETRY (up to max_retries)
|
|
5
|
+
CAPABILITY → SUBSTITUTE or ESCALATE
|
|
6
|
+
DEPENDENCY → PARTIAL_REPLAN
|
|
7
|
+
SCOPE_CHANGE → FULL_REPLAN (user confirmation if replan_count >= 2)
|
|
8
|
+
EXTERNAL → RETRY with backoff, then SUBSTITUTE
|
|
9
|
+
UNRECOVERABLE → ESCALATE
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from enum import Enum
|
|
17
|
+
from typing import Any, Dict, List, Optional
|
|
18
|
+
|
|
19
|
+
from apsimo.goals.models import (
|
|
20
|
+
Goal,
|
|
21
|
+
GoalDAG,
|
|
22
|
+
GoalPriority,
|
|
23
|
+
GoalStatus,
|
|
24
|
+
Subtask,
|
|
25
|
+
SubtaskStatus,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
MAX_REPLANS_DEFAULT = 5
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class FailureClass(str, Enum):
|
|
34
|
+
"""Classification of subtask failure for replan strategy selection."""
|
|
35
|
+
TRANSIENT = "transient" # Temporary failure; retry expected to work
|
|
36
|
+
CAPABILITY = "capability" # Required capability not available
|
|
37
|
+
DEPENDENCY = "dependency" # Upstream subtask produced bad output
|
|
38
|
+
SCOPE_CHANGE = "scope_change" # Goal requirements changed mid-execution
|
|
39
|
+
EXTERNAL = "external" # External service, API, or resource failure
|
|
40
|
+
UNRECOVERABLE = "unrecoverable" # No recovery path exists
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ReplanStrategy(str, Enum):
|
|
44
|
+
"""Available strategies for recovering from subtask failures."""
|
|
45
|
+
RETRY = "retry" # Re-queue the same subtask
|
|
46
|
+
SUBSTITUTE = "substitute" # Replace with an alternative subtask
|
|
47
|
+
SKIP = "skip" # Mark subtask as SKIPPED; continue
|
|
48
|
+
PARTIAL_REPLAN = "partial_replan" # Rebuild the failed subtask's subtree
|
|
49
|
+
FULL_REPLAN = "full_replan" # Rebuild the entire DAG
|
|
50
|
+
ESCALATE = "escalate" # Surface to user for manual intervention
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class FailureAnalysis:
|
|
55
|
+
"""Result of classifying a subtask failure."""
|
|
56
|
+
subtask_id: str
|
|
57
|
+
failure_class: FailureClass
|
|
58
|
+
error_message: str
|
|
59
|
+
retry_viable: bool
|
|
60
|
+
affected_subtasks: List[str] # Other subtasks that may be invalidated
|
|
61
|
+
suggested_strategy: str
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass
|
|
65
|
+
class ReplanResult:
|
|
66
|
+
"""Describes the proposed changes from a replan operation."""
|
|
67
|
+
goal_id: str
|
|
68
|
+
strategy: ReplanStrategy
|
|
69
|
+
requires_user_approval: bool
|
|
70
|
+
new_dag: Optional[GoalDAG] # Set for FULL_REPLAN / PARTIAL_REPLAN
|
|
71
|
+
retried_subtask_ids: List[str] = field(default_factory=list)
|
|
72
|
+
skipped_subtask_ids: List[str] = field(default_factory=list)
|
|
73
|
+
substituted_subtask_ids: List[str] = field(default_factory=list)
|
|
74
|
+
escalation_message: Optional[str] = None
|
|
75
|
+
dag_diff: Optional[str] = None # Human-readable diff description
|
|
76
|
+
estimated_completion_delta_hours: float = 0.0
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# ── Failure classification heuristics ─────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
_TRANSIENT_PATTERNS = [
|
|
82
|
+
"timeout", "timed out", "connection reset", "temporary",
|
|
83
|
+
"retry", "rate limit", "too many requests", "503", "504",
|
|
84
|
+
]
|
|
85
|
+
_CAPABILITY_PATTERNS = [
|
|
86
|
+
"capability", "no worker", "no node", "unsatisfied requirement",
|
|
87
|
+
"capability not available", "hardware not available",
|
|
88
|
+
]
|
|
89
|
+
_EXTERNAL_PATTERNS = [
|
|
90
|
+
"api error", "http error", "network error", "connection refused",
|
|
91
|
+
"dns", "certificate", "ssl", "404", "401", "403",
|
|
92
|
+
]
|
|
93
|
+
_UNRECOVERABLE_PATTERNS = [
|
|
94
|
+
"permission denied", "out of disk", "oom", "memory error",
|
|
95
|
+
"corrupt", "unrecoverable", "fatal",
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _classify_error(error_message: str) -> FailureClass:
|
|
100
|
+
"""Heuristically classify an error message."""
|
|
101
|
+
msg = (error_message or "").lower()
|
|
102
|
+
if any(p in msg for p in _UNRECOVERABLE_PATTERNS):
|
|
103
|
+
return FailureClass.UNRECOVERABLE
|
|
104
|
+
if any(p in msg for p in _CAPABILITY_PATTERNS):
|
|
105
|
+
return FailureClass.CAPABILITY
|
|
106
|
+
# Check EXTERNAL before TRANSIENT: explicit API/network failures are external
|
|
107
|
+
if any(p in msg for p in _EXTERNAL_PATTERNS):
|
|
108
|
+
return FailureClass.EXTERNAL
|
|
109
|
+
if any(p in msg for p in _TRANSIENT_PATTERNS):
|
|
110
|
+
return FailureClass.TRANSIENT
|
|
111
|
+
# Default to TRANSIENT for unknown errors (optimistic)
|
|
112
|
+
return FailureClass.TRANSIENT
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _downstream_subtasks(dag: GoalDAG, failed_id: str) -> List[str]:
|
|
116
|
+
"""Return all subtask IDs that transitively depend on failed_id."""
|
|
117
|
+
affected: List[str] = []
|
|
118
|
+
visited: set = set()
|
|
119
|
+
|
|
120
|
+
def dfs(node_id: str) -> None:
|
|
121
|
+
for sid, s in dag.subtasks.items():
|
|
122
|
+
if node_id in s.depends_on and sid not in visited:
|
|
123
|
+
visited.add(sid)
|
|
124
|
+
affected.append(sid)
|
|
125
|
+
dfs(sid)
|
|
126
|
+
|
|
127
|
+
dfs(failed_id)
|
|
128
|
+
return affected
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class ReplanEngine:
|
|
132
|
+
"""Generate recovery plans for goal execution failures."""
|
|
133
|
+
|
|
134
|
+
def __init__(self, max_replans: int = MAX_REPLANS_DEFAULT) -> None:
|
|
135
|
+
self.max_replans = max_replans
|
|
136
|
+
|
|
137
|
+
def analyze_failure(
|
|
138
|
+
self,
|
|
139
|
+
goal: Goal,
|
|
140
|
+
dag: GoalDAG,
|
|
141
|
+
failed_subtask: Subtask,
|
|
142
|
+
) -> FailureAnalysis:
|
|
143
|
+
"""Classify the failure and identify affected downstream subtasks."""
|
|
144
|
+
error = failed_subtask.error or ""
|
|
145
|
+
failure_class = _classify_error(error)
|
|
146
|
+
|
|
147
|
+
# Override: if max retries exhausted, push toward non-retry strategy
|
|
148
|
+
retry_viable = (
|
|
149
|
+
failure_class in {FailureClass.TRANSIENT, FailureClass.EXTERNAL}
|
|
150
|
+
and failed_subtask.retry_count < failed_subtask.max_retries
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
affected = _downstream_subtasks(dag, failed_subtask.subtask_id)
|
|
154
|
+
|
|
155
|
+
# Select suggested strategy
|
|
156
|
+
if failure_class == FailureClass.UNRECOVERABLE:
|
|
157
|
+
strategy = ReplanStrategy.ESCALATE.value
|
|
158
|
+
elif failure_class == FailureClass.CAPABILITY:
|
|
159
|
+
strategy = ReplanStrategy.SUBSTITUTE.value
|
|
160
|
+
elif failure_class == FailureClass.DEPENDENCY:
|
|
161
|
+
strategy = ReplanStrategy.PARTIAL_REPLAN.value
|
|
162
|
+
elif failure_class == FailureClass.SCOPE_CHANGE:
|
|
163
|
+
strategy = ReplanStrategy.FULL_REPLAN.value
|
|
164
|
+
elif retry_viable:
|
|
165
|
+
strategy = ReplanStrategy.RETRY.value
|
|
166
|
+
else:
|
|
167
|
+
strategy = ReplanStrategy.SUBSTITUTE.value
|
|
168
|
+
|
|
169
|
+
return FailureAnalysis(
|
|
170
|
+
subtask_id=failed_subtask.subtask_id,
|
|
171
|
+
failure_class=failure_class,
|
|
172
|
+
error_message=error,
|
|
173
|
+
retry_viable=retry_viable,
|
|
174
|
+
affected_subtasks=affected,
|
|
175
|
+
suggested_strategy=strategy,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
def generate_replan(
|
|
179
|
+
self,
|
|
180
|
+
goal: Goal,
|
|
181
|
+
dag: GoalDAG,
|
|
182
|
+
analysis: FailureAnalysis,
|
|
183
|
+
) -> ReplanResult:
|
|
184
|
+
"""Generate a recovery plan for the given failure.
|
|
185
|
+
|
|
186
|
+
Selection logic:
|
|
187
|
+
- TRANSIENT failures → RETRY (up to max_retries)
|
|
188
|
+
- CAPABILITY failures → SUBSTITUTE or ESCALATE
|
|
189
|
+
- DEPENDENCY failures → PARTIAL_REPLAN
|
|
190
|
+
- SCOPE_CHANGE → FULL_REPLAN (with user confirmation if replan_count >= 2)
|
|
191
|
+
- EXTERNAL failures → RETRY with backoff, then SUBSTITUTE
|
|
192
|
+
- UNRECOVERABLE → ESCALATE
|
|
193
|
+
|
|
194
|
+
Returns a ReplanResult describing the proposed changes.
|
|
195
|
+
"""
|
|
196
|
+
# Circuit breaker
|
|
197
|
+
if goal.replan_count >= self.max_replans:
|
|
198
|
+
return ReplanResult(
|
|
199
|
+
goal_id=goal.goal_id,
|
|
200
|
+
strategy=ReplanStrategy.ESCALATE,
|
|
201
|
+
requires_user_approval=True,
|
|
202
|
+
new_dag=None,
|
|
203
|
+
escalation_message=(
|
|
204
|
+
f"Goal has been replanned {goal.replan_count} times "
|
|
205
|
+
f"(max {self.max_replans}). Manual intervention required."
|
|
206
|
+
),
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
strategy = ReplanStrategy(analysis.suggested_strategy)
|
|
210
|
+
requires_approval = False
|
|
211
|
+
|
|
212
|
+
if strategy == ReplanStrategy.RETRY:
|
|
213
|
+
return self._plan_retry(goal, dag, analysis)
|
|
214
|
+
|
|
215
|
+
if strategy == ReplanStrategy.SUBSTITUTE:
|
|
216
|
+
return self._plan_substitute(goal, dag, analysis)
|
|
217
|
+
|
|
218
|
+
if strategy == ReplanStrategy.SKIP:
|
|
219
|
+
return self._plan_skip(goal, dag, analysis)
|
|
220
|
+
|
|
221
|
+
if strategy == ReplanStrategy.PARTIAL_REPLAN:
|
|
222
|
+
new_dag = self._partial_replan(goal, dag, analysis)
|
|
223
|
+
requires_approval = False
|
|
224
|
+
return ReplanResult(
|
|
225
|
+
goal_id=goal.goal_id,
|
|
226
|
+
strategy=strategy,
|
|
227
|
+
requires_user_approval=requires_approval,
|
|
228
|
+
new_dag=new_dag,
|
|
229
|
+
dag_diff=f"Rebuilt subtree rooted at {analysis.subtask_id}",
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
if strategy == ReplanStrategy.FULL_REPLAN:
|
|
233
|
+
# Require user approval after 2 replans
|
|
234
|
+
requires_approval = goal.replan_count >= 2
|
|
235
|
+
new_dag = self._full_replan(goal, dag)
|
|
236
|
+
return ReplanResult(
|
|
237
|
+
goal_id=goal.goal_id,
|
|
238
|
+
strategy=strategy,
|
|
239
|
+
requires_user_approval=requires_approval,
|
|
240
|
+
new_dag=new_dag,
|
|
241
|
+
dag_diff="Full DAG rebuild",
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
# ESCALATE
|
|
245
|
+
return ReplanResult(
|
|
246
|
+
goal_id=goal.goal_id,
|
|
247
|
+
strategy=ReplanStrategy.ESCALATE,
|
|
248
|
+
requires_user_approval=True,
|
|
249
|
+
new_dag=None,
|
|
250
|
+
escalation_message=analysis.error_message or "Subtask failure requires manual intervention.",
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
def apply_replan(
|
|
254
|
+
self,
|
|
255
|
+
goal: Goal,
|
|
256
|
+
dag: GoalDAG,
|
|
257
|
+
replan: ReplanResult,
|
|
258
|
+
) -> GoalDAG:
|
|
259
|
+
"""Apply the approved replan to produce a new (or updated) DAG version."""
|
|
260
|
+
if replan.strategy == ReplanStrategy.RETRY:
|
|
261
|
+
for sid in replan.retried_subtask_ids:
|
|
262
|
+
subtask = dag.subtasks.get(sid)
|
|
263
|
+
if subtask:
|
|
264
|
+
subtask.status = SubtaskStatus.PENDING
|
|
265
|
+
subtask.retry_count += 1
|
|
266
|
+
subtask.error = None
|
|
267
|
+
goal.replan_count += 1
|
|
268
|
+
return dag
|
|
269
|
+
|
|
270
|
+
if replan.strategy == ReplanStrategy.SKIP:
|
|
271
|
+
for sid in replan.skipped_subtask_ids:
|
|
272
|
+
subtask = dag.subtasks.get(sid)
|
|
273
|
+
if subtask:
|
|
274
|
+
subtask.status = SubtaskStatus.SKIPPED
|
|
275
|
+
goal.replan_count += 1
|
|
276
|
+
return dag
|
|
277
|
+
|
|
278
|
+
if replan.new_dag is not None:
|
|
279
|
+
replan.new_dag.version = dag.version + 1
|
|
280
|
+
goal.replan_count += 1
|
|
281
|
+
return replan.new_dag
|
|
282
|
+
|
|
283
|
+
goal.replan_count += 1
|
|
284
|
+
return dag
|
|
285
|
+
|
|
286
|
+
# ── Private helpers ────────────────────────────────────────────────────────
|
|
287
|
+
|
|
288
|
+
def _plan_retry(
|
|
289
|
+
self, goal: Goal, dag: GoalDAG, analysis: FailureAnalysis
|
|
290
|
+
) -> ReplanResult:
|
|
291
|
+
subtask = dag.subtasks.get(analysis.subtask_id)
|
|
292
|
+
if subtask is None or subtask.retry_count >= subtask.max_retries:
|
|
293
|
+
# Escalate if retries exhausted
|
|
294
|
+
return ReplanResult(
|
|
295
|
+
goal_id=goal.goal_id,
|
|
296
|
+
strategy=ReplanStrategy.ESCALATE,
|
|
297
|
+
requires_user_approval=True,
|
|
298
|
+
new_dag=None,
|
|
299
|
+
escalation_message=f"Subtask {analysis.subtask_id} exhausted retries.",
|
|
300
|
+
)
|
|
301
|
+
return ReplanResult(
|
|
302
|
+
goal_id=goal.goal_id,
|
|
303
|
+
strategy=ReplanStrategy.RETRY,
|
|
304
|
+
requires_user_approval=False,
|
|
305
|
+
new_dag=None,
|
|
306
|
+
retried_subtask_ids=[analysis.subtask_id],
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
def _plan_substitute(
|
|
310
|
+
self, goal: Goal, dag: GoalDAG, analysis: FailureAnalysis
|
|
311
|
+
) -> ReplanResult:
|
|
312
|
+
"""Replace the failed subtask with a capability-agnostic fallback."""
|
|
313
|
+
subtask = dag.subtasks.get(analysis.subtask_id)
|
|
314
|
+
if subtask is None:
|
|
315
|
+
return ReplanResult(
|
|
316
|
+
goal_id=goal.goal_id,
|
|
317
|
+
strategy=ReplanStrategy.ESCALATE,
|
|
318
|
+
requires_user_approval=True,
|
|
319
|
+
new_dag=None,
|
|
320
|
+
escalation_message=f"Subtask {analysis.subtask_id} not found for substitution.",
|
|
321
|
+
)
|
|
322
|
+
# Reset capabilities and mark as pending
|
|
323
|
+
new_dag = GoalDAG(
|
|
324
|
+
goal_id=dag.goal_id,
|
|
325
|
+
subtasks=dict(dag.subtasks),
|
|
326
|
+
root_ids=list(dag.root_ids),
|
|
327
|
+
leaf_ids=list(dag.leaf_ids),
|
|
328
|
+
critical_path=list(dag.critical_path),
|
|
329
|
+
max_depth=dag.max_depth,
|
|
330
|
+
version=dag.version,
|
|
331
|
+
created_at=dag.created_at,
|
|
332
|
+
)
|
|
333
|
+
replacement = Subtask(
|
|
334
|
+
subtask_id=subtask.subtask_id,
|
|
335
|
+
goal_id=subtask.goal_id,
|
|
336
|
+
title=subtask.title + " [fallback]",
|
|
337
|
+
job_type="custom", # Capability-agnostic fallback
|
|
338
|
+
capabilities=[],
|
|
339
|
+
depends_on=list(subtask.depends_on),
|
|
340
|
+
estimated_hours=subtask.estimated_hours,
|
|
341
|
+
payload=subtask.payload,
|
|
342
|
+
max_retries=subtask.max_retries,
|
|
343
|
+
)
|
|
344
|
+
new_dag.subtasks[subtask.subtask_id] = replacement
|
|
345
|
+
return ReplanResult(
|
|
346
|
+
goal_id=goal.goal_id,
|
|
347
|
+
strategy=ReplanStrategy.SUBSTITUTE,
|
|
348
|
+
requires_user_approval=False,
|
|
349
|
+
new_dag=new_dag,
|
|
350
|
+
substituted_subtask_ids=[subtask.subtask_id],
|
|
351
|
+
dag_diff=f"Substituted {subtask.subtask_id} with capability-agnostic fallback",
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
def _plan_skip(
|
|
355
|
+
self, goal: Goal, dag: GoalDAG, analysis: FailureAnalysis
|
|
356
|
+
) -> ReplanResult:
|
|
357
|
+
return ReplanResult(
|
|
358
|
+
goal_id=goal.goal_id,
|
|
359
|
+
strategy=ReplanStrategy.SKIP,
|
|
360
|
+
requires_user_approval=False,
|
|
361
|
+
new_dag=None,
|
|
362
|
+
skipped_subtask_ids=[analysis.subtask_id],
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
def _partial_replan(self, goal: Goal, dag: GoalDAG, analysis: FailureAnalysis) -> GoalDAG:
|
|
366
|
+
"""Rebuild the failed subtask and its entire downstream subtree."""
|
|
367
|
+
import uuid as _uuid
|
|
368
|
+
|
|
369
|
+
affected = set(analysis.affected_subtasks + [analysis.subtask_id])
|
|
370
|
+
new_dag = GoalDAG(
|
|
371
|
+
goal_id=dag.goal_id,
|
|
372
|
+
subtasks={
|
|
373
|
+
sid: s for sid, s in dag.subtasks.items()
|
|
374
|
+
if sid not in affected
|
|
375
|
+
},
|
|
376
|
+
root_ids=list(dag.root_ids),
|
|
377
|
+
leaf_ids=list(dag.leaf_ids),
|
|
378
|
+
critical_path=list(dag.critical_path),
|
|
379
|
+
max_depth=dag.max_depth,
|
|
380
|
+
version=dag.version,
|
|
381
|
+
created_at=dag.created_at,
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
# Re-add the failed subtask as pending with incremented retry info
|
|
385
|
+
original = dag.subtasks.get(analysis.subtask_id)
|
|
386
|
+
if original:
|
|
387
|
+
new_subtask = Subtask(
|
|
388
|
+
subtask_id=original.subtask_id,
|
|
389
|
+
goal_id=original.goal_id,
|
|
390
|
+
title=original.title,
|
|
391
|
+
job_type=original.job_type,
|
|
392
|
+
capabilities=list(original.capabilities),
|
|
393
|
+
depends_on=list(original.depends_on),
|
|
394
|
+
estimated_hours=original.estimated_hours,
|
|
395
|
+
payload=dict(original.payload),
|
|
396
|
+
max_retries=original.max_retries,
|
|
397
|
+
retry_count=original.retry_count,
|
|
398
|
+
)
|
|
399
|
+
new_dag.subtasks[new_subtask.subtask_id] = new_subtask
|
|
400
|
+
|
|
401
|
+
# Re-add downstream subtasks as fresh pending
|
|
402
|
+
for sid in analysis.affected_subtasks:
|
|
403
|
+
original_s = dag.subtasks.get(sid)
|
|
404
|
+
if original_s:
|
|
405
|
+
fresh = Subtask(
|
|
406
|
+
subtask_id=original_s.subtask_id,
|
|
407
|
+
goal_id=original_s.goal_id,
|
|
408
|
+
title=original_s.title,
|
|
409
|
+
job_type=original_s.job_type,
|
|
410
|
+
capabilities=list(original_s.capabilities),
|
|
411
|
+
depends_on=list(original_s.depends_on),
|
|
412
|
+
estimated_hours=original_s.estimated_hours,
|
|
413
|
+
payload=dict(original_s.payload),
|
|
414
|
+
max_retries=original_s.max_retries,
|
|
415
|
+
)
|
|
416
|
+
new_dag.subtasks[fresh.subtask_id] = fresh
|
|
417
|
+
|
|
418
|
+
return new_dag
|
|
419
|
+
|
|
420
|
+
def _full_replan(self, goal: Goal, dag: GoalDAG) -> GoalDAG:
|
|
421
|
+
"""Create a new GoalDAG, preserving completed subtasks."""
|
|
422
|
+
completed_ids = {
|
|
423
|
+
sid for sid, s in dag.subtasks.items()
|
|
424
|
+
if s.status in {SubtaskStatus.COMPLETED, SubtaskStatus.SKIPPED}
|
|
425
|
+
}
|
|
426
|
+
new_dag = GoalDAG(
|
|
427
|
+
goal_id=dag.goal_id,
|
|
428
|
+
subtasks={
|
|
429
|
+
sid: s for sid, s in dag.subtasks.items()
|
|
430
|
+
if sid in completed_ids
|
|
431
|
+
},
|
|
432
|
+
version=dag.version,
|
|
433
|
+
created_at=dag.created_at,
|
|
434
|
+
)
|
|
435
|
+
# Reset all non-completed subtasks to pending
|
|
436
|
+
for sid, s in dag.subtasks.items():
|
|
437
|
+
if sid not in completed_ids:
|
|
438
|
+
new_s = Subtask(
|
|
439
|
+
subtask_id=s.subtask_id,
|
|
440
|
+
goal_id=s.goal_id,
|
|
441
|
+
title=s.title,
|
|
442
|
+
job_type=s.job_type,
|
|
443
|
+
capabilities=list(s.capabilities),
|
|
444
|
+
depends_on=list(s.depends_on),
|
|
445
|
+
estimated_hours=s.estimated_hours,
|
|
446
|
+
payload=dict(s.payload),
|
|
447
|
+
max_retries=s.max_retries,
|
|
448
|
+
)
|
|
449
|
+
new_dag.subtasks[new_s.subtask_id] = new_s
|
|
450
|
+
return new_dag
|
apsimo/goals/schema.sql
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
-- Colony Goal Engine persistence schema
|
|
2
|
+
|
|
3
|
+
CREATE TABLE IF NOT EXISTS goals (
|
|
4
|
+
goal_id TEXT PRIMARY KEY,
|
|
5
|
+
title TEXT NOT NULL,
|
|
6
|
+
description TEXT NOT NULL DEFAULT '',
|
|
7
|
+
source TEXT NOT NULL DEFAULT 'explicit',
|
|
8
|
+
status TEXT NOT NULL DEFAULT 'proposed',
|
|
9
|
+
priority INTEGER NOT NULL DEFAULT 50,
|
|
10
|
+
outcome_json TEXT, -- GoalOutcome serialised as JSON
|
|
11
|
+
deadline TEXT, -- ISO-8601 datetime
|
|
12
|
+
parent_goal_id TEXT, -- self-reference for sub-goals
|
|
13
|
+
tags_json TEXT NOT NULL DEFAULT '{}',
|
|
14
|
+
context_json TEXT NOT NULL DEFAULT '{}',
|
|
15
|
+
created_at TEXT NOT NULL,
|
|
16
|
+
updated_at TEXT NOT NULL,
|
|
17
|
+
accepted_at TEXT,
|
|
18
|
+
completed_at TEXT,
|
|
19
|
+
abandoned_at TEXT,
|
|
20
|
+
abandon_reason TEXT,
|
|
21
|
+
replan_count INTEGER NOT NULL DEFAULT 0,
|
|
22
|
+
estimated_hours REAL,
|
|
23
|
+
progress_pct REAL NOT NULL DEFAULT 0.0,
|
|
24
|
+
|
|
25
|
+
-- Initiative management fields (v0.7.10)
|
|
26
|
+
last_initiative_at TEXT, -- ISO-8601: last time an initiative was generated for this goal
|
|
27
|
+
snoozed_until TEXT, -- ISO-8601: don't generate initiatives until this time
|
|
28
|
+
snooze_count INTEGER NOT NULL DEFAULT 0,
|
|
29
|
+
dismissal_reason TEXT, -- Why the goal was dismissed (if applicable)
|
|
30
|
+
|
|
31
|
+
FOREIGN KEY (parent_goal_id) REFERENCES goals(goal_id)
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
CREATE INDEX IF NOT EXISTS idx_goals_status ON goals(status);
|
|
35
|
+
CREATE INDEX IF NOT EXISTS idx_goals_priority ON goals(priority DESC);
|
|
36
|
+
CREATE INDEX IF NOT EXISTS idx_goals_deadline ON goals(deadline);
|
|
37
|
+
CREATE INDEX IF NOT EXISTS idx_goals_last_initiative ON goals(last_initiative_at);
|
|
38
|
+
CREATE INDEX IF NOT EXISTS idx_goals_snoozed_until ON goals(snoozed_until);
|
|
39
|
+
|
|
40
|
+
CREATE TABLE IF NOT EXISTS subtasks (
|
|
41
|
+
subtask_id TEXT PRIMARY KEY,
|
|
42
|
+
goal_id TEXT NOT NULL,
|
|
43
|
+
title TEXT NOT NULL,
|
|
44
|
+
job_type TEXT NOT NULL DEFAULT 'custom',
|
|
45
|
+
payload_json TEXT NOT NULL DEFAULT '{}',
|
|
46
|
+
capabilities_json TEXT NOT NULL DEFAULT '[]',
|
|
47
|
+
depends_on_json TEXT NOT NULL DEFAULT '[]',
|
|
48
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
49
|
+
job_id TEXT, -- task queue job_id
|
|
50
|
+
result_json TEXT,
|
|
51
|
+
depth INTEGER NOT NULL DEFAULT 0,
|
|
52
|
+
is_critical_path INTEGER NOT NULL DEFAULT 0,
|
|
53
|
+
retry_count INTEGER NOT NULL DEFAULT 0,
|
|
54
|
+
max_retries INTEGER NOT NULL DEFAULT 2,
|
|
55
|
+
estimated_hours REAL,
|
|
56
|
+
started_at TEXT,
|
|
57
|
+
completed_at TEXT,
|
|
58
|
+
error TEXT,
|
|
59
|
+
dag_version INTEGER NOT NULL DEFAULT 1,
|
|
60
|
+
FOREIGN KEY (goal_id) REFERENCES goals(goal_id)
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
CREATE INDEX IF NOT EXISTS idx_subtasks_goal ON subtasks(goal_id);
|
|
64
|
+
CREATE INDEX IF NOT EXISTS idx_subtasks_status ON subtasks(status);
|
|
65
|
+
CREATE INDEX IF NOT EXISTS idx_subtasks_job ON subtasks(job_id);
|
|
66
|
+
|
|
67
|
+
CREATE TABLE IF NOT EXISTS goal_audit_log (
|
|
68
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
69
|
+
goal_id TEXT NOT NULL,
|
|
70
|
+
from_status TEXT NOT NULL,
|
|
71
|
+
to_status TEXT NOT NULL,
|
|
72
|
+
trigger TEXT NOT NULL,
|
|
73
|
+
created_at TEXT NOT NULL,
|
|
74
|
+
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
75
|
+
FOREIGN KEY (goal_id) REFERENCES goals(goal_id)
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
CREATE INDEX IF NOT EXISTS idx_audit_goal ON goal_audit_log(goal_id);
|
|
79
|
+
CREATE INDEX IF NOT EXISTS idx_audit_time ON goal_audit_log(created_at DESC);
|
|
80
|
+
|
|
81
|
+
CREATE TABLE IF NOT EXISTS goal_dag_versions (
|
|
82
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
83
|
+
goal_id TEXT NOT NULL,
|
|
84
|
+
version INTEGER NOT NULL,
|
|
85
|
+
dag_json TEXT NOT NULL, -- Full GoalDAG serialised as JSON
|
|
86
|
+
created_at TEXT NOT NULL,
|
|
87
|
+
UNIQUE(goal_id, version),
|
|
88
|
+
FOREIGN KEY (goal_id) REFERENCES goals(goal_id)
|
|
89
|
+
);
|