apsimo 1.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- apsimo/__init__.py +38 -0
- apsimo/__main__.py +6 -0
- apsimo/agent/__init__.py +6 -0
- apsimo/agent/client.py +276 -0
- apsimo/agent/models.py +46 -0
- apsimo/agents/__init__.py +20 -0
- apsimo/agents/models.py +264 -0
- apsimo/agents/store.py +861 -0
- apsimo/agents/websocket.py +522 -0
- apsimo/api/__init__.py +1 -0
- apsimo/api/auth_telemetry.py +287 -0
- apsimo/api/authority.py +1203 -0
- apsimo/api/contact_grants.py +347 -0
- apsimo/api/middleware.py +483 -0
- apsimo/api/routers/__init__.py +1 -0
- apsimo/api/routers/commitment_work.py +265 -0
- apsimo/api/routers/context_gate.py +123 -0
- apsimo/api/routers/executions.py +140 -0
- apsimo/api/routers/followup_plans.py +147 -0
- apsimo/api/routers/governed_actions.py +162 -0
- apsimo/api/routers/host.py +14473 -0
- apsimo/api/routers/initiative_work.py +115 -0
- apsimo/api/routers/mining.py +104 -0
- apsimo/api/routers/observations.py +110 -0
- apsimo/api/routers/social_state.py +225 -0
- apsimo/api/routers/task_queue.py +2715 -0
- apsimo/api/routers/temporal_followups.py +251 -0
- apsimo/api/routers/transport.py +110 -0
- apsimo/api/routers/transport_ingress_api.py +240 -0
- apsimo/api/schemas/__init__.py +1 -0
- apsimo/api/schemas/host.py +1949 -0
- apsimo/autonomy/cli.py +110 -0
- apsimo/autonomy/condition_worker.py +437 -0
- apsimo/autonomy/config.py +424 -0
- apsimo/autonomy/loop.py +4316 -0
- apsimo/autonomy/registry.py +339 -0
- apsimo/autonomy/scheduler.py +1822 -0
- apsimo/autonomy/synthesis.py +449 -0
- apsimo/backup.py +962 -0
- apsimo/beliefs/__init__.py +23 -0
- apsimo/beliefs/contradictions.py +109 -0
- apsimo/beliefs/decay.py +61 -0
- apsimo/beliefs/engine.py +479 -0
- apsimo/beliefs/models.py +67 -0
- apsimo/beliefs/promotion.py +41 -0
- apsimo/beliefs/resolve.py +58 -0
- apsimo/beliefs/source_claims.py +690 -0
- apsimo/beliefs/source_projection.py +883 -0
- apsimo/beliefs/source_time.py +208 -0
- apsimo/beliefs/store.py +133 -0
- apsimo/briefings/aggregators.py +824 -0
- apsimo/briefings/composer.py +420 -0
- apsimo/briefings/config.py +55 -0
- apsimo/briefings/delivery.py +439 -0
- apsimo/briefings/engagement.py +97 -0
- apsimo/briefings/engine.py +274 -0
- apsimo/briefings/enhancer.py +99 -0
- apsimo/briefings/models.py +183 -0
- apsimo/briefings/scheduler.py +382 -0
- apsimo/briefings/store.py +435 -0
- apsimo/chain/__init__.py +48 -0
- apsimo/chain/block.py +100 -0
- apsimo/chain/cli.py +704 -0
- apsimo/chain/genesis.py +443 -0
- apsimo/chain/identity.py +416 -0
- apsimo/chain/keys.py +1025 -0
- apsimo/chain/local_keys.py +187 -0
- apsimo/chain/manager.py +290 -0
- apsimo/chain/node.py +163 -0
- apsimo/chain/plugin_transactions.py +371 -0
- apsimo/chain/protocol.py +220 -0
- apsimo/chain/state_machine.py +676 -0
- apsimo/chain/storage.py +503 -0
- apsimo/chain/transactions.py +250 -0
- apsimo/chain/validation.py +397 -0
- apsimo/channels/__init__.py +1 -0
- apsimo/channels/manifest.py +31 -0
- apsimo/channels/migrations/001_channels_schema.sql +12 -0
- apsimo/channels/phone_gateways.py +42 -0
- apsimo/channels/presence.py +188 -0
- apsimo/channels/router.py +235 -0
- apsimo/channels/store.py +231 -0
- apsimo/cli.py +2688 -0
- apsimo/cognition/__init__.py +11 -0
- apsimo/cognition/charter.py +398 -0
- apsimo/cognition/drive_governance.py +3530 -0
- apsimo/cognition/evidence_pipeline.py +1627 -0
- apsimo/cognition/external_events.py +932 -0
- apsimo/cognition/goal_spine.py +3488 -0
- apsimo/cognition/introspection.py +214 -0
- apsimo/cognition/prompt.py +150 -0
- apsimo/cognition/runtime.py +108 -0
- apsimo/cognition/trigger.py +154 -0
- apsimo/commitments/__init__.py +18 -0
- apsimo/commitments/local_work.py +355 -0
- apsimo/commitments/store.py +1052 -0
- apsimo/commitments/work.py +91 -0
- apsimo/compat.py +53 -0
- apsimo/compression/__init__.py +467 -0
- apsimo/connectors/__init__.py +21 -0
- apsimo/connectors/base.py +152 -0
- apsimo/connectors/caldav_calendar.py +125 -0
- apsimo/connectors/fs_documents.py +85 -0
- apsimo/connectors/imap_email.py +138 -0
- apsimo/connectors/manager.py +218 -0
- apsimo/connectors/webhook_pull.py +88 -0
- apsimo/contacts/__init__.py +33 -0
- apsimo/contacts/comms.py +357 -0
- apsimo/contacts/config.py +79 -0
- apsimo/contacts/exporters/__init__.py +1 -0
- apsimo/contacts/exporters/vcard.py +71 -0
- apsimo/contacts/identity_links.py +251 -0
- apsimo/contacts/importer.py +280 -0
- apsimo/contacts/importers/__init__.py +1 -0
- apsimo/contacts/importers/batch.py +43 -0
- apsimo/contacts/importers/macos_contacts.py +101 -0
- apsimo/contacts/migrations/001_contacts_schema.sql +141 -0
- apsimo/contacts/migrations/002_trust_scopes.sql +36 -0
- apsimo/contacts/migrations/003_open_gateway_enum.sql +32 -0
- apsimo/contacts/migrations/004_contact_provision_operations.sql +18 -0
- apsimo/contacts/migrations/005_identity_links.sql +27 -0
- apsimo/contacts/models.py +308 -0
- apsimo/contacts/scoring.py +16 -0
- apsimo/contacts/store.py +1623 -0
- apsimo/contacts/transport_ingress.py +252 -0
- apsimo/contacts/world_bridge.py +314 -0
- apsimo/contextgate/__init__.py +69 -0
- apsimo/contextgate/chunker.py +169 -0
- apsimo/contextgate/estimate.py +54 -0
- apsimo/contextgate/gate.py +313 -0
- apsimo/contextgate/retrieve.py +115 -0
- apsimo/delivery/__init__.py +16 -0
- apsimo/delivery/bridge.py +1260 -0
- apsimo/delivery/channels.py +526 -0
- apsimo/delivery/classification.py +50 -0
- apsimo/delivery/rate_limiter.py +268 -0
- apsimo/delivery/reachout_policy.py +206 -0
- apsimo/directed/__init__.py +22 -0
- apsimo/directed/audit.py +167 -0
- apsimo/directed/intake.py +95 -0
- apsimo/directed/models.py +191 -0
- apsimo/directed/service.py +509 -0
- apsimo/directives/__init__.py +25 -0
- apsimo/directives/evidence.py +87 -0
- apsimo/directives/extractor.py +188 -0
- apsimo/directives/guard.py +364 -0
- apsimo/directives/models.py +206 -0
- apsimo/directives/service.py +372 -0
- apsimo/directives/store.py +167 -0
- apsimo/doctor.py +2173 -0
- apsimo/environment.py +43 -0
- apsimo/events/__init__.py +33 -0
- apsimo/events/broadcaster.py +98 -0
- apsimo/events/bus.py +217 -0
- apsimo/events/journal.py +863 -0
- apsimo/events/stream.py +131 -0
- apsimo/events/types.py +150 -0
- apsimo/execution_results.py +357 -0
- apsimo/feedback/__init__.py +5 -0
- apsimo/feedback/store.py +76 -0
- apsimo/feeds/__init__.py +19 -0
- apsimo/feeds/cli.py +84 -0
- apsimo/feeds/engine.py +437 -0
- apsimo/feeds/example-feed.yaml +77 -0
- apsimo/feeds/hermes_cron.py +126 -0
- apsimo/feeds/manager.py +235 -0
- apsimo/feeds/spec.py +250 -0
- apsimo/feeds/template.py +202 -0
- apsimo/gate/__init__.py +18 -0
- apsimo/gate/audit.py +61 -0
- apsimo/gate/communication_policy.py +166 -0
- apsimo/gate/config.py +72 -0
- apsimo/gate/context_provenance.py +170 -0
- apsimo/gate/env_risk.py +226 -0
- apsimo/gate/guard_audit.py +353 -0
- apsimo/gate/layers/__init__.py +1 -0
- apsimo/gate/layers/base.py +15 -0
- apsimo/gate/layers/l1_recipient.py +66 -0
- apsimo/gate/layers/l2_pii.py +134 -0
- apsimo/gate/layers/l3_cross_context.py +50 -0
- apsimo/gate/layers/l4_trust_tier.py +78 -0
- apsimo/gate/layers/l5_injection.py +199 -0
- apsimo/gate/layers/l6_review.py +86 -0
- apsimo/gate/layers/l7_delay.py +100 -0
- apsimo/gate/layers/tom2_epistemic.py +185 -0
- apsimo/gate/models.py +64 -0
- apsimo/gate/pending_dispatch.py +5 -0
- apsimo/gate/pipeline.py +206 -0
- apsimo/gate/rejection.py +259 -0
- apsimo/gate/response_guard.py +700 -0
- apsimo/gate/rulesets/injection_v1.yaml +51 -0
- apsimo/gate/surface_policy.py +189 -0
- apsimo/gate/taint.py +226 -0
- apsimo/genesis.json +9 -0
- apsimo/goals/__init__.py +100 -0
- apsimo/goals/config.py +38 -0
- apsimo/goals/decomposer.py +421 -0
- apsimo/goals/engine.py +617 -0
- apsimo/goals/inference.py +354 -0
- apsimo/goals/models.py +302 -0
- apsimo/goals/priority.py +270 -0
- apsimo/goals/queue_bridge.py +149 -0
- apsimo/goals/replan.py +450 -0
- apsimo/goals/schema.sql +89 -0
- apsimo/goals/store.py +692 -0
- apsimo/governed_actions.py +1708 -0
- apsimo/harness_integration/__init__.py +45 -0
- apsimo/harness_integration/context.py +41 -0
- apsimo/harness_integration/skills.py +231 -0
- apsimo/identity/__init__.py +26 -0
- apsimo/identity/participants.py +181 -0
- apsimo/identity/resolver.py +329 -0
- apsimo/identity_bootstrap/__init__.py +5 -0
- apsimo/identity_bootstrap/builder.py +208 -0
- apsimo/identity_bootstrap/corpus.py +443 -0
- apsimo/identity_bootstrap/models.py +54 -0
- apsimo/identity_bootstrap/runner.py +353 -0
- apsimo/identity_bootstrap/seeders/__init__.py +25 -0
- apsimo/identity_bootstrap/seeders/briefings.py +109 -0
- apsimo/identity_bootstrap/seeders/chain.py +57 -0
- apsimo/identity_bootstrap/seeders/goals.py +128 -0
- apsimo/identity_bootstrap/seeders/memory.py +191 -0
- apsimo/identity_bootstrap/seeders/neo4j_cognition.py +79 -0
- apsimo/identity_bootstrap/seeders/relationship.py +152 -0
- apsimo/identity_bootstrap/seeders/sessions.py +67 -0
- apsimo/identity_bootstrap/seeders/skills.py +92 -0
- apsimo/identity_bootstrap/seeders/task_queue.py +72 -0
- apsimo/identity_bootstrap/seeders/world_model.py +143 -0
- apsimo/identity_bootstrap/self_query.py +92 -0
- apsimo/identity_bootstrap/self_reflection.py +155 -0
- apsimo/identity_bootstrap/skill.py +37 -0
- apsimo/identity_bootstrap/verifier.py +436 -0
- apsimo/initiatives/__init__.py +20 -0
- apsimo/initiatives/action_registry.py +454 -0
- apsimo/initiatives/approval_authority.py +2105 -0
- apsimo/initiatives/approval_policy.py +123 -0
- apsimo/initiatives/assignment.py +263 -0
- apsimo/initiatives/backup_evidence.py +100 -0
- apsimo/initiatives/context_freshness.py +103 -0
- apsimo/initiatives/models.py +318 -0
- apsimo/initiatives/native_work.py +270 -0
- apsimo/initiatives/standing_approvals.py +232 -0
- apsimo/initiatives/store.py +1081 -0
- apsimo/initiatives/temporal_followup.py +410 -0
- apsimo/intelligence/__init__.py +1 -0
- apsimo/intelligence/cognition/__init__.py +24 -0
- apsimo/intelligence/cognition/gap_detector.py +148 -0
- apsimo/intelligence/cognition/metalearner.py +547 -0
- apsimo/intelligence/cognition/metrics_collector.py +217 -0
- apsimo/intelligence/cognition/performance_index.py +299 -0
- apsimo/intelligence/cognition/registry.py +192 -0
- apsimo/intelligence/cognition/strategy_adjuster.py +222 -0
- apsimo/intelligence/cognition/types.py +16 -0
- apsimo/intelligence/components/__init__.py +66 -0
- apsimo/intelligence/components/anomaly_detector.py +413 -0
- apsimo/intelligence/components/initiative_engine.py +2643 -0
- apsimo/intelligence/components/preference_learner.py +521 -0
- apsimo/intelligence/components/research_orchestrator.py +358 -0
- apsimo/intelligence/components/self_directed_thinker.py +221 -0
- apsimo/intelligence/components/self_reflector.py +252 -0
- apsimo/intelligence/components/session_continuity.py +154 -0
- apsimo/intelligence/components/task_planner.py +320 -0
- apsimo/intelligence/components/tool_learner.py +217 -0
- apsimo/intelligence/graph/__init__.py +79 -0
- apsimo/intelligence/graph/client.py +2483 -0
- apsimo/intelligence/graph/consolidator.py +405 -0
- apsimo/intelligence/graph/distiller.py +312 -0
- apsimo/intelligence/graph/migrations.py +129 -0
- apsimo/intelligence/graph/queries.py +248 -0
- apsimo/intelligence/graph/recall.py +281 -0
- apsimo/intelligence/graph/reconciler.py +144 -0
- apsimo/intelligence/graph/schema.py +337 -0
- apsimo/intelligence/graph/selection.py +252 -0
- apsimo/intelligence/learning/__init__.py +17 -0
- apsimo/intelligence/learning/continuous_learner.py +245 -0
- apsimo/intelligence/learning/feedback_store.py +321 -0
- apsimo/intelligence/mind_model/__init__.py +1 -0
- apsimo/intelligence/mind_model/graph_baseline.py +136 -0
- apsimo/intelligence/mind_model/signal_collector.py +361 -0
- apsimo/intelligence/relationships/__init__.py +11 -0
- apsimo/intelligence/relationships/profiler.py +389 -0
- apsimo/intelligence/relationships/scorer.py +560 -0
- apsimo/intelligence/relationships/signal_floor.py +66 -0
- apsimo/intelligence/relationships/trust_tiers.py +300 -0
- apsimo/intelligence/synthesis/__init__.py +40 -0
- apsimo/intelligence/synthesis/connection_discoverer.py +379 -0
- apsimo/intelligence/synthesis/cross_domain_analyzer.py +287 -0
- apsimo/intelligence/synthesis/insight_deliverer.py +171 -0
- apsimo/intelligence/synthesis/insight_store.py +79 -0
- apsimo/intelligence/synthesis/insight_validator.py +183 -0
- apsimo/intelligence/synthesis/novelty_scorer.py +267 -0
- apsimo/intelligence/turn_middleware/__init__.py +15 -0
- apsimo/intelligence/turn_middleware/memory_sync.py +119 -0
- apsimo/mcp/__init__.py +41 -0
- apsimo/mcp/__main__.py +6 -0
- apsimo/mcp/config.py +287 -0
- apsimo/mcp/server.py +501 -0
- apsimo/migrations.py +187 -0
- apsimo/mining/__init__.py +27 -0
- apsimo/mining/corpus.py +239 -0
- apsimo/mining/escalations.py +289 -0
- apsimo/mining/models.py +169 -0
- apsimo/mining/store.py +210 -0
- apsimo/models/__init__.py +30 -0
- apsimo/models/memory.py +80 -0
- apsimo/models/mesh.py +72 -0
- apsimo/models/person.py +104 -0
- apsimo/models/signal.py +108 -0
- apsimo/observations/__init__.py +15 -0
- apsimo/observations/store.py +277 -0
- apsimo/patterns/__init__.py +6 -0
- apsimo/patterns/extract.py +187 -0
- apsimo/patterns/store.py +227 -0
- apsimo/persona/__init__.py +1 -0
- apsimo/persona/engine.py +611 -0
- apsimo/persona/manifest.py +140 -0
- apsimo/projects/__init__.py +28 -0
- apsimo/projects/engine.py +1681 -0
- apsimo/projects/event_outbox.py +188 -0
- apsimo/projects/models.py +216 -0
- apsimo/projects/planner.py +181 -0
- apsimo/projects/store.py +1446 -0
- apsimo/proposals/__init__.py +12 -0
- apsimo/proposals/engine.py +114 -0
- apsimo/proposals/models.py +207 -0
- apsimo/qualification/__init__.py +1 -0
- apsimo/qualification/cases.py +75 -0
- apsimo/qualification/cli.py +51 -0
- apsimo/qualification/memory_cases.py +209 -0
- apsimo/qualification/records.py +92 -0
- apsimo/qualification/report.py +87 -0
- apsimo/qualification/runner.py +311 -0
- apsimo/qualification/structured_cases.py +131 -0
- apsimo/reasoning/__init__.py +13 -0
- apsimo/reasoning/executor.py +506 -0
- apsimo/reasoning/loop.py +373 -0
- apsimo/reasoning/native_tools/__init__.py +16 -0
- apsimo/reasoning/native_tools/calculate.py +141 -0
- apsimo/reasoning/native_tools/file_ops.py +150 -0
- apsimo/reasoning/native_tools/web_search.py +49 -0
- apsimo/reasoning/tool_policy.py +182 -0
- apsimo/redact/__init__.py +176 -0
- apsimo/repos/__init__.py +5 -0
- apsimo/repos/mirrors.py +204 -0
- apsimo/research/__init__.py +41 -0
- apsimo/research/artifact.py +482 -0
- apsimo/research/gatherer.py +387 -0
- apsimo/research/pipeline.py +513 -0
- apsimo/research/search/__init__.py +7 -0
- apsimo/research/search/base.py +41 -0
- apsimo/research/search/brave.py +59 -0
- apsimo/research/search/cache.py +51 -0
- apsimo/research/search/duckduckgo.py +103 -0
- apsimo/research/search/orchestrator.py +119 -0
- apsimo/research/search/serpapi.py +59 -0
- apsimo/research/search/tavily.py +59 -0
- apsimo/research/synthesizer.py +309 -0
- apsimo/router/__init__.py +30 -0
- apsimo/router/complexity_scorer.py +148 -0
- apsimo/router/endpoints.py +153 -0
- apsimo/router/fallback.py +58 -0
- apsimo/router/functions.py +243 -0
- apsimo/router/native_policy.py +52 -0
- apsimo/router/router.py +762 -0
- apsimo/router/self_learning.py +174 -0
- apsimo/router/tiers.py +677 -0
- apsimo/sandbox/__init__.py +21 -0
- apsimo/sandbox/backend.py +195 -0
- apsimo/sandbox/manager.py +173 -0
- apsimo/scope_bounds.py +7 -0
- apsimo/secrets/__init__.py +6 -0
- apsimo/secrets/backends/__init__.py +8 -0
- apsimo/secrets/backends/base.py +42 -0
- apsimo/secrets/backends/env.py +110 -0
- apsimo/secrets/backends/keyring.py +72 -0
- apsimo/secrets/backends/onepassword.py +232 -0
- apsimo/secrets/cli.py +191 -0
- apsimo/secrets/manager.py +160 -0
- apsimo/secrets/migration.py +101 -0
- apsimo/secrets/types.py +98 -0
- apsimo/seed.py +41 -0
- apsimo/self_model/__init__.py +37 -0
- apsimo/self_model/appraisals.py +673 -0
- apsimo/self_model/benchmark.py +1314 -0
- apsimo/self_model/brief.py +40 -0
- apsimo/self_model/event_concerns.py +1128 -0
- apsimo/self_model/execution_forecasts.py +353 -0
- apsimo/self_model/expectations.py +1595 -0
- apsimo/self_model/experiments.py +1150 -0
- apsimo/self_model/journal.py +148 -0
- apsimo/self_model/judgments.py +705 -0
- apsimo/self_model/native_outcomes.py +55 -0
- apsimo/self_model/params.py +220 -0
- apsimo/self_model/perspective.py +246 -0
- apsimo/self_model/reconcile.py +183 -0
- apsimo/self_model/reply_forecasts.py +381 -0
- apsimo/self_model/runtime_forecasts.py +296 -0
- apsimo/self_model/runtime_models.py +67 -0
- apsimo/self_model/settlement.py +207 -0
- apsimo/self_model/situation.py +1731 -0
- apsimo/self_model/store.py +883 -0
- apsimo/self_model/supervised.py +137 -0
- apsimo/self_model/thinker.py +99 -0
- apsimo/self_model/trust.py +388 -0
- apsimo/self_model/workspace.py +2388 -0
- apsimo/server.py +4197 -0
- apsimo/services/__init__.py +1 -0
- apsimo/services/agent_bridge.py +474 -0
- apsimo/services/initiative_executor.py +914 -0
- apsimo/services/instance.py +297 -0
- apsimo/sessions/__init__.py +22 -0
- apsimo/sessions/config.py +13 -0
- apsimo/sessions/context_loader.py +88 -0
- apsimo/sessions/federation_session.py +75 -0
- apsimo/sessions/isolated_session.py +98 -0
- apsimo/sessions/reports.py +84 -0
- apsimo/sessions/store.py +148 -0
- apsimo/setup.py +2818 -0
- apsimo/setup_hermes.py +879 -0
- apsimo/setup_local_work.py +218 -0
- apsimo/setup_native_goals.py +134 -0
- apsimo/setup_native_reviews.py +115 -0
- apsimo/skills/__init__.py +10 -0
- apsimo/skills/base.py +108 -0
- apsimo/skills/budget.py +28 -0
- apsimo/skills/executor.py +493 -0
- apsimo/skills/executors/__init__.py +1 -0
- apsimo/skills/executors/behavioral_correction.py +75 -0
- apsimo/skills/executors/capability_gap.py +38 -0
- apsimo/skills/executors/data_quality.py +163 -0
- apsimo/skills/executors/knowledge_acquisition.py +41 -0
- apsimo/skills/executors/operational_hygiene.py +185 -0
- apsimo/skills/executors/subsystem_health.py +169 -0
- apsimo/skills/hermes_export.py +431 -0
- apsimo/skills/index.py +123 -0
- apsimo/skills/learning/__init__.py +21 -0
- apsimo/skills/learning/novelty_detector.py +206 -0
- apsimo/skills/learning/pattern_extractor.py +199 -0
- apsimo/skills/learning/triggers.py +159 -0
- apsimo/skills/loader.py +246 -0
- apsimo/skills/migrations/002_progressive_loading.sql +6 -0
- apsimo/skills/migrations/backfill_triggers.py +20 -0
- apsimo/skills/models.py +202 -0
- apsimo/skills/packager.py +128 -0
- apsimo/skills/protocols.py +70 -0
- apsimo/skills/registry.py +191 -0
- apsimo/skills/runtime.py +58 -0
- apsimo/skills/sandbox_runner.py +229 -0
- apsimo/skills/scheduler.py +129 -0
- apsimo/skills/schema.py +79 -0
- apsimo/skills/security/__init__.py +12 -0
- apsimo/skills/security/guards.py +53 -0
- apsimo/skills/security/scanner.py +223 -0
- apsimo/skills_memory/__init__.py +26 -0
- apsimo/skills_memory/distill.py +159 -0
- apsimo/skills_memory/models.py +85 -0
- apsimo/skills_memory/retrieve.py +62 -0
- apsimo/skills_memory/store.py +172 -0
- apsimo/surprise/__init__.py +6 -0
- apsimo/surprise/accumulation.py +57 -0
- apsimo/surprise/scorer.py +102 -0
- apsimo/surprise/store.py +203 -0
- apsimo/task_queue/__init__.py +69 -0
- apsimo/task_queue/action_receipts.py +148 -0
- apsimo/task_queue/approval_relay_canary.py +108 -0
- apsimo/task_queue/config.py +85 -0
- apsimo/task_queue/contract.py +361 -0
- apsimo/task_queue/events.py +130 -0
- apsimo/task_queue/governor.py +1031 -0
- apsimo/task_queue/handlers/__init__.py +16 -0
- apsimo/task_queue/handlers/base.py +37 -0
- apsimo/task_queue/handlers/inference.py +640 -0
- apsimo/task_queue/handlers/monitoring.py +116 -0
- apsimo/task_queue/handlers/registry.py +75 -0
- apsimo/task_queue/handlers/subtask_handler.py +173 -0
- apsimo/task_queue/handlers/system_maintenance.py +147 -0
- apsimo/task_queue/mesh_integration.py +111 -0
- apsimo/task_queue/models.py +317 -0
- apsimo/task_queue/queue_manager.py +8286 -0
- apsimo/task_queue/routing.py +287 -0
- apsimo/task_queue/scheduler.py +252 -0
- apsimo/task_queue/schema.sql +197 -0
- apsimo/task_queue/work_control.py +342 -0
- apsimo/task_queue/worker.py +993 -0
- apsimo/telemetry.py +145 -0
- apsimo/tom/__init__.py +6 -0
- apsimo/tom/affect.py +387 -0
- apsimo/tom/approvals.py +171 -0
- apsimo/tom/arcs.py +896 -0
- apsimo/tom/asymmetry.py +131 -0
- apsimo/tom/eligibility.py +248 -0
- apsimo/tom/engagement.py +214 -0
- apsimo/tom/exposure.py +214 -0
- apsimo/tom/extractor.py +306 -0
- apsimo/tom/fact_adapters.py +144 -0
- apsimo/tom/facts.py +326 -0
- apsimo/tom/integration.py +592 -0
- apsimo/tom/leveled.py +118 -0
- apsimo/tom/levels.py +247 -0
- apsimo/tom/recipient_audit.py +995 -0
- apsimo/tom/recipient_simulator.py +593 -0
- apsimo/tom/source_lineage.py +93 -0
- apsimo/tom/tom2.py +277 -0
- apsimo/tom/visibility.py +559 -0
- apsimo/tom/visibility_store.py +414 -0
- apsimo/tools/__init__.py +0 -0
- apsimo/tools/definitions.py +740 -0
- apsimo/tools/handlers.py +943 -0
- apsimo/toolsmith/__init__.py +26 -0
- apsimo/toolsmith/authority.py +166 -0
- apsimo/toolsmith/engine.py +559 -0
- apsimo/toolsmith/integrity.py +100 -0
- apsimo/toolsmith/miner.py +145 -0
- apsimo/toolsmith/policy.py +110 -0
- apsimo/toolsmith/registry.py +635 -0
- apsimo/turns/__init__.py +17 -0
- apsimo/turns/audio.py +134 -0
- apsimo/turns/documents.py +235 -0
- apsimo/turns/executions.py +486 -0
- apsimo/turns/hermes_history.py +245 -0
- apsimo/turns/hermes_kanban.py +268 -0
- apsimo/turns/hermes_work.py +96 -0
- apsimo/turns/idempotency.py +752 -0
- apsimo/turns/local_work.py +115 -0
- apsimo/turns/media.py +581 -0
- apsimo/turns/reported_workers.py +196 -0
- apsimo/turns/source_annotations.py +283 -0
- apsimo/turns/source_attribution.py +154 -0
- apsimo/turns/source_read.py +351 -0
- apsimo/turns/source_vectors.py +263 -0
- apsimo/turns/video.py +210 -0
- apsimo/util/autonomy_preset.py +220 -0
- apsimo/util/instance.py +92 -0
- apsimo/util/model_output.py +25 -0
- apsimo/util/quiet_hours.py +27 -0
- apsimo/util/session_safety.py +37 -0
- apsimo/util/temporal.py +343 -0
- apsimo/vector/__init__.py +75 -0
- apsimo/vector/backfill.py +171 -0
- apsimo/vector/caption.py +114 -0
- apsimo/vector/collections.py +51 -0
- apsimo/vector/config.py +102 -0
- apsimo/vector/embedder.py +670 -0
- apsimo/vector/image_preprocess.py +406 -0
- apsimo/vector/image_store.py +296 -0
- apsimo/vector/indexes.py +162 -0
- apsimo/vector/migrate.py +334 -0
- apsimo/vector/multimodal_provider.py +417 -0
- apsimo/vector/multimodal_types.py +87 -0
- apsimo/vector/openai_provider.py +119 -0
- apsimo/vector/query.py +49 -0
- apsimo/vector/reranker.py +565 -0
- apsimo/vector/safety_image.py +159 -0
- apsimo/vector/scanner.py +197 -0
- apsimo/vector/setup.py +289 -0
- apsimo/vector/store.py +533 -0
- apsimo/vector/tiers.py +263 -0
- apsimo/work_orders.py +925 -0
- apsimo/workers/__init__.py +21 -0
- apsimo/workers/agent_bridge.py +640 -0
- apsimo/workers/colony_worker.py +382 -0
- apsimo/workers/queue_worker.py +441 -0
- apsimo/workers/skills_sync.py +152 -0
- apsimo/world_model/__init__.py +71 -0
- apsimo/world_model/causal_maintenance.py +131 -0
- apsimo/world_model/causal_policy.py +43 -0
- apsimo/world_model/causal_query.py +125 -0
- apsimo/world_model/confidence.py +54 -0
- apsimo/world_model/config.py +64 -0
- apsimo/world_model/constants.py +97 -0
- apsimo/world_model/entities.py +145 -0
- apsimo/world_model/expectation_resolvers.py +177 -0
- apsimo/world_model/extraction/__init__.py +7 -0
- apsimo/world_model/extraction/base.py +62 -0
- apsimo/world_model/extraction/conversation_extractor.py +262 -0
- apsimo/world_model/extraction/detector.py +74 -0
- apsimo/world_model/extraction/document_extractor.py +78 -0
- apsimo/world_model/extraction/formats/__init__.py +24 -0
- apsimo/world_model/extraction/formats/csv_fmt.py +68 -0
- apsimo/world_model/extraction/formats/html_fmt.py +72 -0
- apsimo/world_model/extraction/formats/json_fmt.py +68 -0
- apsimo/world_model/extraction/formats/pdf.py +43 -0
- apsimo/world_model/extraction/formats/text.py +27 -0
- apsimo/world_model/extraction/llm_extractor.py +164 -0
- apsimo/world_model/extraction/pipeline.py +73 -0
- apsimo/world_model/integrations/__init__.py +5 -0
- apsimo/world_model/integrations/mind_model_bridge.py +115 -0
- apsimo/world_model/integrations/social_intel_bridge.py +120 -0
- apsimo/world_model/jobs/__init__.py +4 -0
- apsimo/world_model/jobs/extraction_job.py +168 -0
- apsimo/world_model/llm_extract.py +572 -0
- apsimo/world_model/neo4j/__init__.py +5 -0
- apsimo/world_model/neo4j/backend.py +654 -0
- apsimo/world_model/observations.py +155 -0
- apsimo/world_model/populator.py +307 -0
- apsimo/world_model/postgres/__init__.py +1 -0
- apsimo/world_model/postgres/backend.py +683 -0
- apsimo/world_model/relationships.py +25 -0
- apsimo/world_model/resolution/__init__.py +13 -0
- apsimo/world_model/resolution/entity_resolver.py +232 -0
- apsimo/world_model/resolution/merge_audit.py +16 -0
- apsimo/world_model/resolution/merge_workflow.py +117 -0
- apsimo/world_model/source_reports.py +121 -0
- apsimo/world_model/sqlite/__init__.py +4 -0
- apsimo/world_model/sqlite/backend.py +855 -0
- apsimo/world_model/sqlite/schema.sql +132 -0
- apsimo/world_model/store.py +545 -0
- apsimo-1.3.0.dist-info/METADATA +78 -0
- apsimo-1.3.0.dist-info/RECORD +614 -0
- apsimo-1.3.0.dist-info/WHEEL +5 -0
- apsimo-1.3.0.dist-info/entry_points.txt +11 -0
- apsimo-1.3.0.dist-info/licenses/LICENSE +21 -0
- apsimo-1.3.0.dist-info/top_level.txt +2 -0
- colony_sidecar/__init__.py +4 -0
apsimo/models/signal.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Behavioral signal models.
|
|
2
|
+
|
|
3
|
+
Signals are atomic observations about a person's communication patterns,
|
|
4
|
+
sentiment, and availability. They feed into the mind model for state
|
|
5
|
+
estimation and relationship scoring.
|
|
6
|
+
|
|
7
|
+
Colony is a first-class participant in its own signal stream — not merely an
|
|
8
|
+
observer. Colony-sourced signals (``direction="colony"``) capture Colony's own
|
|
9
|
+
behavior: when it reaches out, how effective its actions are, how well its
|
|
10
|
+
adapted style is received. Contact-sourced signals (``direction="contact"``)
|
|
11
|
+
capture the other person's behavior. Bilateral signals (``direction="bilateral"``)
|
|
12
|
+
describe the exchange pattern between Colony and a contact.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
from enum import Enum
|
|
18
|
+
from typing import Any, Dict, Optional
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SignalType(str, Enum):
|
|
22
|
+
"""Categories of behavioral signals.
|
|
23
|
+
|
|
24
|
+
Communication patterns track message flow characteristics.
|
|
25
|
+
Sentiment signals capture emotional tone.
|
|
26
|
+
Interaction quality measures depth of engagement.
|
|
27
|
+
Availability tracks when someone is typically reachable.
|
|
28
|
+
|
|
29
|
+
Colony signals (new) capture Colony's own behavior in the signal stream.
|
|
30
|
+
These feed the colony_initiative_response, colony_effectiveness, and
|
|
31
|
+
colony_style_fit scoring dimensions.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
# Communication patterns
|
|
35
|
+
MESSAGE_FREQUENCY = "message_frequency"
|
|
36
|
+
RESPONSE_TIME = "response_time"
|
|
37
|
+
INITIATION_RATE = "initiation_rate"
|
|
38
|
+
|
|
39
|
+
# Sentiment
|
|
40
|
+
SENTIMENT_POSITIVE = "sentiment_positive"
|
|
41
|
+
SENTIMENT_NEGATIVE = "sentiment_negative"
|
|
42
|
+
SENTIMENT_NEUTRAL = "sentiment_neutral"
|
|
43
|
+
|
|
44
|
+
# Interaction quality
|
|
45
|
+
ENGAGEMENT_DEPTH = "engagement_depth"
|
|
46
|
+
TOPIC_DIVERSITY = "topic_diversity"
|
|
47
|
+
|
|
48
|
+
# Availability
|
|
49
|
+
TYPICAL_ACTIVE_HOURS = "typical_active_hours"
|
|
50
|
+
AVAILABILITY_PATTERN = "availability_pattern"
|
|
51
|
+
|
|
52
|
+
# Colony-sourced signals (Colony is a participant, not just an observer)
|
|
53
|
+
COLONY_INITIATIVE = "colony_initiative" # Colony reached out first
|
|
54
|
+
COLONY_SUGGESTION = "colony_suggestion" # Colony made a suggestion/action
|
|
55
|
+
COLONY_EFFECTIVENESS = "colony_effectiveness" # Outcome of Colony's action
|
|
56
|
+
CONTACT_RESPONSE_TO_COLONY = "contact_response_to_colony" # Response to Colony-origin
|
|
57
|
+
COLONY_STYLE_SIGNAL = "colony_style_signal" # Style adaptation feedback
|
|
58
|
+
|
|
59
|
+
# Legacy aliases kept for migration compatibility
|
|
60
|
+
OWNER_EXPLICIT = "owner_explicit"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class SignalStrength(str, Enum):
|
|
64
|
+
"""Confidence level of a signal observation.
|
|
65
|
+
|
|
66
|
+
WEAK: Low confidence, sparse data
|
|
67
|
+
MODERATE: Reasonable confidence, sufficient data
|
|
68
|
+
STRONG: High confidence, abundant data
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
WEAK = "weak"
|
|
72
|
+
MODERATE = "moderate"
|
|
73
|
+
STRONG = "strong"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class Signal:
|
|
78
|
+
"""A single behavioral observation about a person.
|
|
79
|
+
|
|
80
|
+
Signals are the raw building blocks of Colony's understanding.
|
|
81
|
+
Each signal captures one dimension of behavior at a point in time.
|
|
82
|
+
|
|
83
|
+
Attributes:
|
|
84
|
+
id: Unique identifier
|
|
85
|
+
person_id: The person this signal relates to
|
|
86
|
+
signal_type: What kind of behavior was observed
|
|
87
|
+
value: Normalized value between 0 and 1
|
|
88
|
+
raw_value: Original unnormalized value (for debugging/recalibration)
|
|
89
|
+
strength: Confidence level of this observation
|
|
90
|
+
source: How the signal was generated ("observed", "inferred", "explicit")
|
|
91
|
+
timestamp: When the signal was captured
|
|
92
|
+
context: Additional metadata (channel, message_id, etc.)
|
|
93
|
+
direction: Who the signal is about.
|
|
94
|
+
"contact" — describes the contact's behavior (existing signals).
|
|
95
|
+
"colony" — describes Colony's own behavior (colony_initiative etc.).
|
|
96
|
+
"bilateral" — describes the exchange pattern (e.g. initiation_ratio).
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
id: str
|
|
100
|
+
person_id: str
|
|
101
|
+
signal_type: SignalType
|
|
102
|
+
value: float
|
|
103
|
+
raw_value: Optional[float] = None
|
|
104
|
+
strength: SignalStrength = SignalStrength.MODERATE
|
|
105
|
+
source: str = "observed"
|
|
106
|
+
timestamp: Optional[datetime] = None
|
|
107
|
+
context: Optional[Dict[str, Any]] = None
|
|
108
|
+
direction: str = "contact" # "contact" | "colony" | "bilateral"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Observation store (v0.16.0) — the agent is Colony's sensor array.
|
|
2
|
+
|
|
3
|
+
Colony does not own external API clients. The agent observes the world
|
|
4
|
+
through its existing Hermes connections (github, terminal, web, ...)
|
|
5
|
+
and reports domain snapshots here. Colony's context loaders read
|
|
6
|
+
observations, never external APIs.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from apsimo.observations.store import (
|
|
10
|
+
OBSERVATION_DOMAINS,
|
|
11
|
+
Observation,
|
|
12
|
+
ObservationStore,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = ["Observation", "ObservationStore", "OBSERVATION_DOMAINS"]
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"""SQLite store for agent-reported domain observations (v0.16.0).
|
|
2
|
+
|
|
3
|
+
One row per (domain, entity_id) — the latest snapshot wins. Colony's
|
|
4
|
+
initiative generators read these; the autonomy loop requests refreshes
|
|
5
|
+
by posting read-only ``agent_sync_<domain>`` jobs when a domain's
|
|
6
|
+
newest observation outlives its sync interval.
|
|
7
|
+
|
|
8
|
+
Expected payload shapes per domain (generators are defensive — extra
|
|
9
|
+
keys pass through into initiative context, missing keys skip rules):
|
|
10
|
+
|
|
11
|
+
- coding: {title, url, ci_status, review_requested, draft, author}
|
|
12
|
+
- task: {title, url, state, assignee, due, stale_days}
|
|
13
|
+
- calendar: {title, start_time, location, attendees, needs_prep}
|
|
14
|
+
- research: {title, url, status, last_checked}
|
|
15
|
+
- project: {title, due_on, open_issues, closed_issues}
|
|
16
|
+
- system: {status, latency_ms, error_rate, message}
|
|
17
|
+
- skills: {description, tags, path, source} (v0.18.0 Hermes bridge)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
import logging
|
|
24
|
+
import sqlite3
|
|
25
|
+
from dataclasses import dataclass
|
|
26
|
+
from datetime import datetime, timezone
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Any, Dict, List, Optional
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
# Domains the sensor loop knows about. Adding a domain means: a sync
|
|
33
|
+
# action in the action registry, a generator in the initiative engine,
|
|
34
|
+
# and (if volatile) a durability entry in context_freshness.py.
|
|
35
|
+
OBSERVATION_DOMAINS = (
|
|
36
|
+
"coding",
|
|
37
|
+
"task",
|
|
38
|
+
"calendar",
|
|
39
|
+
"research",
|
|
40
|
+
"project",
|
|
41
|
+
"system",
|
|
42
|
+
# v0.18.0: the agent's installed Hermes skill index, reported by the
|
|
43
|
+
# OpenClaw plugin. Push-only — the plugin scans ~/.hermes/skills on
|
|
44
|
+
# its own schedule, so this domain deliberately has NO entry in
|
|
45
|
+
# OBSERVATION_SYNC_ACTIONS and is NOT in the COLONY_SYNC_DOMAINS
|
|
46
|
+
# defaults; the autonomy loop never posts agent_sync jobs for it.
|
|
47
|
+
"skills",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# How old a domain's newest observation may be before the autonomy loop
|
|
51
|
+
# requests a fresh sync from the agent (seconds). Coarser than the
|
|
52
|
+
# context-freshness TTLs — those govern acting on one snapshot, these
|
|
53
|
+
# govern re-scanning a whole domain.
|
|
54
|
+
OBSERVATION_SYNC_INTERVALS: Dict[str, int] = {
|
|
55
|
+
"coding": 900,
|
|
56
|
+
"task": 3600,
|
|
57
|
+
"calendar": 900,
|
|
58
|
+
"research": 86400,
|
|
59
|
+
"project": 86400,
|
|
60
|
+
"system": 300,
|
|
61
|
+
# Informational only: the plugin pushes the Hermes skill index every
|
|
62
|
+
# 24h; the loop never requests a sync for this push-only domain.
|
|
63
|
+
"skills": 86400,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass
|
|
68
|
+
class Observation:
|
|
69
|
+
domain: str
|
|
70
|
+
entity_id: str
|
|
71
|
+
payload: Dict[str, Any]
|
|
72
|
+
observed_at: Optional[datetime]
|
|
73
|
+
reported_by: Optional[str] = None
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def from_row(cls, row: dict) -> "Observation":
|
|
77
|
+
payload_raw = row.get("payload") or "{}"
|
|
78
|
+
try:
|
|
79
|
+
payload = json.loads(payload_raw) if isinstance(payload_raw, str) else payload_raw
|
|
80
|
+
except (ValueError, TypeError):
|
|
81
|
+
payload = {}
|
|
82
|
+
observed_at = row.get("observed_at")
|
|
83
|
+
if isinstance(observed_at, str):
|
|
84
|
+
try:
|
|
85
|
+
observed_at = datetime.fromisoformat(observed_at.replace("Z", "+00:00"))
|
|
86
|
+
except (ValueError, TypeError):
|
|
87
|
+
observed_at = None
|
|
88
|
+
return cls(
|
|
89
|
+
domain=row["domain"],
|
|
90
|
+
entity_id=row["entity_id"],
|
|
91
|
+
payload=payload,
|
|
92
|
+
observed_at=observed_at if isinstance(observed_at, datetime) else None,
|
|
93
|
+
reported_by=row.get("reported_by"),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
97
|
+
return {
|
|
98
|
+
"domain": self.domain,
|
|
99
|
+
"entity_id": self.entity_id,
|
|
100
|
+
"payload": self.payload,
|
|
101
|
+
"observed_at": self.observed_at.isoformat() if self.observed_at else None,
|
|
102
|
+
"reported_by": self.reported_by,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class ObservationStore:
|
|
107
|
+
"""Latest-snapshot-per-entity observation persistence."""
|
|
108
|
+
|
|
109
|
+
def __init__(self, state_dir: Optional[Path] = None):
|
|
110
|
+
if state_dir is None:
|
|
111
|
+
from apsimo.initiatives.store import get_state_dir
|
|
112
|
+
state_dir = get_state_dir()
|
|
113
|
+
self._state_dir = Path(state_dir)
|
|
114
|
+
self._state_dir.mkdir(parents=True, exist_ok=True)
|
|
115
|
+
self._db_path = self._state_dir / "observations.db"
|
|
116
|
+
self._db = self._connect()
|
|
117
|
+
|
|
118
|
+
def _connect(self) -> sqlite3.Connection:
|
|
119
|
+
conn = sqlite3.connect(self._db_path, check_same_thread=False)
|
|
120
|
+
conn.row_factory = sqlite3.Row
|
|
121
|
+
conn.execute("PRAGMA journal_mode=WAL")
|
|
122
|
+
conn.execute("PRAGMA busy_timeout=5000")
|
|
123
|
+
conn.execute(
|
|
124
|
+
"""
|
|
125
|
+
CREATE TABLE IF NOT EXISTS observations (
|
|
126
|
+
domain TEXT NOT NULL,
|
|
127
|
+
entity_id TEXT NOT NULL,
|
|
128
|
+
payload TEXT NOT NULL DEFAULT '{}',
|
|
129
|
+
observed_at TEXT NOT NULL,
|
|
130
|
+
reported_by TEXT,
|
|
131
|
+
PRIMARY KEY (domain, entity_id)
|
|
132
|
+
)
|
|
133
|
+
"""
|
|
134
|
+
)
|
|
135
|
+
conn.execute(
|
|
136
|
+
"CREATE INDEX IF NOT EXISTS idx_observations_domain "
|
|
137
|
+
"ON observations(domain, observed_at DESC)"
|
|
138
|
+
)
|
|
139
|
+
conn.commit()
|
|
140
|
+
return conn
|
|
141
|
+
|
|
142
|
+
# ------------------------------------------------------------------
|
|
143
|
+
# Writes
|
|
144
|
+
# ------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
def record(
|
|
147
|
+
self,
|
|
148
|
+
domain: str,
|
|
149
|
+
entity_id: str,
|
|
150
|
+
payload: Dict[str, Any],
|
|
151
|
+
reported_by: Optional[str] = None,
|
|
152
|
+
observed_at: Optional[datetime] = None,
|
|
153
|
+
) -> Observation:
|
|
154
|
+
"""Upsert the latest snapshot for one entity."""
|
|
155
|
+
observed_at = observed_at or datetime.now(timezone.utc)
|
|
156
|
+
self._db.execute(
|
|
157
|
+
"""
|
|
158
|
+
INSERT INTO observations (domain, entity_id, payload, observed_at, reported_by)
|
|
159
|
+
VALUES (?, ?, ?, ?, ?)
|
|
160
|
+
ON CONFLICT(domain, entity_id) DO UPDATE SET
|
|
161
|
+
payload = excluded.payload,
|
|
162
|
+
observed_at = excluded.observed_at,
|
|
163
|
+
reported_by = excluded.reported_by
|
|
164
|
+
""",
|
|
165
|
+
[domain, entity_id, json.dumps(payload), observed_at.isoformat(), reported_by],
|
|
166
|
+
)
|
|
167
|
+
self._db.commit()
|
|
168
|
+
return Observation(domain, entity_id, payload, observed_at, reported_by)
|
|
169
|
+
|
|
170
|
+
def record_batch(
|
|
171
|
+
self,
|
|
172
|
+
domain: str,
|
|
173
|
+
observations: List[Dict[str, Any]],
|
|
174
|
+
reported_by: Optional[str] = None,
|
|
175
|
+
) -> int:
|
|
176
|
+
"""Record many snapshots for one domain. Returns rows written."""
|
|
177
|
+
written = 0
|
|
178
|
+
for obs in observations:
|
|
179
|
+
entity_id = obs.get("entity_id")
|
|
180
|
+
if not entity_id:
|
|
181
|
+
continue
|
|
182
|
+
observed_at = obs.get("observed_at")
|
|
183
|
+
if isinstance(observed_at, str):
|
|
184
|
+
try:
|
|
185
|
+
observed_at = datetime.fromisoformat(observed_at.replace("Z", "+00:00"))
|
|
186
|
+
except (ValueError, TypeError):
|
|
187
|
+
continue # An invalid supplied time is not a fresh observation.
|
|
188
|
+
self.record(
|
|
189
|
+
domain=domain,
|
|
190
|
+
entity_id=str(entity_id),
|
|
191
|
+
payload=obs.get("payload") or {},
|
|
192
|
+
reported_by=reported_by,
|
|
193
|
+
observed_at=observed_at,
|
|
194
|
+
)
|
|
195
|
+
written += 1
|
|
196
|
+
return written
|
|
197
|
+
|
|
198
|
+
def delete(self, domain: str, entity_id: str) -> bool:
|
|
199
|
+
cur = self._db.execute(
|
|
200
|
+
"DELETE FROM observations WHERE domain = ? AND entity_id = ?",
|
|
201
|
+
[domain, entity_id],
|
|
202
|
+
)
|
|
203
|
+
self._db.commit()
|
|
204
|
+
return cur.rowcount > 0
|
|
205
|
+
|
|
206
|
+
def prune(self, older_than_days: float = 30.0) -> int:
|
|
207
|
+
"""Drop snapshots nothing has refreshed in a long time."""
|
|
208
|
+
cutoff = datetime.now(timezone.utc).timestamp() - older_than_days * 86400
|
|
209
|
+
cutoff_iso = datetime.fromtimestamp(cutoff, tz=timezone.utc).isoformat()
|
|
210
|
+
cur = self._db.execute(
|
|
211
|
+
"DELETE FROM observations WHERE observed_at < ?", [cutoff_iso]
|
|
212
|
+
)
|
|
213
|
+
self._db.commit()
|
|
214
|
+
return cur.rowcount
|
|
215
|
+
|
|
216
|
+
# ------------------------------------------------------------------
|
|
217
|
+
# Reads
|
|
218
|
+
# ------------------------------------------------------------------
|
|
219
|
+
|
|
220
|
+
def get(self, domain: str, entity_id: str) -> Optional[Observation]:
|
|
221
|
+
cur = self._db.execute(
|
|
222
|
+
"SELECT * FROM observations WHERE domain = ? AND entity_id = ?",
|
|
223
|
+
[domain, entity_id],
|
|
224
|
+
)
|
|
225
|
+
row = cur.fetchone()
|
|
226
|
+
return Observation.from_row(dict(row)) if row else None
|
|
227
|
+
|
|
228
|
+
def list(self, domain: str, limit: int = 100) -> List[Observation]:
|
|
229
|
+
cur = self._db.execute(
|
|
230
|
+
"SELECT * FROM observations WHERE domain = ? "
|
|
231
|
+
"ORDER BY observed_at DESC LIMIT ?",
|
|
232
|
+
[domain, limit],
|
|
233
|
+
)
|
|
234
|
+
return [Observation.from_row(dict(r)) for r in cur.fetchall()]
|
|
235
|
+
|
|
236
|
+
def domain_age_seconds(self, domain: str) -> Optional[float]:
|
|
237
|
+
"""Seconds since the NEWEST observation in a domain.
|
|
238
|
+
|
|
239
|
+
None when the domain has never been observed — callers treat
|
|
240
|
+
that as maximally stale (a sync is needed to prime it).
|
|
241
|
+
"""
|
|
242
|
+
cur = self._db.execute(
|
|
243
|
+
"SELECT MAX(observed_at) AS newest FROM observations WHERE domain = ?",
|
|
244
|
+
[domain],
|
|
245
|
+
)
|
|
246
|
+
row = cur.fetchone()
|
|
247
|
+
newest = row["newest"] if row else None
|
|
248
|
+
if not newest:
|
|
249
|
+
return None
|
|
250
|
+
try:
|
|
251
|
+
stamp = datetime.fromisoformat(str(newest).replace("Z", "+00:00"))
|
|
252
|
+
except (ValueError, TypeError):
|
|
253
|
+
return None
|
|
254
|
+
if stamp.tzinfo is None:
|
|
255
|
+
stamp = stamp.replace(tzinfo=timezone.utc)
|
|
256
|
+
return (datetime.now(timezone.utc) - stamp).total_seconds()
|
|
257
|
+
|
|
258
|
+
def summary(self) -> Dict[str, Dict[str, Any]]:
|
|
259
|
+
"""Per-domain counts and freshness."""
|
|
260
|
+
cur = self._db.execute(
|
|
261
|
+
"SELECT domain, COUNT(*) AS n, MAX(observed_at) AS newest "
|
|
262
|
+
"FROM observations GROUP BY domain"
|
|
263
|
+
)
|
|
264
|
+
out: Dict[str, Dict[str, Any]] = {}
|
|
265
|
+
for row in cur.fetchall():
|
|
266
|
+
out[row["domain"]] = {
|
|
267
|
+
"count": row["n"],
|
|
268
|
+
"newest_observed_at": row["newest"],
|
|
269
|
+
"age_seconds": self.domain_age_seconds(row["domain"]),
|
|
270
|
+
}
|
|
271
|
+
return out
|
|
272
|
+
|
|
273
|
+
def close(self) -> None:
|
|
274
|
+
try:
|
|
275
|
+
self._db.close()
|
|
276
|
+
except Exception:
|
|
277
|
+
pass
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""Pattern extraction — pull patterns from the world model."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def extract_patterns(
|
|
12
|
+
world_store: Any = None,
|
|
13
|
+
pattern_store: Any = None,
|
|
14
|
+
) -> Dict[str, Any]:
|
|
15
|
+
"""Run pattern extraction against the world model.
|
|
16
|
+
|
|
17
|
+
If world_store or pattern_store is None, returns a no-op result.
|
|
18
|
+
Returns {"new": N, "updated": M, "total": T}.
|
|
19
|
+
"""
|
|
20
|
+
if world_store is None or pattern_store is None:
|
|
21
|
+
return {"new": 0, "updated": 0, "total": 0, "reason": "stores_not_wired"}
|
|
22
|
+
|
|
23
|
+
results = {"new": 0, "updated": 0, "total": 0}
|
|
24
|
+
|
|
25
|
+
# Extract entity cooccurrence patterns.
|
|
26
|
+
try:
|
|
27
|
+
cooc = _extract_entity_cooccurrence(world_store)
|
|
28
|
+
for pattern in cooc:
|
|
29
|
+
result = pattern_store.create_pattern(**pattern)
|
|
30
|
+
if result.get("frequency", 1) > 1:
|
|
31
|
+
results["updated"] += 1
|
|
32
|
+
else:
|
|
33
|
+
results["new"] += 1
|
|
34
|
+
except Exception as exc:
|
|
35
|
+
logger.debug("entity cooccurrence extraction failed: %s", exc)
|
|
36
|
+
|
|
37
|
+
# Extract relation frequency patterns.
|
|
38
|
+
try:
|
|
39
|
+
rels = _extract_relation_frequency(world_store)
|
|
40
|
+
for pattern in rels:
|
|
41
|
+
result = pattern_store.create_pattern(**pattern)
|
|
42
|
+
if result.get("frequency", 1) > 1:
|
|
43
|
+
results["updated"] += 1
|
|
44
|
+
else:
|
|
45
|
+
results["new"] += 1
|
|
46
|
+
except Exception as exc:
|
|
47
|
+
logger.debug("relation frequency extraction failed: %s", exc)
|
|
48
|
+
|
|
49
|
+
# Extract attribute cluster patterns.
|
|
50
|
+
try:
|
|
51
|
+
attrs = _extract_attribute_clusters(world_store)
|
|
52
|
+
for pattern in attrs:
|
|
53
|
+
result = pattern_store.create_pattern(**pattern)
|
|
54
|
+
if result.get("frequency", 1) > 1:
|
|
55
|
+
results["updated"] += 1
|
|
56
|
+
else:
|
|
57
|
+
results["new"] += 1
|
|
58
|
+
except Exception as exc:
|
|
59
|
+
logger.debug("attribute cluster extraction failed: %s", exc)
|
|
60
|
+
|
|
61
|
+
total = pattern_store.list_patterns(active_only=True)
|
|
62
|
+
results["total"] = total.get("total", 0)
|
|
63
|
+
return results
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _extract_entity_cooccurrence(world_store: Any) -> List[Dict[str, Any]]:
|
|
67
|
+
"""Find entities that share relationships."""
|
|
68
|
+
patterns: List[Dict[str, Any]] = []
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
entities = world_store.list_entities(limit=200)
|
|
72
|
+
except Exception as exc:
|
|
73
|
+
logger.debug("list_entities failed in pattern extraction: %s", exc)
|
|
74
|
+
return patterns
|
|
75
|
+
|
|
76
|
+
if not entities:
|
|
77
|
+
return patterns
|
|
78
|
+
|
|
79
|
+
# Group entities by relationship target.
|
|
80
|
+
target_map: Dict[str, List[str]] = {}
|
|
81
|
+
for entity in entities:
|
|
82
|
+
name = entity.get("name", "")
|
|
83
|
+
etype = entity.get("entity_type", "")
|
|
84
|
+
if not name:
|
|
85
|
+
continue
|
|
86
|
+
# Use entity type as a grouping key for cooccurrence.
|
|
87
|
+
if etype not in target_map:
|
|
88
|
+
target_map[etype] = []
|
|
89
|
+
target_map[etype].append(name)
|
|
90
|
+
|
|
91
|
+
# Entities that share a type are cooccurring.
|
|
92
|
+
for etype, names in target_map.items():
|
|
93
|
+
if len(names) < 2:
|
|
94
|
+
continue
|
|
95
|
+
# Create pairwise patterns for top entities.
|
|
96
|
+
for i in range(min(len(names), 5)):
|
|
97
|
+
for j in range(i + 1, min(len(names), 5)):
|
|
98
|
+
key = f"cooc:{sorted([names[i], names[j]])[0]}→{sorted([names[i], names[j]])[1]}"
|
|
99
|
+
patterns.append({
|
|
100
|
+
"pattern_type": "entity_cooccurrence",
|
|
101
|
+
"description": f"{names[i]} and {names[j]} both appear as {etype}",
|
|
102
|
+
"pattern_key": key,
|
|
103
|
+
"confidence": 0.6,
|
|
104
|
+
"source": "extraction",
|
|
105
|
+
"metadata": {"type": etype, "entities": [names[i], names[j]]},
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
return patterns
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _extract_relation_frequency(world_store: Any) -> List[Dict[str, Any]]:
|
|
112
|
+
"""Find frequently occurring relationship types."""
|
|
113
|
+
patterns: List[Dict[str, Any]] = []
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
entities = world_store.list_entities(limit=200)
|
|
117
|
+
except Exception as exc:
|
|
118
|
+
logger.debug("list_entities failed in pattern extraction: %s", exc)
|
|
119
|
+
return patterns
|
|
120
|
+
|
|
121
|
+
# Count relationship types.
|
|
122
|
+
rel_counts: Dict[str, int] = {}
|
|
123
|
+
for entity in entities:
|
|
124
|
+
rels = entity.get("relationships", [])
|
|
125
|
+
if isinstance(rels, list):
|
|
126
|
+
for rel in rels:
|
|
127
|
+
rtype = rel.get("type", "") if isinstance(rel, dict) else str(rel)
|
|
128
|
+
if rtype:
|
|
129
|
+
rel_counts[rtype] = rel_counts.get(rtype, 0) + 1
|
|
130
|
+
|
|
131
|
+
for rtype, count in rel_counts.items():
|
|
132
|
+
if count < 2:
|
|
133
|
+
continue
|
|
134
|
+
patterns.append({
|
|
135
|
+
"pattern_type": "relation_frequency",
|
|
136
|
+
"description": f"Relationship type '{rtype}' appears {count} times",
|
|
137
|
+
"pattern_key": f"rel_freq:{rtype}",
|
|
138
|
+
"frequency": count,
|
|
139
|
+
"confidence": min(1.0, 0.3 + count * 0.1),
|
|
140
|
+
"source": "extraction",
|
|
141
|
+
"metadata": {"relation_type": rtype, "count": count},
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
return patterns
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _extract_attribute_clusters(world_store: Any) -> List[Dict[str, Any]]:
|
|
148
|
+
"""Find entities of the same type that share attribute keys."""
|
|
149
|
+
patterns: List[Dict[str, Any]] = []
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
entities = world_store.list_entities(limit=200)
|
|
153
|
+
except Exception as exc:
|
|
154
|
+
logger.debug("list_entities failed in pattern extraction: %s", exc)
|
|
155
|
+
return patterns
|
|
156
|
+
|
|
157
|
+
# Group by type, collect attribute keys.
|
|
158
|
+
type_attrs: Dict[str, Dict[str, int]] = {}
|
|
159
|
+
for entity in entities:
|
|
160
|
+
etype = entity.get("entity_type", "")
|
|
161
|
+
attrs = entity.get("attributes", {})
|
|
162
|
+
if not isinstance(attrs, dict):
|
|
163
|
+
continue
|
|
164
|
+
if etype not in type_attrs:
|
|
165
|
+
type_attrs[etype] = {}
|
|
166
|
+
for key in attrs:
|
|
167
|
+
type_attrs[etype][key] = type_attrs[etype].get(key, 0) + 1
|
|
168
|
+
|
|
169
|
+
for etype, attr_counts in type_attrs.items():
|
|
170
|
+
if len(attr_counts) < 2:
|
|
171
|
+
continue
|
|
172
|
+
# Only include attributes that appear in multiple entities.
|
|
173
|
+
common = [k for k, v in attr_counts.items() if v >= 2]
|
|
174
|
+
if len(common) < 2:
|
|
175
|
+
continue
|
|
176
|
+
common.sort()
|
|
177
|
+
key = f"attr_cluster:{etype}:{','.join(common[:5])}"
|
|
178
|
+
patterns.append({
|
|
179
|
+
"pattern_type": "attribute_cluster",
|
|
180
|
+
"description": f"{etype} entities share attributes: {', '.join(common[:5])}",
|
|
181
|
+
"pattern_key": key,
|
|
182
|
+
"confidence": 0.7,
|
|
183
|
+
"source": "extraction",
|
|
184
|
+
"metadata": {"entity_type": etype, "attributes": common[:10]},
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
return patterns
|