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,116 @@
|
|
|
1
|
+
"""MonitoringHandler — health-check probe jobs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import ipaddress
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import time
|
|
9
|
+
from typing import Any, Dict
|
|
10
|
+
from urllib.parse import urlsplit
|
|
11
|
+
|
|
12
|
+
from apsimo.task_queue.handlers.base import JobHandler, Job
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _host_allowlisted(host: str) -> bool:
|
|
18
|
+
entries = {
|
|
19
|
+
item.strip().lower()
|
|
20
|
+
for item in os.environ.get(
|
|
21
|
+
"COLONY_MONITORING_HOST_ALLOWLIST", ""
|
|
22
|
+
).split(",")
|
|
23
|
+
if item.strip()
|
|
24
|
+
}
|
|
25
|
+
host = host.lower().rstrip(".")
|
|
26
|
+
return host in entries or any(
|
|
27
|
+
entry.startswith("*.") and host.endswith(entry[1:])
|
|
28
|
+
for entry in entries
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _unsafe_address(value: str) -> bool:
|
|
33
|
+
address = ipaddress.ip_address(value)
|
|
34
|
+
return not address.is_global
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
async def _validate_endpoint(url: str) -> str:
|
|
38
|
+
parsed = urlsplit(str(url or ""))
|
|
39
|
+
if parsed.scheme not in {"http", "https"} or not parsed.hostname:
|
|
40
|
+
raise ValueError("monitoring endpoint must be an http(s) URL")
|
|
41
|
+
if parsed.username is not None or parsed.password is not None:
|
|
42
|
+
raise ValueError("monitoring endpoint cannot contain credentials")
|
|
43
|
+
host = parsed.hostname.rstrip(".")
|
|
44
|
+
try:
|
|
45
|
+
address = str(ipaddress.ip_address(host))
|
|
46
|
+
except ValueError:
|
|
47
|
+
# DNS resolution followed by an ordinary HTTP client resolution is a
|
|
48
|
+
# rebinding TOCTOU. Hostnames therefore require an exact/operator
|
|
49
|
+
# wildcard allowlist entry; literal public IPs need no DNS at all.
|
|
50
|
+
if not _host_allowlisted(host):
|
|
51
|
+
raise ValueError(
|
|
52
|
+
"monitoring hostname must be explicitly configured in "
|
|
53
|
+
"COLONY_MONITORING_HOST_ALLOWLIST"
|
|
54
|
+
)
|
|
55
|
+
return url
|
|
56
|
+
if _unsafe_address(address) and not _host_allowlisted(host):
|
|
57
|
+
raise ValueError(
|
|
58
|
+
"monitoring endpoint resolves to a non-public address; configure "
|
|
59
|
+
"COLONY_MONITORING_HOST_ALLOWLIST for an intentional internal probe"
|
|
60
|
+
)
|
|
61
|
+
return url
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class MonitoringHandler(JobHandler):
|
|
65
|
+
"""Run a health-check probe.
|
|
66
|
+
|
|
67
|
+
Job payload:
|
|
68
|
+
endpoint (str): HTTP URL to GET.
|
|
69
|
+
expected_status (int, default 200): Expected HTTP status.
|
|
70
|
+
timeout_secs (float, default 10): Per-request timeout.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
{"metrics": {"status": int, "latency_ms": float}, "alerts": list[str]}
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
async def execute(self, job: Job) -> Dict[str, Any]:
|
|
77
|
+
try:
|
|
78
|
+
import aiohttp
|
|
79
|
+
except ImportError as exc:
|
|
80
|
+
raise RuntimeError(
|
|
81
|
+
"MonitoringHandler requires 'aiohttp'. Install with: pip install aiohttp"
|
|
82
|
+
) from exc
|
|
83
|
+
|
|
84
|
+
url = await _validate_endpoint(job.payload["endpoint"])
|
|
85
|
+
expected = job.payload.get("expected_status", 200)
|
|
86
|
+
timeout = job.payload.get("timeout_secs", 10.0)
|
|
87
|
+
|
|
88
|
+
t0 = time.monotonic()
|
|
89
|
+
async with aiohttp.ClientSession() as session:
|
|
90
|
+
async with session.get(
|
|
91
|
+
url,
|
|
92
|
+
timeout=aiohttp.ClientTimeout(total=timeout),
|
|
93
|
+
allow_redirects=False,
|
|
94
|
+
) as resp:
|
|
95
|
+
latency_ms = (time.monotonic() - t0) * 1000
|
|
96
|
+
alerts = []
|
|
97
|
+
if resp.status != expected:
|
|
98
|
+
alerts.append(
|
|
99
|
+
f"Expected status {expected}, got {resp.status}"
|
|
100
|
+
)
|
|
101
|
+
succeeded = not alerts
|
|
102
|
+
return {
|
|
103
|
+
"status": "completed" if succeeded else "failed",
|
|
104
|
+
"summary": (
|
|
105
|
+
f"probe returned expected HTTP {resp.status}"
|
|
106
|
+
if succeeded else "; ".join(alerts)
|
|
107
|
+
),
|
|
108
|
+
"action_plane": {
|
|
109
|
+
"state": "completed" if succeeded else "failed",
|
|
110
|
+
},
|
|
111
|
+
"metrics": {
|
|
112
|
+
"status": resp.status,
|
|
113
|
+
"latency_ms": round(latency_ms, 2),
|
|
114
|
+
},
|
|
115
|
+
"alerts": alerts,
|
|
116
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Default handler registry for the Colony task queue."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Dict, Optional
|
|
6
|
+
|
|
7
|
+
from apsimo.task_queue.models import JobType
|
|
8
|
+
from apsimo.task_queue.worker import JobHandler
|
|
9
|
+
from apsimo.task_queue.handlers.monitoring import MonitoringHandler
|
|
10
|
+
from apsimo.task_queue.handlers.subtask_handler import SubtaskHandler
|
|
11
|
+
from apsimo.task_queue.handlers.system_maintenance import SystemMaintenanceHandler
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from apsimo.router.router import LLMRouter
|
|
15
|
+
from apsimo.world_model.store import WorldModelStore
|
|
16
|
+
from apsimo.contacts.store import ContactStore
|
|
17
|
+
from apsimo.task_queue.handlers.inference import _InferenceGateSessionStore
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def build_default_handlers(
|
|
21
|
+
router: Optional["LLMRouter"] = None,
|
|
22
|
+
world_model_store: Optional["WorldModelStore"] = None,
|
|
23
|
+
contact_store: Optional["ContactStore"] = None,
|
|
24
|
+
response_gate: Optional[Any] = None,
|
|
25
|
+
gate_session_store: Optional["_InferenceGateSessionStore"] = None,
|
|
26
|
+
desktop_config: Optional[Any] = None,
|
|
27
|
+
browser_config: Optional[Any] = None,
|
|
28
|
+
node_id: str = "",
|
|
29
|
+
) -> Dict[JobType, JobHandler]:
|
|
30
|
+
"""Assemble the default set of job handlers.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
router: LLMRouter instance. Required for INFERENCE jobs.
|
|
34
|
+
If None, InferenceHandler is omitted from the registry.
|
|
35
|
+
world_model_store: Optional WorldModelStore for context enrichment.
|
|
36
|
+
contact_store: Optional ContactStore for contact resolution.
|
|
37
|
+
response_gate: Optional ResponseGate for outbound response filtering.
|
|
38
|
+
gate_session_store: Session store adapter used by the ResponseGate.
|
|
39
|
+
desktop_config: Retired unsupported parameter; must be None.
|
|
40
|
+
browser_config: Retired unsupported parameter; must be None.
|
|
41
|
+
node_id: Legacy compatibility parameter, unused by supported handlers.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Dict mapping JobType → JobHandler instance.
|
|
45
|
+
"""
|
|
46
|
+
if desktop_config is not None or browser_config is not None:
|
|
47
|
+
raise ValueError(
|
|
48
|
+
"Desktop/browser queue workers are unsupported. Remove desktop_config "
|
|
49
|
+
"and browser_config; use the native runtime's registered tools."
|
|
50
|
+
)
|
|
51
|
+
handlers: Dict[JobType, JobHandler] = {
|
|
52
|
+
JobType.MONITORING: MonitoringHandler(),
|
|
53
|
+
JobType.SYSTEM_MAINTENANCE: SystemMaintenanceHandler(),
|
|
54
|
+
JobType.CUSTOM: SubtaskHandler(),
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if router is not None:
|
|
58
|
+
from apsimo.task_queue.handlers.inference import (
|
|
59
|
+
InferenceHandler,
|
|
60
|
+
ThoughtOnlyInferenceHandler,
|
|
61
|
+
)
|
|
62
|
+
inference_handler = InferenceHandler(
|
|
63
|
+
router,
|
|
64
|
+
world_model_store=world_model_store,
|
|
65
|
+
contact_store=contact_store,
|
|
66
|
+
response_gate=response_gate,
|
|
67
|
+
gate_session_store=gate_session_store,
|
|
68
|
+
)
|
|
69
|
+
handlers[JobType.INFERENCE] = inference_handler
|
|
70
|
+
# ThoughtJobV1 shares only the router transport. A distinct strict
|
|
71
|
+
# handler makes it impossible for a registry mix-up to run a generic
|
|
72
|
+
# inference payload on the owner-private cognition lane.
|
|
73
|
+
handlers[JobType.THOUGHT] = ThoughtOnlyInferenceHandler(router)
|
|
74
|
+
|
|
75
|
+
return handlers
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Custom subtask handler for the Colony task queue.
|
|
2
|
+
|
|
3
|
+
Handles job_type=CUSTOM jobs dispatched by GoalQueueBridge.
|
|
4
|
+
For simple subtasks, spawns a lightweight Sonnet agent to execute the task
|
|
5
|
+
and returns the result as a job output dict.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
from datetime import datetime, timezone
|
|
13
|
+
from typing import Any, Dict
|
|
14
|
+
|
|
15
|
+
from apsimo.task_queue.models import Job
|
|
16
|
+
from apsimo.task_queue.worker import JobHandler
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
_SONNET_MODEL = "claude-sonnet-4-6"
|
|
21
|
+
_MAX_TOKENS = 1024
|
|
22
|
+
|
|
23
|
+
_SYSTEM_PROMPT = (
|
|
24
|
+
"You are an Apsimo subtask worker with NO tools: you cannot browse, run "
|
|
25
|
+
"commands, send messages, or touch external systems. Produce the work "
|
|
26
|
+
"product itself: the analysis, draft, plan, or answer the task asks for, "
|
|
27
|
+
"concise and self-contained. Never claim to have performed an external "
|
|
28
|
+
"action. If the task needs information or access you do not have, state "
|
|
29
|
+
"exactly what is missing in one line and deliver the best partial result "
|
|
30
|
+
"from what is given. Do not ask clarifying questions; make the most "
|
|
31
|
+
"reasonable assumption and note it."
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class SubtaskHandler(JobHandler):
|
|
36
|
+
"""Executes custom subtasks dispatched by GoalQueueBridge.
|
|
37
|
+
|
|
38
|
+
The job payload is expected to contain:
|
|
39
|
+
goal_id — parent goal identifier
|
|
40
|
+
subtask_id — subtask identifier
|
|
41
|
+
description — natural-language task description (preferred)
|
|
42
|
+
title / task / action / objective — fallback description keys
|
|
43
|
+
|
|
44
|
+
For each subtask the handler calls the Anthropic API with the Sonnet
|
|
45
|
+
model (restricted to text-only, no tools) and returns the model's
|
|
46
|
+
completion report as the job output.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(self, api_key: str | None = None) -> None:
|
|
50
|
+
self._api_key = api_key or os.environ.get("ANTHROPIC_API_KEY", "")
|
|
51
|
+
|
|
52
|
+
async def execute(self, job: Job) -> Dict[str, Any]:
|
|
53
|
+
goal_id = job.payload.get("goal_id", "")
|
|
54
|
+
subtask_id = job.payload.get("subtask_id", "")
|
|
55
|
+
task_description = job.payload.get("description", "") or _build_description(job.payload)
|
|
56
|
+
|
|
57
|
+
logger.info(
|
|
58
|
+
"SubtaskHandler: executing subtask %s for goal %s",
|
|
59
|
+
subtask_id,
|
|
60
|
+
goal_id,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
result = await self._run_agent(task_description, goal_id, subtask_id)
|
|
64
|
+
|
|
65
|
+
logger.info(
|
|
66
|
+
"SubtaskHandler: subtask %s finished (goal=%s, success=%s)",
|
|
67
|
+
subtask_id,
|
|
68
|
+
goal_id,
|
|
69
|
+
result.get("success"),
|
|
70
|
+
)
|
|
71
|
+
return result
|
|
72
|
+
|
|
73
|
+
async def _run_agent(
|
|
74
|
+
self,
|
|
75
|
+
task_description: str,
|
|
76
|
+
goal_id: str,
|
|
77
|
+
subtask_id: str,
|
|
78
|
+
) -> Dict[str, Any]:
|
|
79
|
+
"""Call a lightweight Sonnet agent to execute the task."""
|
|
80
|
+
if not self._api_key:
|
|
81
|
+
logger.warning(
|
|
82
|
+
"SubtaskHandler: ANTHROPIC_API_KEY not set — subtask %s skipped",
|
|
83
|
+
subtask_id,
|
|
84
|
+
)
|
|
85
|
+
return {
|
|
86
|
+
"success": False,
|
|
87
|
+
"status": "failed",
|
|
88
|
+
"action_plane": {"state": "failed"},
|
|
89
|
+
"error": "ANTHROPIC_API_KEY not configured",
|
|
90
|
+
"goal_id": goal_id,
|
|
91
|
+
"subtask_id": subtask_id,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
import anthropic
|
|
96
|
+
except ImportError:
|
|
97
|
+
logger.error("SubtaskHandler: anthropic package not installed")
|
|
98
|
+
return {
|
|
99
|
+
"success": False,
|
|
100
|
+
"status": "failed",
|
|
101
|
+
"action_plane": {"state": "failed"},
|
|
102
|
+
"error": "anthropic package not available",
|
|
103
|
+
"goal_id": goal_id,
|
|
104
|
+
"subtask_id": subtask_id,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# Try ModelRegistry utility role for model/credentials; fall back to hardcoded
|
|
108
|
+
_model = _SONNET_MODEL
|
|
109
|
+
_api_key = self._api_key
|
|
110
|
+
try:
|
|
111
|
+
from colony_cli.model_config import load_model_registry
|
|
112
|
+
_registry = load_model_registry()
|
|
113
|
+
_rt = _registry.resolve_runtime(role="utility")
|
|
114
|
+
if _rt.get("model"):
|
|
115
|
+
_model = _rt["model"]
|
|
116
|
+
if _rt.get("api_key"):
|
|
117
|
+
_api_key = _rt["api_key"]
|
|
118
|
+
except Exception:
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
client = anthropic.AsyncAnthropic(api_key=_api_key)
|
|
122
|
+
try:
|
|
123
|
+
message = await client.messages.create(
|
|
124
|
+
model=_model,
|
|
125
|
+
max_tokens=_MAX_TOKENS,
|
|
126
|
+
system=_SYSTEM_PROMPT,
|
|
127
|
+
messages=[
|
|
128
|
+
{
|
|
129
|
+
"role": "user",
|
|
130
|
+
"content": (
|
|
131
|
+
f"Goal ID: {goal_id}\n"
|
|
132
|
+
f"Subtask ID: {subtask_id}\n"
|
|
133
|
+
f"Task: {task_description}"
|
|
134
|
+
),
|
|
135
|
+
}
|
|
136
|
+
],
|
|
137
|
+
)
|
|
138
|
+
output_text = message.content[0].text if message.content else ""
|
|
139
|
+
return {
|
|
140
|
+
"success": True,
|
|
141
|
+
"status": "completed",
|
|
142
|
+
"summary": output_text,
|
|
143
|
+
"action_plane": {"state": "completed"},
|
|
144
|
+
"output": output_text,
|
|
145
|
+
"goal_id": goal_id,
|
|
146
|
+
"subtask_id": subtask_id,
|
|
147
|
+
"model": _model,
|
|
148
|
+
"completed_at": datetime.now(timezone.utc).isoformat(),
|
|
149
|
+
}
|
|
150
|
+
except Exception as exc:
|
|
151
|
+
logger.error(
|
|
152
|
+
"SubtaskHandler: agent call failed for subtask %s: %s",
|
|
153
|
+
subtask_id,
|
|
154
|
+
exc,
|
|
155
|
+
)
|
|
156
|
+
return {
|
|
157
|
+
"success": False,
|
|
158
|
+
"status": "failed",
|
|
159
|
+
"action_plane": {"state": "failed"},
|
|
160
|
+
"error": str(exc),
|
|
161
|
+
"goal_id": goal_id,
|
|
162
|
+
"subtask_id": subtask_id,
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _build_description(payload: Dict[str, Any]) -> str:
|
|
167
|
+
"""Construct a task description from payload when 'description' is absent."""
|
|
168
|
+
for key in ("title", "task", "action", "objective"):
|
|
169
|
+
if payload.get(key):
|
|
170
|
+
return str(payload[key])
|
|
171
|
+
skip = {"goal_id", "subtask_id"}
|
|
172
|
+
parts = [f"{k}: {v}" for k, v in payload.items() if k not in skip and v]
|
|
173
|
+
return " — ".join(parts) or "Execute subtask"
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""SystemMaintenanceHandler — safe allow-listed maintenance jobs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import shutil
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Dict, List
|
|
10
|
+
|
|
11
|
+
from apsimo.task_queue.handlers.base import JobHandler, Job, run_subprocess
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SystemMaintenanceHandler(JobHandler):
|
|
17
|
+
"""Run safe system maintenance commands (allow-listed only).
|
|
18
|
+
|
|
19
|
+
Job payload:
|
|
20
|
+
action (str): One of: "disk_cleanup", "log_rotate", "db_vacuum".
|
|
21
|
+
target_path (str, optional): Path scoped to the colony data directory.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
{"actions_taken": list[str], "errors": list[str]}
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
ALLOWED_ACTIONS = {"disk_cleanup", "log_rotate", "db_vacuum"}
|
|
28
|
+
|
|
29
|
+
def __init__(self, allowed_roots: List[Path] | None = None) -> None:
|
|
30
|
+
if allowed_roots is None:
|
|
31
|
+
configured = os.environ.get("COLONY_MAINTENANCE_ROOTS", "")
|
|
32
|
+
roots = [
|
|
33
|
+
Path(item.strip()).expanduser()
|
|
34
|
+
for item in configured.split(",") if item.strip()
|
|
35
|
+
]
|
|
36
|
+
if not roots:
|
|
37
|
+
roots = [Path(
|
|
38
|
+
os.environ.get("COLONY_STATE_DIR")
|
|
39
|
+
or os.environ.get("COLONY_HOME", "~/.colony")
|
|
40
|
+
).expanduser()]
|
|
41
|
+
else:
|
|
42
|
+
roots = list(allowed_roots)
|
|
43
|
+
self._allowed_roots = tuple(root.resolve() for root in roots)
|
|
44
|
+
|
|
45
|
+
def _target(self, value: str | None, *, default_name: str) -> Path:
|
|
46
|
+
root = self._allowed_roots[0]
|
|
47
|
+
raw = Path(value).expanduser() if value else root / default_name
|
|
48
|
+
if not raw.is_absolute():
|
|
49
|
+
raw = root / raw
|
|
50
|
+
resolved = raw.resolve(strict=False)
|
|
51
|
+
if not any(resolved.is_relative_to(base) for base in self._allowed_roots):
|
|
52
|
+
raise ValueError(
|
|
53
|
+
"maintenance target must stay inside COLONY_MAINTENANCE_ROOTS"
|
|
54
|
+
)
|
|
55
|
+
if resolved in self._allowed_roots:
|
|
56
|
+
raise ValueError("maintenance target cannot be a configured root")
|
|
57
|
+
return resolved
|
|
58
|
+
|
|
59
|
+
async def execute(self, job: Job) -> Dict[str, Any]:
|
|
60
|
+
action = job.payload.get("action")
|
|
61
|
+
if action not in self.ALLOWED_ACTIONS:
|
|
62
|
+
raise ValueError(
|
|
63
|
+
f"Disallowed maintenance action: {action!r}. "
|
|
64
|
+
f"Allowed: {sorted(self.ALLOWED_ACTIONS)}"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
target_path: str | None = job.payload.get("target_path")
|
|
68
|
+
actions_taken: List[str] = []
|
|
69
|
+
errors: List[str] = []
|
|
70
|
+
|
|
71
|
+
if action == "db_vacuum":
|
|
72
|
+
if not target_path:
|
|
73
|
+
errors.append("db_vacuum requires 'target_path' in payload")
|
|
74
|
+
else:
|
|
75
|
+
try:
|
|
76
|
+
path = self._target(target_path, default_name="state.db")
|
|
77
|
+
import aiosqlite
|
|
78
|
+
async with aiosqlite.connect(path) as db:
|
|
79
|
+
await db.execute("VACUUM")
|
|
80
|
+
actions_taken.append(f"vacuumed {path}")
|
|
81
|
+
logger.info("SystemMaintenance: vacuumed %s", path)
|
|
82
|
+
except ImportError:
|
|
83
|
+
errors.append(
|
|
84
|
+
"db_vacuum requires 'aiosqlite'. Install with: pip install aiosqlite"
|
|
85
|
+
)
|
|
86
|
+
except Exception as exc:
|
|
87
|
+
errors.append(f"db_vacuum failed: {exc}")
|
|
88
|
+
logger.warning(
|
|
89
|
+
"SystemMaintenance: db_vacuum error on %s: %s",
|
|
90
|
+
target_path, exc,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
elif action == "disk_cleanup":
|
|
94
|
+
path = self._target(target_path, default_name="tmp")
|
|
95
|
+
try:
|
|
96
|
+
cleaned = 0
|
|
97
|
+
if path.is_dir():
|
|
98
|
+
for item in path.iterdir():
|
|
99
|
+
try:
|
|
100
|
+
if item.is_symlink() or item.is_file():
|
|
101
|
+
item.unlink()
|
|
102
|
+
cleaned += 1
|
|
103
|
+
elif item.is_dir():
|
|
104
|
+
shutil.rmtree(item)
|
|
105
|
+
cleaned += 1
|
|
106
|
+
except Exception as exc:
|
|
107
|
+
errors.append(f"Could not remove {item}: {exc}")
|
|
108
|
+
actions_taken.append(f"disk_cleanup: removed {cleaned} items from {path}")
|
|
109
|
+
logger.info("SystemMaintenance: disk_cleanup removed %d items from %s", cleaned, path)
|
|
110
|
+
except Exception as exc:
|
|
111
|
+
errors.append(f"disk_cleanup failed: {exc}")
|
|
112
|
+
logger.warning("SystemMaintenance: disk_cleanup error: %s", exc)
|
|
113
|
+
|
|
114
|
+
elif action == "log_rotate":
|
|
115
|
+
path = self._target(target_path, default_name="logs")
|
|
116
|
+
try:
|
|
117
|
+
rotated = 0
|
|
118
|
+
if path.is_dir():
|
|
119
|
+
for log_file in path.glob("*.log"):
|
|
120
|
+
rotated_name = log_file.with_suffix(".log.bak")
|
|
121
|
+
try:
|
|
122
|
+
log_file.rename(rotated_name)
|
|
123
|
+
rotated += 1
|
|
124
|
+
except Exception as exc:
|
|
125
|
+
errors.append(f"Could not rotate {log_file}: {exc}")
|
|
126
|
+
actions_taken.append(f"log_rotate: rotated {rotated} log files in {path}")
|
|
127
|
+
logger.info("SystemMaintenance: log_rotate rotated %d files in %s", rotated, path)
|
|
128
|
+
except Exception as exc:
|
|
129
|
+
errors.append(f"log_rotate failed: {exc}")
|
|
130
|
+
logger.warning("SystemMaintenance: log_rotate error: %s", exc)
|
|
131
|
+
|
|
132
|
+
succeeded = not errors
|
|
133
|
+
return {
|
|
134
|
+
"status": "completed" if succeeded else "failed",
|
|
135
|
+
"summary": (
|
|
136
|
+
"; ".join(actions_taken)
|
|
137
|
+
if succeeded else "; ".join(errors)
|
|
138
|
+
),
|
|
139
|
+
"operations": [
|
|
140
|
+
"delete" if action == "disk_cleanup" else "write"
|
|
141
|
+
],
|
|
142
|
+
"actions_taken": actions_taken,
|
|
143
|
+
"errors": errors,
|
|
144
|
+
"action_plane": {
|
|
145
|
+
"state": "completed" if succeeded else "failed",
|
|
146
|
+
},
|
|
147
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Mesh layer integration for the Colony distributed task queue.
|
|
2
|
+
|
|
3
|
+
Bridges SWIM node-dead events and mesh role-change events into
|
|
4
|
+
queue scheduler actions. Wires the task queue into the existing
|
|
5
|
+
colony/mesh/ layer without modifying it.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from typing import Any, Optional
|
|
12
|
+
|
|
13
|
+
from apsimo.task_queue.queue_manager import QueueManager
|
|
14
|
+
from apsimo.task_queue.scheduler import Scheduler
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
_BACKGROUND_TASKS: set = set()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _retain(task) -> None:
|
|
23
|
+
"""Keep a strong reference to a fire-and-forget task until it finishes.
|
|
24
|
+
|
|
25
|
+
The event loop holds only a weak reference; an unreferenced task can be
|
|
26
|
+
garbage-collected (cancelled) mid-flight — for the Sovereign scheduler
|
|
27
|
+
loop that means job scheduling silently halts with no error.
|
|
28
|
+
"""
|
|
29
|
+
_BACKGROUND_TASKS.add(task)
|
|
30
|
+
task.add_done_callback(_BACKGROUND_TASKS.discard)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class QueueMeshEventHandler:
|
|
34
|
+
"""Handles mesh-layer events that affect the task queue.
|
|
35
|
+
|
|
36
|
+
Subscribe this handler to the Colony event bus for MeshEvent types.
|
|
37
|
+
|
|
38
|
+
Responsibilities:
|
|
39
|
+
- On NODE_DEAD: immediately abandon all jobs claimed by the dead node.
|
|
40
|
+
- On ROLE_CHANGE to SOVEREIGN (self): assume the scheduler role.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
queue: QueueManager,
|
|
46
|
+
scheduler: Scheduler,
|
|
47
|
+
own_node_id: str,
|
|
48
|
+
event_bus: Optional[Any] = None,
|
|
49
|
+
) -> None:
|
|
50
|
+
self._queue = queue
|
|
51
|
+
self._scheduler = scheduler
|
|
52
|
+
self._own_node_id = own_node_id
|
|
53
|
+
self._event_bus = event_bus
|
|
54
|
+
|
|
55
|
+
async def on_node_dead(self, node_id: str) -> None:
|
|
56
|
+
"""Immediately abandon all jobs claimed by the dead node.
|
|
57
|
+
|
|
58
|
+
Called when SWIM emits a NODE_DEAD event. Does not wait for the
|
|
59
|
+
heartbeat timeout — fast-path abandonment.
|
|
60
|
+
"""
|
|
61
|
+
abandoned = await self._queue.abandon_jobs_for_node(node_id)
|
|
62
|
+
if abandoned:
|
|
63
|
+
logger.info(
|
|
64
|
+
"QueueMeshEventHandler: abandoned %d jobs from dead node %s: %s",
|
|
65
|
+
len(abandoned), node_id, abandoned,
|
|
66
|
+
)
|
|
67
|
+
# Trigger immediate redistribution
|
|
68
|
+
await self._queue.requeue_retryable_jobs(
|
|
69
|
+
__import__("datetime").datetime.now(__import__("datetime").timezone.utc)
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
async def on_role_change(self, new_role: str, node_id: str) -> None:
|
|
73
|
+
"""Handle mesh role-change events.
|
|
74
|
+
|
|
75
|
+
If this node becomes SOVEREIGN (Queen), start the scheduler loop.
|
|
76
|
+
"""
|
|
77
|
+
if new_role == "sovereign" and node_id == self._own_node_id:
|
|
78
|
+
logger.info(
|
|
79
|
+
"QueueMeshEventHandler: this node (%s) became Sovereign — "
|
|
80
|
+
"starting scheduler", self._own_node_id,
|
|
81
|
+
)
|
|
82
|
+
import asyncio
|
|
83
|
+
_retain(asyncio.create_task(self._scheduler.run()))
|
|
84
|
+
|
|
85
|
+
def handle_mesh_event(self, event: Any) -> None:
|
|
86
|
+
"""Synchronous event bus callback. Dispatches to async handlers."""
|
|
87
|
+
import asyncio
|
|
88
|
+
node_id = getattr(event, "node_id", "")
|
|
89
|
+
event_type = getattr(event, "event_type", "")
|
|
90
|
+
|
|
91
|
+
if event_type == "node_dead" or event_type == "NODE_DEAD":
|
|
92
|
+
_retain(asyncio.create_task(self.on_node_dead(node_id)))
|
|
93
|
+
elif event_type in {"role_changed", "ROLE_CHANGE"}:
|
|
94
|
+
new_role = ""
|
|
95
|
+
new_role_attr = getattr(event, "new_role", None)
|
|
96
|
+
if new_role_attr is not None:
|
|
97
|
+
new_role = str(new_role_attr.value if hasattr(new_role_attr, "value") else new_role_attr)
|
|
98
|
+
_retain(asyncio.create_task(self.on_role_change(new_role, node_id)))
|
|
99
|
+
|
|
100
|
+
def subscribe_to_event_bus(self) -> None:
|
|
101
|
+
"""Register this handler on the Colony event bus if one is configured."""
|
|
102
|
+
if self._event_bus is None:
|
|
103
|
+
return
|
|
104
|
+
from apsimo.events.types import MeshEvent
|
|
105
|
+
self._event_bus.subscribe(
|
|
106
|
+
handler=self.handle_mesh_event,
|
|
107
|
+
event_types=[MeshEvent],
|
|
108
|
+
)
|
|
109
|
+
logger.info(
|
|
110
|
+
"QueueMeshEventHandler: subscribed to MeshEvent on event bus"
|
|
111
|
+
)
|