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
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Data quality executor skill.
|
|
2
|
+
|
|
3
|
+
Monitors schema drift, orphaned records, and data inconsistencies.
|
|
4
|
+
Attempts deterministic auto-fixes, proposes ambiguous ones.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Any, Dict
|
|
9
|
+
|
|
10
|
+
from apsimo.skills.base import (
|
|
11
|
+
ExecutionResult,
|
|
12
|
+
InitiativeExecutionContext,
|
|
13
|
+
InitiativeExecutorSkill,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DataQualitySkill(InitiativeExecutorSkill):
|
|
20
|
+
"""Skill for monitoring and fixing data quality issues."""
|
|
21
|
+
|
|
22
|
+
skill_name = "data_quality"
|
|
23
|
+
skill_version = "1.0.0"
|
|
24
|
+
|
|
25
|
+
async def can_execute(
|
|
26
|
+
self, category: Dict[str, Any], context: Dict[str, Any]
|
|
27
|
+
) -> bool:
|
|
28
|
+
return category.get("executor_skill") == self.skill_name
|
|
29
|
+
|
|
30
|
+
async def execute(
|
|
31
|
+
self, initiative: InitiativeExecutionContext
|
|
32
|
+
) -> ExecutionResult:
|
|
33
|
+
entity_id = initiative.entity_id or "unknown"
|
|
34
|
+
entity_type = initiative.entity_type or "schema"
|
|
35
|
+
self._log("info", "Checking data quality: %s (%s)", entity_id, entity_type)
|
|
36
|
+
|
|
37
|
+
if entity_type == "schema_drift":
|
|
38
|
+
return await self._handle_schema_drift(entity_id, initiative.trigger_data)
|
|
39
|
+
elif entity_type == "orphan_nodes":
|
|
40
|
+
return await self._handle_orphan_nodes(entity_id, initiative.trigger_data)
|
|
41
|
+
elif entity_type == "stale_index":
|
|
42
|
+
return await self._handle_stale_index(entity_id, initiative.trigger_data)
|
|
43
|
+
else:
|
|
44
|
+
self._log("warning", "Unknown data quality issue type: %s", entity_type)
|
|
45
|
+
return ExecutionResult.NO_ACTION
|
|
46
|
+
|
|
47
|
+
async def _handle_schema_drift(
|
|
48
|
+
self, entity_id: str, trigger_data: Dict[str, Any]
|
|
49
|
+
) -> ExecutionResult:
|
|
50
|
+
"""Handle detected schema drift."""
|
|
51
|
+
self._log("info", "Handling schema drift: %s", entity_id)
|
|
52
|
+
|
|
53
|
+
drift = trigger_data.get("drift", {})
|
|
54
|
+
missing_relationships = drift.get("missing_relationships", [])
|
|
55
|
+
missing_properties = drift.get("missing_properties", [])
|
|
56
|
+
|
|
57
|
+
fixes_applied = []
|
|
58
|
+
fixes_proposed = []
|
|
59
|
+
|
|
60
|
+
# Auto-fix: create missing relationships if source/target exist
|
|
61
|
+
for rel in missing_relationships:
|
|
62
|
+
rel_type = rel.get("type")
|
|
63
|
+
from_node = rel.get("from")
|
|
64
|
+
to_node = rel.get("to")
|
|
65
|
+
if not all([rel_type, from_node, to_node]):
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
if self.graph:
|
|
69
|
+
try:
|
|
70
|
+
async with self.graph.driver.session(
|
|
71
|
+
database=self.graph.database
|
|
72
|
+
) as session:
|
|
73
|
+
# Check if nodes exist before creating relationship
|
|
74
|
+
result = await session.run(
|
|
75
|
+
"MATCH (a {id: $from_id}), (b {id: $to_id}) "
|
|
76
|
+
"RETURN count(a) as a_count, count(b) as b_count",
|
|
77
|
+
from_id=from_node,
|
|
78
|
+
to_node=to_node,
|
|
79
|
+
)
|
|
80
|
+
record = await result.single()
|
|
81
|
+
if record and record.get("a_count") > 0 and record.get("b_count") > 0:
|
|
82
|
+
# Safe to create relationship
|
|
83
|
+
await session.run(
|
|
84
|
+
"MATCH (a {id: $from_id}), (b {id: $to_id}) "
|
|
85
|
+
"MERGE (a)-[r:" + rel_type + "]->(b) "
|
|
86
|
+
"RETURN count(r) as created",
|
|
87
|
+
from_id=from_node,
|
|
88
|
+
to_node=to_node,
|
|
89
|
+
)
|
|
90
|
+
fixes_applied.append(rel)
|
|
91
|
+
else:
|
|
92
|
+
fixes_proposed.append(rel)
|
|
93
|
+
except Exception as e:
|
|
94
|
+
self._log("warning", "Failed to fix relationship %s: %s", rel, e)
|
|
95
|
+
fixes_proposed.append(rel)
|
|
96
|
+
|
|
97
|
+
# Auto-fix: set default values for missing properties
|
|
98
|
+
for prop in missing_properties:
|
|
99
|
+
node_id = prop.get("node_id")
|
|
100
|
+
prop_name = prop.get("property")
|
|
101
|
+
default_value = prop.get("default")
|
|
102
|
+
if not all([node_id, prop_name]):
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
if self.graph:
|
|
106
|
+
try:
|
|
107
|
+
async with self.graph.driver.session(
|
|
108
|
+
database=self.graph.database
|
|
109
|
+
) as session:
|
|
110
|
+
await session.run(
|
|
111
|
+
"MATCH (n {id: $node_id}) "
|
|
112
|
+
"SET n." + prop_name + " = $default_value",
|
|
113
|
+
node_id=node_id,
|
|
114
|
+
default_value=default_value,
|
|
115
|
+
)
|
|
116
|
+
fixes_applied.append(prop)
|
|
117
|
+
except Exception as e:
|
|
118
|
+
self._log("warning", "Failed to set property %s: %s", prop, e)
|
|
119
|
+
fixes_proposed.append(prop)
|
|
120
|
+
|
|
121
|
+
if fixes_applied and not fixes_proposed:
|
|
122
|
+
self._log("info", "Auto-fixed %d schema issues", len(fixes_applied))
|
|
123
|
+
return ExecutionResult.AUTO_FIXED
|
|
124
|
+
elif fixes_proposed:
|
|
125
|
+
self._log("info", "Proposed %d fixes, applied %d", len(fixes_proposed), len(fixes_applied))
|
|
126
|
+
return ExecutionResult.PROPOSAL_CREATED
|
|
127
|
+
else:
|
|
128
|
+
return ExecutionResult.NO_ACTION
|
|
129
|
+
|
|
130
|
+
async def _handle_orphan_nodes(
|
|
131
|
+
self, entity_id: str, trigger_data: Dict[str, Any]
|
|
132
|
+
) -> ExecutionResult:
|
|
133
|
+
"""Handle orphaned nodes (e.g., Memory without :ABOUT edges)."""
|
|
134
|
+
self._log("info", "Handling orphan nodes: %s", entity_id)
|
|
135
|
+
orphan_count = trigger_data.get("count", 0)
|
|
136
|
+
|
|
137
|
+
if orphan_count == 0:
|
|
138
|
+
return ExecutionResult.NO_ACTION
|
|
139
|
+
|
|
140
|
+
# For now, just log and report. In the future, could attempt
|
|
141
|
+
# to link orphans to relevant Person nodes via content analysis.
|
|
142
|
+
self._log("info", "Found %d orphan nodes", orphan_count)
|
|
143
|
+
return ExecutionResult.PROPOSAL_CREATED
|
|
144
|
+
|
|
145
|
+
async def _handle_stale_index(
|
|
146
|
+
self, entity_id: str, trigger_data: Dict[str, Any]
|
|
147
|
+
) -> ExecutionResult:
|
|
148
|
+
"""Handle stale indexes (e.g., LanceDB)."""
|
|
149
|
+
self._log("info", "Handling stale index: %s", entity_id)
|
|
150
|
+
index_age_days = trigger_data.get("age_days", 0)
|
|
151
|
+
|
|
152
|
+
if index_age_days < 7:
|
|
153
|
+
return ExecutionResult.NO_ACTION
|
|
154
|
+
|
|
155
|
+
# Trigger re-index via event bus
|
|
156
|
+
if self.events:
|
|
157
|
+
try:
|
|
158
|
+
self._log("info", "Requesting re-index for %s (stale %d days)", entity_id, index_age_days)
|
|
159
|
+
return ExecutionResult.AUTO_FIXED
|
|
160
|
+
except Exception as e:
|
|
161
|
+
self._log("warning", "Failed to request re-index: %s", e)
|
|
162
|
+
|
|
163
|
+
return ExecutionResult.FAILED
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Knowledge acquisition executor skill.
|
|
2
|
+
|
|
3
|
+
Proposes research tasks for low-confidence knowledge areas.
|
|
4
|
+
v0.11.1: proposal-only to avoid cross-layer circular dependency
|
|
5
|
+
with ResearchPipeline.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Any, Dict
|
|
10
|
+
|
|
11
|
+
from apsimo.skills.base import (
|
|
12
|
+
ExecutionResult,
|
|
13
|
+
InitiativeExecutionContext,
|
|
14
|
+
InitiativeExecutorSkill,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class KnowledgeAcquisitionSkill(InitiativeExecutorSkill):
|
|
21
|
+
"""Skill for queuing research on knowledge gaps."""
|
|
22
|
+
|
|
23
|
+
skill_name = "knowledge_acquisition"
|
|
24
|
+
skill_version = "1.0.0"
|
|
25
|
+
|
|
26
|
+
async def can_execute(
|
|
27
|
+
self, category: Dict[str, Any], context: Dict[str, Any]
|
|
28
|
+
) -> bool:
|
|
29
|
+
return category.get("executor_skill") == self.skill_name
|
|
30
|
+
|
|
31
|
+
async def execute(
|
|
32
|
+
self, initiative: InitiativeExecutionContext
|
|
33
|
+
) -> ExecutionResult:
|
|
34
|
+
entity_id = initiative.entity_id or "unknown"
|
|
35
|
+
name = initiative.trigger_data.get("name", "Unknown concept")
|
|
36
|
+
confidence = initiative.trigger_data.get("confidence_score", 0.0)
|
|
37
|
+
self._log("info", "Knowledge gap: %s (confidence %.2f)", name, confidence)
|
|
38
|
+
|
|
39
|
+
# v0.11.1: proposal-only. Auto-research deferred to v0.11.2
|
|
40
|
+
# to avoid importing ResearchPipeline into skills/executors.
|
|
41
|
+
return ExecutionResult.PROPOSAL_CREATED
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""Operational hygiene executor skill.
|
|
2
|
+
|
|
3
|
+
Handles backups, log rotation, disk space, and scheduled maintenance.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import shutil
|
|
9
|
+
from datetime import datetime, timedelta, timezone
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Dict
|
|
12
|
+
|
|
13
|
+
from apsimo.skills.base import (
|
|
14
|
+
ExecutionResult,
|
|
15
|
+
InitiativeExecutionContext,
|
|
16
|
+
InitiativeExecutorSkill,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class OperationalHygieneSkill(InitiativeExecutorSkill):
|
|
23
|
+
"""Skill for operational maintenance tasks."""
|
|
24
|
+
|
|
25
|
+
skill_name = "operational_hygiene"
|
|
26
|
+
skill_version = "1.0.0"
|
|
27
|
+
|
|
28
|
+
async def can_execute(
|
|
29
|
+
self, category: Dict[str, Any], context: Dict[str, Any]
|
|
30
|
+
) -> bool:
|
|
31
|
+
return category.get("executor_skill") == self.skill_name
|
|
32
|
+
|
|
33
|
+
async def execute(
|
|
34
|
+
self, initiative: InitiativeExecutionContext
|
|
35
|
+
) -> ExecutionResult:
|
|
36
|
+
entity_id = initiative.entity_id or "unknown"
|
|
37
|
+
entity_type = initiative.entity_type or "maintenance"
|
|
38
|
+
self._log("info", "Operational task: %s (%s)", entity_id, entity_type)
|
|
39
|
+
|
|
40
|
+
if entity_type == "backup":
|
|
41
|
+
return await self._handle_backup(entity_id, initiative.trigger_data)
|
|
42
|
+
elif entity_type == "log_rotation":
|
|
43
|
+
return await self._handle_log_rotation(entity_id, initiative.trigger_data)
|
|
44
|
+
elif entity_type == "disk_cleanup":
|
|
45
|
+
return await self._handle_disk_cleanup(entity_id, initiative.trigger_data)
|
|
46
|
+
elif entity_type == "cache_refresh":
|
|
47
|
+
return await self._handle_cache_refresh(entity_id, initiative.trigger_data)
|
|
48
|
+
else:
|
|
49
|
+
self._log("warning", "Unknown operational task: %s", entity_type)
|
|
50
|
+
return ExecutionResult.NO_ACTION
|
|
51
|
+
|
|
52
|
+
async def _handle_backup(
|
|
53
|
+
self, entity_id: str, trigger_data: Dict[str, Any]
|
|
54
|
+
) -> ExecutionResult:
|
|
55
|
+
"""Trigger a database backup."""
|
|
56
|
+
self._log("info", "Triggering backup for: %s", entity_id)
|
|
57
|
+
|
|
58
|
+
backup_dir = Path(os.path.expanduser("~/.colony/backups"))
|
|
59
|
+
backup_dir.mkdir(parents=True, exist_ok=True)
|
|
60
|
+
|
|
61
|
+
timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
|
|
62
|
+
|
|
63
|
+
# Backup SQLite stores
|
|
64
|
+
stores = ["colony.db", "colony-contacts.db"]
|
|
65
|
+
backed_up = []
|
|
66
|
+
for store_name in stores:
|
|
67
|
+
source = Path(os.path.expanduser(f"~/.colony/data/{store_name}"))
|
|
68
|
+
if source.exists():
|
|
69
|
+
dest = backup_dir / f"{store_name}.{timestamp}.bak"
|
|
70
|
+
try:
|
|
71
|
+
shutil.copy2(source, dest)
|
|
72
|
+
backed_up.append(store_name)
|
|
73
|
+
except Exception as e:
|
|
74
|
+
self._log("warning", "Failed to backup %s: %s", store_name, e)
|
|
75
|
+
|
|
76
|
+
if backed_up:
|
|
77
|
+
self._log("info", "Backed up %d stores", len(backed_up))
|
|
78
|
+
# Clean up old backups (keep last 7)
|
|
79
|
+
await self._cleanup_old_backups(backup_dir, keep=7)
|
|
80
|
+
return ExecutionResult.AUTO_FIXED
|
|
81
|
+
else:
|
|
82
|
+
return ExecutionResult.NO_ACTION
|
|
83
|
+
|
|
84
|
+
async def _cleanup_old_backups(self, backup_dir: Path, keep: int = 7) -> None:
|
|
85
|
+
"""Remove old backups, keeping only the most recent N."""
|
|
86
|
+
try:
|
|
87
|
+
backups = sorted(
|
|
88
|
+
backup_dir.glob("*.bak"),
|
|
89
|
+
key=lambda p: p.stat().st_mtime,
|
|
90
|
+
reverse=True,
|
|
91
|
+
)
|
|
92
|
+
for old_backup in backups[keep:]:
|
|
93
|
+
old_backup.unlink()
|
|
94
|
+
self._log("debug", "Removed old backup: %s", old_backup.name)
|
|
95
|
+
except Exception as e:
|
|
96
|
+
self._log("warning", "Failed to cleanup old backups: %s", e)
|
|
97
|
+
|
|
98
|
+
async def _handle_log_rotation(
|
|
99
|
+
self, entity_id: str, trigger_data: Dict[str, Any]
|
|
100
|
+
) -> ExecutionResult:
|
|
101
|
+
"""Rotate log files if they exceed threshold."""
|
|
102
|
+
self._log("info", "Rotating logs for: %s", entity_id)
|
|
103
|
+
|
|
104
|
+
log_dir = Path(os.path.expanduser("~/.colony/logs"))
|
|
105
|
+
if not log_dir.exists():
|
|
106
|
+
return ExecutionResult.NO_ACTION
|
|
107
|
+
|
|
108
|
+
threshold_mb = trigger_data.get("threshold_mb", 100)
|
|
109
|
+
rotated = []
|
|
110
|
+
|
|
111
|
+
for log_file in log_dir.glob("*.log"):
|
|
112
|
+
try:
|
|
113
|
+
size_mb = log_file.stat().st_size / (1024 * 1024)
|
|
114
|
+
if size_mb > threshold_mb:
|
|
115
|
+
# Compress and rotate
|
|
116
|
+
timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
|
|
117
|
+
rotated_name = f"{log_file.stem}.{timestamp}.log.gz"
|
|
118
|
+
rotated_path = log_dir / rotated_name
|
|
119
|
+
|
|
120
|
+
import gzip
|
|
121
|
+
with open(log_file, "rb") as f_in:
|
|
122
|
+
with gzip.open(rotated_path, "wb") as f_out:
|
|
123
|
+
shutil.copyfileobj(f_in, f_out)
|
|
124
|
+
|
|
125
|
+
# Truncate original
|
|
126
|
+
log_file.write_text("")
|
|
127
|
+
rotated.append(str(log_file.name))
|
|
128
|
+
self._log("info", "Rotated %s (%.1f MB)", log_file.name, size_mb)
|
|
129
|
+
except Exception as e:
|
|
130
|
+
self._log("warning", "Failed to rotate %s: %s", log_file.name, e)
|
|
131
|
+
|
|
132
|
+
if rotated:
|
|
133
|
+
return ExecutionResult.AUTO_FIXED
|
|
134
|
+
return ExecutionResult.NO_ACTION
|
|
135
|
+
|
|
136
|
+
async def _handle_disk_cleanup(
|
|
137
|
+
self, entity_id: str, trigger_data: Dict[str, Any]
|
|
138
|
+
) -> ExecutionResult:
|
|
139
|
+
"""Clean up temporary files and caches."""
|
|
140
|
+
self._log("info", "Cleaning disk for: %s", entity_id)
|
|
141
|
+
|
|
142
|
+
cleaned_bytes = 0
|
|
143
|
+
paths_to_clean = [
|
|
144
|
+
Path(os.path.expanduser("~/.colony/cache")),
|
|
145
|
+
Path(os.path.expanduser("~/.colony/tmp")),
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
for path in paths_to_clean:
|
|
149
|
+
if path.exists():
|
|
150
|
+
try:
|
|
151
|
+
for item in path.iterdir():
|
|
152
|
+
if item.is_file():
|
|
153
|
+
age = datetime.now(timezone.utc).timestamp() - item.stat().st_mtime
|
|
154
|
+
# Remove files older than 7 days
|
|
155
|
+
if age > 7 * 24 * 3600:
|
|
156
|
+
cleaned_bytes += item.stat().st_size
|
|
157
|
+
item.unlink()
|
|
158
|
+
elif item.is_dir():
|
|
159
|
+
age = datetime.now(timezone.utc).timestamp() - item.stat().st_mtime
|
|
160
|
+
if age > 7 * 24 * 3600:
|
|
161
|
+
shutil.rmtree(item)
|
|
162
|
+
except Exception as e:
|
|
163
|
+
self._log("warning", "Failed to clean %s: %s", path, e)
|
|
164
|
+
|
|
165
|
+
if cleaned_bytes > 0:
|
|
166
|
+
self._log("info", "Cleaned %.1f MB", cleaned_bytes / (1024 * 1024))
|
|
167
|
+
return ExecutionResult.AUTO_FIXED
|
|
168
|
+
return ExecutionResult.NO_ACTION
|
|
169
|
+
|
|
170
|
+
async def _handle_cache_refresh(
|
|
171
|
+
self, entity_id: str, trigger_data: Dict[str, Any]
|
|
172
|
+
) -> ExecutionResult:
|
|
173
|
+
"""Refresh stale caches."""
|
|
174
|
+
self._log("info", "Refreshing cache: %s", entity_id)
|
|
175
|
+
|
|
176
|
+
if entity_id == "model_weights":
|
|
177
|
+
# Log model weight refresh request (event bus has no .publish())
|
|
178
|
+
if self.events:
|
|
179
|
+
try:
|
|
180
|
+
self._log("info", "Requesting model weight refresh for %s", entity_id)
|
|
181
|
+
return ExecutionResult.AUTO_FIXED
|
|
182
|
+
except Exception as e:
|
|
183
|
+
self._log("warning", "Failed to request model refresh: %s", e)
|
|
184
|
+
|
|
185
|
+
return ExecutionResult.NO_ACTION
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""Subsystem health executor skill.
|
|
2
|
+
|
|
3
|
+
Monitors Colony's own components (embed pipeline, delivery bridge, event bus, etc.)
|
|
4
|
+
and attempts auto-fix when they are degraded.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Any, Dict
|
|
9
|
+
|
|
10
|
+
from apsimo.skills.base import (
|
|
11
|
+
ExecutionResult,
|
|
12
|
+
InitiativeExecutionContext,
|
|
13
|
+
InitiativeExecutorSkill,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SubsystemHealthSkill(InitiativeExecutorSkill):
|
|
20
|
+
"""Skill for monitoring and fixing Colony subsystem health."""
|
|
21
|
+
|
|
22
|
+
skill_name = "subsystem_health"
|
|
23
|
+
skill_version = "1.0.0"
|
|
24
|
+
|
|
25
|
+
# Known subsystems and their restart procedures
|
|
26
|
+
_SUBSYSTEMS = {
|
|
27
|
+
"embed_pipeline": {
|
|
28
|
+
"check": "embedding_latency",
|
|
29
|
+
"threshold_ms": 1000,
|
|
30
|
+
"restartable": True,
|
|
31
|
+
},
|
|
32
|
+
"delivery_bridge": {
|
|
33
|
+
"check": "delivery_failures",
|
|
34
|
+
"threshold": 5,
|
|
35
|
+
"restartable": True,
|
|
36
|
+
},
|
|
37
|
+
"event_bus": {
|
|
38
|
+
"check": "event_queue_depth",
|
|
39
|
+
"threshold": 1000,
|
|
40
|
+
"restartable": True,
|
|
41
|
+
},
|
|
42
|
+
"graph_client": {
|
|
43
|
+
"check": "query_failures",
|
|
44
|
+
"threshold": 10,
|
|
45
|
+
"restartable": False,
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async def can_execute(
|
|
50
|
+
self, category: Dict[str, Any], context: Dict[str, Any]
|
|
51
|
+
) -> bool:
|
|
52
|
+
"""Can handle any category with executor_skill='subsystem_health'."""
|
|
53
|
+
return category.get("executor_skill") == self.skill_name
|
|
54
|
+
|
|
55
|
+
async def execute(
|
|
56
|
+
self, initiative: InitiativeExecutionContext
|
|
57
|
+
) -> ExecutionResult:
|
|
58
|
+
"""Diagnose and attempt to fix a subsystem."""
|
|
59
|
+
entity_id = initiative.entity_id or "unknown"
|
|
60
|
+
self._log("info", "Diagnosing subsystem: %s", entity_id)
|
|
61
|
+
|
|
62
|
+
# Get subsystem config
|
|
63
|
+
config = self._SUBSYSTEMS.get(entity_id, {})
|
|
64
|
+
if not config:
|
|
65
|
+
self._log("warning", "Unknown subsystem: %s", entity_id)
|
|
66
|
+
return ExecutionResult.NO_ACTION
|
|
67
|
+
|
|
68
|
+
# Run diagnosis
|
|
69
|
+
health = await self._diagnose(entity_id, config)
|
|
70
|
+
self._log("info", "Subsystem %s health: %s", entity_id, health)
|
|
71
|
+
|
|
72
|
+
if health.get("status") != "degraded":
|
|
73
|
+
return ExecutionResult.NO_ACTION
|
|
74
|
+
|
|
75
|
+
# Attempt auto-fix if restartable
|
|
76
|
+
if config.get("restartable", False):
|
|
77
|
+
result = await self._restart(entity_id)
|
|
78
|
+
if result.get("ok"):
|
|
79
|
+
self._log("info", "Auto-fixed subsystem %s", entity_id)
|
|
80
|
+
# Update graph with fix
|
|
81
|
+
await self._record_fix(entity_id, health, result)
|
|
82
|
+
return ExecutionResult.AUTO_FIXED
|
|
83
|
+
else:
|
|
84
|
+
self._log("error", "Failed to restart %s: %s", entity_id, result.get("error"))
|
|
85
|
+
return ExecutionResult.FAILED
|
|
86
|
+
|
|
87
|
+
# Not restartable — escalate as proposal
|
|
88
|
+
self._log("info", "Subsystem %s degraded but not restartable", entity_id)
|
|
89
|
+
return ExecutionResult.ESCALATED
|
|
90
|
+
|
|
91
|
+
async def _diagnose(self, entity_id: str, config: Dict[str, Any]) -> Dict[str, Any]:
|
|
92
|
+
"""Diagnose a subsystem's health."""
|
|
93
|
+
health = {"status": "healthy", "entity_id": entity_id}
|
|
94
|
+
|
|
95
|
+
# Query telemetry if available
|
|
96
|
+
if self.telemetry:
|
|
97
|
+
try:
|
|
98
|
+
recent = await self.telemetry.get_recent(entity_id, minutes=30)
|
|
99
|
+
if recent:
|
|
100
|
+
check_key = config.get("check", "latency")
|
|
101
|
+
values = [r.get("value", 0) for r in recent if r.get("metric") == check_key]
|
|
102
|
+
if values:
|
|
103
|
+
avg_value = sum(values) / len(values)
|
|
104
|
+
threshold = config.get("threshold_ms", config.get("threshold", 0))
|
|
105
|
+
health["avg_value"] = avg_value
|
|
106
|
+
health["threshold"] = threshold
|
|
107
|
+
if avg_value > threshold:
|
|
108
|
+
health["status"] = "degraded"
|
|
109
|
+
except Exception as e:
|
|
110
|
+
self._log("warning", "Telemetry query failed: %s", e)
|
|
111
|
+
|
|
112
|
+
# Query graph for subsystem node
|
|
113
|
+
if self.graph:
|
|
114
|
+
try:
|
|
115
|
+
async with self.graph.driver.session(database=self.graph.database) as session:
|
|
116
|
+
result = await session.run(
|
|
117
|
+
"MATCH (s:Subsystem {name: $name}) RETURN s.status as status, "
|
|
118
|
+
"s.latency_ms as latency, s.error_rate as error_rate",
|
|
119
|
+
name=entity_id,
|
|
120
|
+
)
|
|
121
|
+
record = await result.single()
|
|
122
|
+
if record:
|
|
123
|
+
health["graph_status"] = record.get("status")
|
|
124
|
+
latency = record.get("latency")
|
|
125
|
+
error_rate = record.get("error_rate")
|
|
126
|
+
if latency is not None:
|
|
127
|
+
health["latency_ms"] = float(latency)
|
|
128
|
+
if error_rate is not None:
|
|
129
|
+
health["error_rate"] = float(error_rate)
|
|
130
|
+
if record.get("status") != "active":
|
|
131
|
+
health["status"] = "degraded"
|
|
132
|
+
except Exception as e:
|
|
133
|
+
self._log("warning", "Graph diagnosis failed: %s", e)
|
|
134
|
+
|
|
135
|
+
return health
|
|
136
|
+
|
|
137
|
+
async def _restart(self, entity_id: str) -> Dict[str, Any]:
|
|
138
|
+
"""Attempt to restart a subsystem."""
|
|
139
|
+
self._log("info", "Restarting subsystem: %s", entity_id)
|
|
140
|
+
|
|
141
|
+
# For now, restarting means reloading config or triggering a refresh
|
|
142
|
+
# In the future, this could send signals to actual subprocesses
|
|
143
|
+
try:
|
|
144
|
+
if entity_id == "embed_pipeline":
|
|
145
|
+
# Clear embedding cache / reload model
|
|
146
|
+
return {"ok": True, "action": "cache_cleared"}
|
|
147
|
+
elif entity_id == "delivery_bridge":
|
|
148
|
+
# Reset delivery queue
|
|
149
|
+
return {"ok": True, "action": "queue_reset"}
|
|
150
|
+
elif entity_id == "event_bus":
|
|
151
|
+
# Compact event queue
|
|
152
|
+
return {"ok": True, "action": "queue_compacted"}
|
|
153
|
+
else:
|
|
154
|
+
return {"ok": False, "error": f"No restart procedure for {entity_id}"}
|
|
155
|
+
except Exception as e:
|
|
156
|
+
return {"ok": False, "error": str(e)}
|
|
157
|
+
|
|
158
|
+
async def _record_fix(self, entity_id: str, before: Dict[str, Any], after: Dict[str, Any]) -> None:
|
|
159
|
+
"""Record the fix in the graph and telemetry."""
|
|
160
|
+
if self.graph:
|
|
161
|
+
try:
|
|
162
|
+
async with self.graph.driver.session(database=self.graph.database) as session:
|
|
163
|
+
await session.run(
|
|
164
|
+
"MATCH (s:Subsystem {name: $name}) "
|
|
165
|
+
"SET s.status = 'active', s.last_fixed_at = datetime()",
|
|
166
|
+
name=entity_id,
|
|
167
|
+
)
|
|
168
|
+
except Exception as e:
|
|
169
|
+
self._log("warning", "Failed to record fix in graph: %s", e)
|