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,172 @@
|
|
|
1
|
+
"""SkillStore -- SQLite persistence for procedure memory + strategy notes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import sqlite3
|
|
8
|
+
import threading
|
|
9
|
+
import time
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
from apsimo.skills_memory.models import Skill, signature_overlap
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
_DEFAULT_MAX = 200
|
|
17
|
+
_NOTE_CAP = 600
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def skills_enabled() -> bool:
|
|
21
|
+
return os.environ.get("COLONY_SKILLS_ENABLED", "true").strip().lower() != "false"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def skills_distill_mode() -> str:
|
|
25
|
+
from apsimo.util.autonomy_preset import resolve
|
|
26
|
+
return resolve("COLONY_SKILLS_DISTILL",
|
|
27
|
+
("off", "shadow", "live"), "shadow")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def skills_max() -> int:
|
|
31
|
+
try:
|
|
32
|
+
return max(10, int(os.environ.get("COLONY_SKILLS_MAX", str(_DEFAULT_MAX))))
|
|
33
|
+
except (TypeError, ValueError):
|
|
34
|
+
return _DEFAULT_MAX
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class SkillStore:
|
|
38
|
+
def __init__(self, db_path: Optional[str] = None) -> None:
|
|
39
|
+
self._lock = threading.RLock()
|
|
40
|
+
self._conn = sqlite3.connect(str(db_path) if db_path else ":memory:",
|
|
41
|
+
check_same_thread=False)
|
|
42
|
+
self._conn.row_factory = sqlite3.Row
|
|
43
|
+
with self._lock:
|
|
44
|
+
self._conn.execute(
|
|
45
|
+
"""CREATE TABLE IF NOT EXISTS skills (
|
|
46
|
+
id TEXT PRIMARY KEY, title TEXT NOT NULL, situation TEXT,
|
|
47
|
+
situation_signature TEXT, steps TEXT, gotchas TEXT,
|
|
48
|
+
domain TEXT, source_ref TEXT,
|
|
49
|
+
uses INTEGER DEFAULT 0, wins INTEGER DEFAULT 0,
|
|
50
|
+
losses INTEGER DEFAULT 0, confidence REAL DEFAULT 0.6,
|
|
51
|
+
created_at REAL, last_used_at REAL
|
|
52
|
+
)""")
|
|
53
|
+
self._conn.execute(
|
|
54
|
+
"""CREATE TABLE IF NOT EXISTS strategy_notes (
|
|
55
|
+
domain TEXT PRIMARY KEY, note TEXT, updated_at REAL
|
|
56
|
+
)""")
|
|
57
|
+
self._conn.commit()
|
|
58
|
+
|
|
59
|
+
# -- skills ----------------------------------------------------------
|
|
60
|
+
def add(self, skill: Skill) -> Skill:
|
|
61
|
+
row = skill.to_row()
|
|
62
|
+
with self._lock:
|
|
63
|
+
cols = ", ".join(row); ph = ", ".join(["?"] * len(row))
|
|
64
|
+
self._conn.execute(
|
|
65
|
+
f"INSERT OR REPLACE INTO skills ({cols}) VALUES ({ph})",
|
|
66
|
+
list(row.values()))
|
|
67
|
+
self._conn.commit()
|
|
68
|
+
return skill
|
|
69
|
+
|
|
70
|
+
def get(self, skill_id: str) -> Optional[Skill]:
|
|
71
|
+
with self._lock:
|
|
72
|
+
r = self._conn.execute(
|
|
73
|
+
"SELECT * FROM skills WHERE id=?", (skill_id,)).fetchone()
|
|
74
|
+
return Skill.from_row(dict(r)) if r else None
|
|
75
|
+
|
|
76
|
+
def list(self, domain: Optional[str] = None, limit: int = 100) -> List[Skill]:
|
|
77
|
+
q = "SELECT * FROM skills"
|
|
78
|
+
params: List[Any] = []
|
|
79
|
+
if domain:
|
|
80
|
+
q += " WHERE domain=?"; params.append(domain)
|
|
81
|
+
q += " ORDER BY last_used_at DESC, created_at DESC LIMIT ?"
|
|
82
|
+
params.append(limit)
|
|
83
|
+
with self._lock:
|
|
84
|
+
rows = self._conn.execute(q, params).fetchall()
|
|
85
|
+
return [Skill.from_row(dict(r)) for r in rows]
|
|
86
|
+
|
|
87
|
+
def count(self) -> int:
|
|
88
|
+
with self._lock:
|
|
89
|
+
r = self._conn.execute("SELECT COUNT(*) AS n FROM skills").fetchone()
|
|
90
|
+
return int(r["n"])
|
|
91
|
+
|
|
92
|
+
def find_similar(self, signature: str, threshold: float = 0.8) -> Optional[Skill]:
|
|
93
|
+
"""Existing skill whose signature overlaps `signature` above threshold."""
|
|
94
|
+
best, best_ov = None, 0.0
|
|
95
|
+
for s in self.list(limit=100000):
|
|
96
|
+
ov = signature_overlap(signature, s.situation_signature)
|
|
97
|
+
if ov > best_ov:
|
|
98
|
+
best, best_ov = s, ov
|
|
99
|
+
return best if best is not None and best_ov > threshold else None
|
|
100
|
+
|
|
101
|
+
def bump_use(self, skill_id: str) -> None:
|
|
102
|
+
with self._lock:
|
|
103
|
+
self._conn.execute(
|
|
104
|
+
"UPDATE skills SET uses=uses+1, last_used_at=? WHERE id=?",
|
|
105
|
+
(time.time(), skill_id))
|
|
106
|
+
self._conn.commit()
|
|
107
|
+
|
|
108
|
+
def record_outcome(self, skill_id: str, win: bool) -> None:
|
|
109
|
+
col = "wins" if win else "losses"
|
|
110
|
+
with self._lock:
|
|
111
|
+
self._conn.execute(
|
|
112
|
+
f"UPDATE skills SET {col}={col}+1 WHERE id=?", (skill_id,))
|
|
113
|
+
self._conn.commit()
|
|
114
|
+
|
|
115
|
+
def evict_to_cap(self, cap: Optional[int] = None) -> int:
|
|
116
|
+
"""Drop lowest-scoring skills until at most `cap` remain."""
|
|
117
|
+
cap = cap if cap is not None else skills_max()
|
|
118
|
+
skills = self.list(limit=100000)
|
|
119
|
+
excess = len(skills) - cap
|
|
120
|
+
if excess <= 0:
|
|
121
|
+
return 0
|
|
122
|
+
now = time.time()
|
|
123
|
+
victims = sorted(skills, key=lambda s: s.score(now))[:excess]
|
|
124
|
+
with self._lock:
|
|
125
|
+
for v in victims:
|
|
126
|
+
self._conn.execute("DELETE FROM skills WHERE id=?", (v.id,))
|
|
127
|
+
self._conn.commit()
|
|
128
|
+
logger.info("skills_memory: evicted %d skill(s) to cap %d", excess, cap)
|
|
129
|
+
return excess
|
|
130
|
+
|
|
131
|
+
# -- per-domain strategy notes (failure post-mortems) -----------------
|
|
132
|
+
def record_failure_note(self, domain: str, note: str) -> None:
|
|
133
|
+
"""Append/refresh the short strategy note for a domain (capped)."""
|
|
134
|
+
domain = (domain or "unknown").strip().lower()
|
|
135
|
+
note = (note or "").strip()
|
|
136
|
+
if not note:
|
|
137
|
+
return
|
|
138
|
+
with self._lock:
|
|
139
|
+
row = self._conn.execute(
|
|
140
|
+
"SELECT note FROM strategy_notes WHERE domain=?", (domain,)).fetchone()
|
|
141
|
+
existing = (row["note"] if row else "") or ""
|
|
142
|
+
if note in existing:
|
|
143
|
+
merged = existing
|
|
144
|
+
else:
|
|
145
|
+
merged = (existing + ("\n" if existing else "") + f"- {note}")
|
|
146
|
+
# keep the newest lines within the cap
|
|
147
|
+
if len(merged) > _NOTE_CAP:
|
|
148
|
+
lines = merged.splitlines()
|
|
149
|
+
while lines and len("\n".join(lines)) > _NOTE_CAP:
|
|
150
|
+
lines.pop(0)
|
|
151
|
+
merged = "\n".join(lines)
|
|
152
|
+
self._conn.execute(
|
|
153
|
+
"""INSERT INTO strategy_notes (domain, note, updated_at)
|
|
154
|
+
VALUES (?, ?, ?)
|
|
155
|
+
ON CONFLICT(domain) DO UPDATE SET note=?, updated_at=?""",
|
|
156
|
+
(domain, merged, time.time(), merged, time.time()))
|
|
157
|
+
self._conn.commit()
|
|
158
|
+
|
|
159
|
+
def get_note(self, domain: str) -> str:
|
|
160
|
+
domain = (domain or "").strip().lower()
|
|
161
|
+
with self._lock:
|
|
162
|
+
row = self._conn.execute(
|
|
163
|
+
"SELECT note FROM strategy_notes WHERE domain=?", (domain,)).fetchone()
|
|
164
|
+
return (row["note"] if row else "") or ""
|
|
165
|
+
|
|
166
|
+
def snapshot(self) -> Dict[str, Any]:
|
|
167
|
+
return {
|
|
168
|
+
"count": self.count(),
|
|
169
|
+
"cap": skills_max(),
|
|
170
|
+
"distill_mode": skills_distill_mode(),
|
|
171
|
+
"skills": [s.to_row() for s in self.list(limit=200)],
|
|
172
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Surprise-accumulation consumer: close the loop into the workspace.
|
|
2
|
+
|
|
3
|
+
The condition worker emits ``surprise.accumulation`` when unresolved
|
|
4
|
+
surprises pile up, but nothing ever consumed it — the signal died on the
|
|
5
|
+
WebSocket unless a client happened to be watching. This consumer turns the
|
|
6
|
+
event into a workspace concern so accumulating surprises land on the
|
|
7
|
+
agent's mind (one strengthening concern, keyed stably, never a pile-up).
|
|
8
|
+
|
|
9
|
+
Clean no-op when the workspace is disabled or not wired: the event still
|
|
10
|
+
broadcasts and journals exactly as before.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
from typing import Any, Dict
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
_DEDUP_KEY = "surprise:accumulation"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def handle_surprise_accumulation(event: Dict[str, Any]) -> bool:
|
|
24
|
+
"""Raise/strengthen the accumulation concern. Returns True when a
|
|
25
|
+
concern was bumped, False on the (clean) no-op paths."""
|
|
26
|
+
try:
|
|
27
|
+
from apsimo.self_model.workspace import workspace_enabled
|
|
28
|
+
if not workspace_enabled():
|
|
29
|
+
return False
|
|
30
|
+
from apsimo.api.routers.host import _workspace
|
|
31
|
+
if _workspace is None:
|
|
32
|
+
return False
|
|
33
|
+
payload = event.get("payload") or {}
|
|
34
|
+
try:
|
|
35
|
+
count = int(payload.get("unresolved_count") or 0)
|
|
36
|
+
except (TypeError, ValueError):
|
|
37
|
+
count = 0
|
|
38
|
+
_workspace.bump(
|
|
39
|
+
kind="anomaly",
|
|
40
|
+
summary=(f"{count} unresolved surprise(s) accumulating — "
|
|
41
|
+
"reality keeps diverging from expectations; review "
|
|
42
|
+
"and resolve them"),
|
|
43
|
+
dedup_key=_DEDUP_KEY,
|
|
44
|
+
salience=min(0.9, 0.4 + 0.05 * count),
|
|
45
|
+
sources=["surprise-store"])
|
|
46
|
+
return True
|
|
47
|
+
except Exception:
|
|
48
|
+
logger.debug("surprise.accumulation -> workspace failed",
|
|
49
|
+
exc_info=True)
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def register() -> None:
|
|
54
|
+
"""Subscribe the consumer. Enablement is checked at EVENT time (not
|
|
55
|
+
registration time), so a workspace flipped on later just starts working."""
|
|
56
|
+
from apsimo.events.broadcaster import subscribe
|
|
57
|
+
subscribe("surprise.accumulation", handle_surprise_accumulation)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Surprise Scorer — compute surprise scores for observations.
|
|
2
|
+
|
|
3
|
+
Scoring logic:
|
|
4
|
+
- No matching pattern: surprise = 0.7 (moderately surprising)
|
|
5
|
+
- Pattern violated: surprise = 0.5 + (pattern_confidence * 0.5)
|
|
6
|
+
- Low-frequency pattern match: surprise = 0.2 (rare but known)
|
|
7
|
+
- High-frequency pattern match: surprise = 0.0 (expected)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
from typing import Any, Dict, Optional
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def compute_surprise(
|
|
19
|
+
observation: str,
|
|
20
|
+
*,
|
|
21
|
+
pattern_store: Any = None,
|
|
22
|
+
observation_type: str = "",
|
|
23
|
+
observation_data: Optional[Dict[str, Any]] = None,
|
|
24
|
+
) -> Dict[str, Any]:
|
|
25
|
+
"""Compute a surprise score for an observation.
|
|
26
|
+
|
|
27
|
+
Returns dict with:
|
|
28
|
+
- surprise_score: float (0.0 to 1.0)
|
|
29
|
+
- expected: what was expected (if a pattern was found)
|
|
30
|
+
- pattern_id: the matched/violated pattern ID (if any)
|
|
31
|
+
- reasoning: human-readable explanation
|
|
32
|
+
"""
|
|
33
|
+
if pattern_store is None:
|
|
34
|
+
return {
|
|
35
|
+
"surprise_score": 0.5,
|
|
36
|
+
"expected": None,
|
|
37
|
+
"pattern_id": None,
|
|
38
|
+
"reasoning": "No pattern store available, defaulting to moderate surprise",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# Look for matching patterns by searching descriptions.
|
|
42
|
+
try:
|
|
43
|
+
all_patterns = pattern_store.list_patterns(active_only=True, limit=100)
|
|
44
|
+
except Exception:
|
|
45
|
+
return {
|
|
46
|
+
"surprise_score": 0.5,
|
|
47
|
+
"expected": None,
|
|
48
|
+
"pattern_id": None,
|
|
49
|
+
"reasoning": "Could not query pattern store",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Find patterns that mention entities from the observation.
|
|
53
|
+
matching = []
|
|
54
|
+
obs_lower = observation.lower()
|
|
55
|
+
obs_words = set(obs_lower.split())
|
|
56
|
+
|
|
57
|
+
for pattern in all_patterns.get("patterns", []):
|
|
58
|
+
desc_lower = pattern.get("description", "").lower()
|
|
59
|
+
# Check for word overlap.
|
|
60
|
+
desc_words = set(desc_lower.split())
|
|
61
|
+
overlap = obs_words & desc_words
|
|
62
|
+
if overlap or observation_type == pattern.get("pattern_type", ""):
|
|
63
|
+
matching.append(pattern)
|
|
64
|
+
|
|
65
|
+
if not matching:
|
|
66
|
+
return {
|
|
67
|
+
"surprise_score": 0.7,
|
|
68
|
+
"expected": None,
|
|
69
|
+
"pattern_id": None,
|
|
70
|
+
"reasoning": "No matching pattern found for this observation",
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# Check if the observation fits the pattern or violates it.
|
|
74
|
+
best_pattern = max(matching, key=lambda p: p.get("frequency", 0) * p.get("confidence", 0.5))
|
|
75
|
+
freq = best_pattern.get("frequency", 1)
|
|
76
|
+
conf = best_pattern.get("confidence", 0.5)
|
|
77
|
+
|
|
78
|
+
if freq >= 5:
|
|
79
|
+
# High-frequency pattern match — expected.
|
|
80
|
+
return {
|
|
81
|
+
"surprise_score": 0.0,
|
|
82
|
+
"expected": best_pattern["description"],
|
|
83
|
+
"pattern_id": best_pattern["id"],
|
|
84
|
+
"reasoning": f"Matches high-frequency pattern (seen {freq} times)",
|
|
85
|
+
}
|
|
86
|
+
elif freq >= 2:
|
|
87
|
+
# Low-frequency pattern match — rare but known.
|
|
88
|
+
return {
|
|
89
|
+
"surprise_score": 0.2,
|
|
90
|
+
"expected": best_pattern["description"],
|
|
91
|
+
"pattern_id": best_pattern["id"],
|
|
92
|
+
"reasoning": f"Matches low-frequency pattern (seen {freq} times)",
|
|
93
|
+
}
|
|
94
|
+
else:
|
|
95
|
+
# Pattern seen only once — could be a violation.
|
|
96
|
+
score = 0.5 + (conf * 0.5)
|
|
97
|
+
return {
|
|
98
|
+
"surprise_score": round(score, 4),
|
|
99
|
+
"expected": best_pattern["description"],
|
|
100
|
+
"pattern_id": best_pattern["id"],
|
|
101
|
+
"reasoning": f"Pattern seen only once (confidence {conf:.1f}), possible violation",
|
|
102
|
+
}
|
apsimo/surprise/store.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""Surprise Store — record and track unexpected observations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import sqlite3
|
|
8
|
+
import uuid
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SurpriseStore:
|
|
16
|
+
"""SQLite-backed surprise observation store."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, db_path: str) -> None:
|
|
19
|
+
self._db_path = db_path
|
|
20
|
+
self._conn = sqlite3.connect(db_path)
|
|
21
|
+
self._conn.row_factory = sqlite3.Row
|
|
22
|
+
self._conn.execute("PRAGMA journal_mode=WAL")
|
|
23
|
+
self._conn.execute("PRAGMA foreign_keys=ON")
|
|
24
|
+
self._create_tables()
|
|
25
|
+
|
|
26
|
+
def _create_tables(self) -> None:
|
|
27
|
+
self._conn.executescript("""
|
|
28
|
+
CREATE TABLE IF NOT EXISTS surprises (
|
|
29
|
+
id TEXT PRIMARY KEY,
|
|
30
|
+
observation TEXT NOT NULL,
|
|
31
|
+
expected TEXT,
|
|
32
|
+
surprise_score REAL NOT NULL,
|
|
33
|
+
pattern_id TEXT,
|
|
34
|
+
context TEXT,
|
|
35
|
+
timestamp TEXT NOT NULL,
|
|
36
|
+
resolved INTEGER NOT NULL DEFAULT 0,
|
|
37
|
+
resolution TEXT
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
CREATE INDEX IF NOT EXISTS idx_surprises_score
|
|
41
|
+
ON surprises(surprise_score DESC);
|
|
42
|
+
CREATE INDEX IF NOT EXISTS idx_surprises_timestamp
|
|
43
|
+
ON surprises(timestamp);
|
|
44
|
+
CREATE INDEX IF NOT EXISTS idx_surprises_resolved
|
|
45
|
+
ON surprises(resolved);
|
|
46
|
+
""")
|
|
47
|
+
self._conn.commit()
|
|
48
|
+
|
|
49
|
+
def create_surprise(
|
|
50
|
+
self,
|
|
51
|
+
*,
|
|
52
|
+
observation: str,
|
|
53
|
+
expected: Optional[str] = None,
|
|
54
|
+
surprise_score: float = 0.5,
|
|
55
|
+
pattern_id: Optional[str] = None,
|
|
56
|
+
context: Optional[Dict[str, Any]] = None,
|
|
57
|
+
) -> Dict[str, Any]:
|
|
58
|
+
"""Record a surprise observation.
|
|
59
|
+
|
|
60
|
+
Returns the created surprise dict.
|
|
61
|
+
"""
|
|
62
|
+
surprise_score = max(0.0, min(1.0, surprise_score))
|
|
63
|
+
|
|
64
|
+
surprise_id = str(uuid.uuid4())
|
|
65
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
66
|
+
ctx_json = json.dumps(context) if context else None
|
|
67
|
+
|
|
68
|
+
self._conn.execute(
|
|
69
|
+
"""INSERT INTO surprises (id, observation, expected, surprise_score,
|
|
70
|
+
pattern_id, context, timestamp, resolved, resolution)
|
|
71
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 0, NULL)""",
|
|
72
|
+
(surprise_id, observation, expected, surprise_score,
|
|
73
|
+
pattern_id, ctx_json, now),
|
|
74
|
+
)
|
|
75
|
+
self._conn.commit()
|
|
76
|
+
|
|
77
|
+
result = self.get_surprise(surprise_id)
|
|
78
|
+
assert result is not None
|
|
79
|
+
return result
|
|
80
|
+
|
|
81
|
+
def get_surprise(self, surprise_id: str) -> Optional[Dict[str, Any]]:
|
|
82
|
+
row = self._conn.execute(
|
|
83
|
+
"SELECT * FROM surprises WHERE id = ?", (surprise_id,)
|
|
84
|
+
).fetchone()
|
|
85
|
+
if row is None:
|
|
86
|
+
return None
|
|
87
|
+
d = dict(row)
|
|
88
|
+
d["resolved"] = bool(d["resolved"])
|
|
89
|
+
if d.get("context"):
|
|
90
|
+
try:
|
|
91
|
+
d["context"] = json.loads(d["context"])
|
|
92
|
+
except (json.JSONDecodeError, TypeError):
|
|
93
|
+
pass
|
|
94
|
+
return d
|
|
95
|
+
|
|
96
|
+
def list_surprises(
|
|
97
|
+
self,
|
|
98
|
+
*,
|
|
99
|
+
min_score: float = 0.0,
|
|
100
|
+
resolved: Optional[bool] = None,
|
|
101
|
+
limit: int = 50,
|
|
102
|
+
offset: int = 0,
|
|
103
|
+
) -> Dict[str, Any]:
|
|
104
|
+
"""List surprises with optional filters, sorted by score descending."""
|
|
105
|
+
clauses: List[str] = []
|
|
106
|
+
params: List[Any] = []
|
|
107
|
+
|
|
108
|
+
if min_score > 0:
|
|
109
|
+
clauses.append("surprise_score >= ?")
|
|
110
|
+
params.append(min_score)
|
|
111
|
+
if resolved is not None:
|
|
112
|
+
clauses.append("resolved = ?")
|
|
113
|
+
params.append(1 if resolved else 0)
|
|
114
|
+
|
|
115
|
+
where = f" WHERE {' AND '.join(clauses)}" if clauses else ""
|
|
116
|
+
|
|
117
|
+
total_row = self._conn.execute(
|
|
118
|
+
f"SELECT COUNT(*) as cnt FROM surprises{where}", params
|
|
119
|
+
).fetchone()
|
|
120
|
+
total = total_row["cnt"] if total_row else 0
|
|
121
|
+
|
|
122
|
+
rows = self._conn.execute(
|
|
123
|
+
f"SELECT * FROM surprises{where} ORDER BY surprise_score DESC, timestamp DESC LIMIT ? OFFSET ?",
|
|
124
|
+
params + [limit, offset],
|
|
125
|
+
).fetchall()
|
|
126
|
+
|
|
127
|
+
surprises = []
|
|
128
|
+
for row in rows:
|
|
129
|
+
d = dict(row)
|
|
130
|
+
d["resolved"] = bool(d["resolved"])
|
|
131
|
+
if d.get("context"):
|
|
132
|
+
try:
|
|
133
|
+
d["context"] = json.loads(d["context"])
|
|
134
|
+
except (json.JSONDecodeError, TypeError):
|
|
135
|
+
pass
|
|
136
|
+
surprises.append(d)
|
|
137
|
+
|
|
138
|
+
return {"surprises": surprises, "total": total, "limit": limit, "offset": offset}
|
|
139
|
+
|
|
140
|
+
def get_unresolved(self, min_score: float = 0.5, limit: int = 10) -> List[Dict[str, Any]]:
|
|
141
|
+
"""Get unresolved high-score surprises."""
|
|
142
|
+
result = self.list_surprises(min_score=min_score, resolved=False, limit=limit)
|
|
143
|
+
return result["surprises"]
|
|
144
|
+
|
|
145
|
+
def resolve_surprise(
|
|
146
|
+
self,
|
|
147
|
+
surprise_id: str,
|
|
148
|
+
*,
|
|
149
|
+
resolution: Optional[str] = None,
|
|
150
|
+
) -> Optional[Dict[str, Any]]:
|
|
151
|
+
"""Mark a surprise as resolved. Returns updated surprise or None."""
|
|
152
|
+
existing = self.get_surprise(surprise_id)
|
|
153
|
+
if existing is None:
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
self._conn.execute(
|
|
157
|
+
"UPDATE surprises SET resolved = 1, resolution = ? WHERE id = ?",
|
|
158
|
+
(resolution, surprise_id),
|
|
159
|
+
)
|
|
160
|
+
self._conn.commit()
|
|
161
|
+
return self.get_surprise(surprise_id)
|
|
162
|
+
|
|
163
|
+
def delete_surprise(self, surprise_id: str) -> bool:
|
|
164
|
+
"""Delete a surprise. Returns True if deleted."""
|
|
165
|
+
cursor = self._conn.execute("DELETE FROM surprises WHERE id = ?", (surprise_id,))
|
|
166
|
+
self._conn.commit()
|
|
167
|
+
return cursor.rowcount > 0
|
|
168
|
+
|
|
169
|
+
def resolve_stale(self, ttl_days: float) -> int:
|
|
170
|
+
"""Auto-resolve unresolved surprises older than ``ttl_days``.
|
|
171
|
+
|
|
172
|
+
A surprise nobody addressed in two weeks is stale context, not a
|
|
173
|
+
live signal — leaving it unresolved forever keeps it in context
|
|
174
|
+
assembly and inflates the accumulation trigger. Marks (never
|
|
175
|
+
deletes) with an explicit auto-resolution note. ``ttl_days <= 0``
|
|
176
|
+
disables. Returns the number resolved.
|
|
177
|
+
"""
|
|
178
|
+
if ttl_days <= 0:
|
|
179
|
+
return 0
|
|
180
|
+
from datetime import timedelta
|
|
181
|
+
cutoff = (datetime.now(timezone.utc)
|
|
182
|
+
- timedelta(days=ttl_days)).isoformat()
|
|
183
|
+
cursor = self._conn.execute(
|
|
184
|
+
"UPDATE surprises SET resolved = 1, resolution = ? "
|
|
185
|
+
"WHERE resolved = 0 AND timestamp < ?",
|
|
186
|
+
(f"auto-resolved: unaddressed for {ttl_days:g} days "
|
|
187
|
+
"(COLONY_SURPRISE_TTL_DAYS)", cutoff),
|
|
188
|
+
)
|
|
189
|
+
self._conn.commit()
|
|
190
|
+
return cursor.rowcount
|
|
191
|
+
|
|
192
|
+
def count_unresolved(self, since_hours: float = 1.0) -> int:
|
|
193
|
+
"""Count unresolved surprises within the last N hours."""
|
|
194
|
+
from datetime import timedelta
|
|
195
|
+
cutoff = (datetime.now(timezone.utc) - timedelta(hours=since_hours)).isoformat()
|
|
196
|
+
row = self._conn.execute(
|
|
197
|
+
"SELECT COUNT(*) as cnt FROM surprises WHERE resolved = 0 AND timestamp >= ?",
|
|
198
|
+
(cutoff,),
|
|
199
|
+
).fetchone()
|
|
200
|
+
return row["cnt"] if row else 0
|
|
201
|
+
|
|
202
|
+
def close(self) -> None:
|
|
203
|
+
self._conn.close()
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Colony Distributed Task Queue.
|
|
2
|
+
|
|
3
|
+
Hardware-aware distributed job scheduling across the Colony mesh.
|
|
4
|
+
The Queen (Sovereign) schedules; Workers (Vassals) execute.
|
|
5
|
+
|
|
6
|
+
Public API::
|
|
7
|
+
|
|
8
|
+
from apsimo.task_queue import (
|
|
9
|
+
Job, JobType, JobStatus, JobPriority,
|
|
10
|
+
JobCapabilityRequirement, JobResult,
|
|
11
|
+
WorkerCapabilities, QueueManager, WorkerNode,
|
|
12
|
+
JobHandler, Scheduler, TaskQueueConfig,
|
|
13
|
+
)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from apsimo.task_queue.config import TaskQueueConfig
|
|
17
|
+
from apsimo.task_queue.mesh_integration import QueueMeshEventHandler
|
|
18
|
+
from apsimo.task_queue.models import (
|
|
19
|
+
AuditEntry,
|
|
20
|
+
CircularDependencyError,
|
|
21
|
+
FederatedJob,
|
|
22
|
+
HeartbeatPayload,
|
|
23
|
+
Job,
|
|
24
|
+
JobCapabilityRequirement,
|
|
25
|
+
JobPriority,
|
|
26
|
+
JobResult,
|
|
27
|
+
JobStatus,
|
|
28
|
+
JobType,
|
|
29
|
+
QueueStats,
|
|
30
|
+
WorkerCapabilities,
|
|
31
|
+
deadline_urgency,
|
|
32
|
+
)
|
|
33
|
+
from apsimo.task_queue.queue_manager import QueueManager
|
|
34
|
+
from apsimo.task_queue.scheduler import Scheduler
|
|
35
|
+
from apsimo.task_queue.worker import JobHandler, WorkerNode, detect_local_capabilities
|
|
36
|
+
from apsimo.task_queue.work_control import (
|
|
37
|
+
WorkControlError,
|
|
38
|
+
WorkControlService,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
# Models
|
|
43
|
+
"Job",
|
|
44
|
+
"JobType",
|
|
45
|
+
"JobStatus",
|
|
46
|
+
"JobPriority",
|
|
47
|
+
"JobCapabilityRequirement",
|
|
48
|
+
"JobResult",
|
|
49
|
+
"WorkerCapabilities",
|
|
50
|
+
"HeartbeatPayload",
|
|
51
|
+
"QueueStats",
|
|
52
|
+
"AuditEntry",
|
|
53
|
+
"FederatedJob",
|
|
54
|
+
"CircularDependencyError",
|
|
55
|
+
"deadline_urgency",
|
|
56
|
+
# Core components
|
|
57
|
+
"QueueManager",
|
|
58
|
+
"WorkControlService",
|
|
59
|
+
"WorkControlError",
|
|
60
|
+
"WorkerNode",
|
|
61
|
+
"JobHandler",
|
|
62
|
+
"Scheduler",
|
|
63
|
+
# Config
|
|
64
|
+
"TaskQueueConfig",
|
|
65
|
+
# Mesh integration
|
|
66
|
+
"QueueMeshEventHandler",
|
|
67
|
+
# Utilities
|
|
68
|
+
"detect_local_capabilities",
|
|
69
|
+
]
|