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,252 @@
|
|
|
1
|
+
"""Metadata receipts for a transport-owned durable inbox, in the communications DB.
|
|
2
|
+
|
|
3
|
+
No message bodies or attachment bytes belong here. A claimed handoff is deliberately
|
|
4
|
+
not retried: only an actual canonical turn receipt can settle its processing.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import hashlib
|
|
9
|
+
import json
|
|
10
|
+
import sqlite3
|
|
11
|
+
import time
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def ensure_schema(conn: sqlite3.Connection) -> None:
|
|
15
|
+
conn.executescript('''
|
|
16
|
+
CREATE TABLE IF NOT EXISTS transport_ingress (
|
|
17
|
+
receipt_id TEXT PRIMARY KEY, producer TEXT NOT NULL,
|
|
18
|
+
account_id TEXT NOT NULL, epoch TEXT NOT NULL, sequence INTEGER NOT NULL,
|
|
19
|
+
event_id TEXT NOT NULL, contact_id TEXT, occurred_at REAL NOT NULL,
|
|
20
|
+
journal_ref TEXT NOT NULL, payload_digest TEXT NOT NULL,
|
|
21
|
+
media_available INTEGER NOT NULL, metadata_json TEXT NOT NULL,
|
|
22
|
+
state TEXT NOT NULL DEFAULT 'admitted', batch_id TEXT,
|
|
23
|
+
native_turn_json TEXT, source_versions_json TEXT,
|
|
24
|
+
outcome TEXT, created_at REAL NOT NULL, completed_at REAL,
|
|
25
|
+
UNIQUE(producer,account_id,epoch,sequence));
|
|
26
|
+
CREATE INDEX IF NOT EXISTS transport_ingress_native
|
|
27
|
+
ON transport_ingress(native_turn_json,state);
|
|
28
|
+
CREATE TABLE IF NOT EXISTS transport_ingress_coverage (
|
|
29
|
+
producer TEXT NOT NULL, account_id TEXT NOT NULL, epoch TEXT NOT NULL,
|
|
30
|
+
connected_since REAL, observed_at REAL NOT NULL, watermark INTEGER NOT NULL,
|
|
31
|
+
connected INTEGER NOT NULL, unavailable INTEGER NOT NULL,
|
|
32
|
+
sequence_floor INTEGER NOT NULL DEFAULT 0,
|
|
33
|
+
PRIMARY KEY(producer,account_id));
|
|
34
|
+
''')
|
|
35
|
+
columns = {row[1] for row in conn.execute('PRAGMA table_info(transport_ingress_coverage)')}
|
|
36
|
+
if 'sequence_floor' not in columns:
|
|
37
|
+
conn.execute('ALTER TABLE transport_ingress_coverage ADD COLUMN sequence_floor INTEGER NOT NULL DEFAULT 0')
|
|
38
|
+
conn.commit()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _json(value):
|
|
42
|
+
return json.dumps(value, sort_keys=True, separators=(',', ':'), allow_nan=False)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _text(value, name, limit=512):
|
|
46
|
+
if not isinstance(value, str) or not value or len(value) > limit:
|
|
47
|
+
raise ValueError('invalid_ingress_' + name)
|
|
48
|
+
return value
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class TransportIngress:
|
|
52
|
+
"""Use the caller's existing CommsLog connection and transaction lifetime."""
|
|
53
|
+
|
|
54
|
+
def __init__(self, conn: sqlite3.Connection):
|
|
55
|
+
self.conn = conn
|
|
56
|
+
|
|
57
|
+
def get(self, receipt_id):
|
|
58
|
+
row = self.conn.execute('SELECT * FROM transport_ingress WHERE receipt_id=?',
|
|
59
|
+
(receipt_id,)).fetchone()
|
|
60
|
+
return dict(row) if row else None
|
|
61
|
+
|
|
62
|
+
def admit(self, *, producer, account_id, epoch, sequence, event_id, contact_id,
|
|
63
|
+
occurred_at, journal_ref, payload_digest, media_available, metadata=None,
|
|
64
|
+
now=None):
|
|
65
|
+
now = time.time() if now is None else float(now)
|
|
66
|
+
for name, value in [('producer', producer), ('account_id', account_id),
|
|
67
|
+
('epoch', epoch), ('event_id', event_id), ('journal_ref', journal_ref)]:
|
|
68
|
+
_text(value, name)
|
|
69
|
+
if (type(sequence) is not int or sequence < 1 or type(media_available) is not bool
|
|
70
|
+
or not 0 < float(occurred_at) <= now + 1
|
|
71
|
+
or len(payload_digest) != 64 or any(c not in '0123456789abcdef' for c in payload_digest)):
|
|
72
|
+
raise ValueError('invalid_ingress_envelope')
|
|
73
|
+
if contact_id is not None:
|
|
74
|
+
_text(contact_id, 'contact_id', 128)
|
|
75
|
+
metadata = dict(metadata or {})
|
|
76
|
+
if set(metadata) - {'channel', 'sender_ref', 'reply_to_ref', 'from_owner', 'is_group'}:
|
|
77
|
+
raise ValueError('ingress_metadata_only')
|
|
78
|
+
if len(_json(metadata)) > 2048:
|
|
79
|
+
raise ValueError('ingress_metadata_too_large')
|
|
80
|
+
receipt_id = 'ingress:' + hashlib.sha256(_json([producer, account_id, epoch, sequence]).encode()).hexdigest()
|
|
81
|
+
fields = dict(producer=producer, account_id=account_id, epoch=epoch, sequence=sequence,
|
|
82
|
+
event_id=event_id, contact_id=contact_id, occurred_at=float(occurred_at),
|
|
83
|
+
journal_ref=journal_ref, payload_digest=payload_digest,
|
|
84
|
+
media_available=int(media_available), metadata_json=_json(metadata))
|
|
85
|
+
with self.conn:
|
|
86
|
+
old = self.get(receipt_id)
|
|
87
|
+
if old:
|
|
88
|
+
if old['state'] == 'erased':
|
|
89
|
+
if any(old[key] != fields[key] for key in ('event_id', 'journal_ref', 'payload_digest')):
|
|
90
|
+
raise ValueError('ingress_event_conflict')
|
|
91
|
+
return self.view(old)
|
|
92
|
+
# A retained provider event can acquire its missing attachment later.
|
|
93
|
+
# No claimed turn or identity/timestamp may be rewritten by repair.
|
|
94
|
+
repair = (old['state'] == 'admitted' and not old['media_available']
|
|
95
|
+
and fields['media_available'] == 1
|
|
96
|
+
and all(old[key] == val for key, val in fields.items()
|
|
97
|
+
if key not in {'payload_digest', 'media_available'}))
|
|
98
|
+
if repair:
|
|
99
|
+
self.conn.execute('UPDATE transport_ingress SET payload_digest=?,media_available=1 '
|
|
100
|
+
'WHERE receipt_id=?', (payload_digest, receipt_id))
|
|
101
|
+
return self.view(self.get(receipt_id))
|
|
102
|
+
if any(old[key] != val for key, val in fields.items()):
|
|
103
|
+
raise ValueError('ingress_event_conflict')
|
|
104
|
+
return self.view(old)
|
|
105
|
+
columns = ','.join(fields)
|
|
106
|
+
self.conn.execute(f'INSERT INTO transport_ingress (receipt_id,{columns},created_at) '
|
|
107
|
+
f'VALUES ({",".join("?" for _ in range(len(fields)+2))})',
|
|
108
|
+
(receipt_id, *fields.values(), now))
|
|
109
|
+
return self.view(self.get(receipt_id))
|
|
110
|
+
|
|
111
|
+
@staticmethod
|
|
112
|
+
def view(row):
|
|
113
|
+
return {key: row[key] for key in ('receipt_id', 'state', 'batch_id', 'journal_ref',
|
|
114
|
+
'media_available', 'source_versions_json', 'outcome')}
|
|
115
|
+
|
|
116
|
+
def handoff(self, *, producer, receipt_ids, batch_id, native_turn=None):
|
|
117
|
+
_text(batch_id, 'batch_id', 128)
|
|
118
|
+
ids = sorted(set(receipt_ids))
|
|
119
|
+
if not ids or len(ids) > 100:
|
|
120
|
+
raise ValueError('invalid_ingress_batch')
|
|
121
|
+
native = None
|
|
122
|
+
if native_turn is not None:
|
|
123
|
+
if set(native_turn) != {'session_id', 'task_id', 'turn_id'}:
|
|
124
|
+
raise ValueError('invalid_ingress_native_turn')
|
|
125
|
+
native = _json({key: _text(value, key, 256) for key, value in native_turn.items()})
|
|
126
|
+
with self.conn:
|
|
127
|
+
self.conn.execute('BEGIN IMMEDIATE')
|
|
128
|
+
rows = [self.get(receipt) for receipt in ids]
|
|
129
|
+
if any(not row or row['producer'] != producer for row in rows):
|
|
130
|
+
raise ValueError('ingress_receipt_scope_mismatch')
|
|
131
|
+
if any(row['state'] not in {'admitted', 'handed_off'} or not row['media_available'] for row in rows):
|
|
132
|
+
raise ValueError('ingress_not_ready')
|
|
133
|
+
if any(row['batch_id'] not in (None, batch_id) for row in rows):
|
|
134
|
+
raise ValueError('ingress_handoff_conflict')
|
|
135
|
+
if native and any(row['native_turn_json'] not in (None, native) for row in rows):
|
|
136
|
+
raise ValueError('ingress_native_turn_conflict')
|
|
137
|
+
may_dispatch = all(row['state'] == 'admitted' for row in rows)
|
|
138
|
+
if native and may_dispatch:
|
|
139
|
+
raise ValueError('ingress_handoff_required_before_native_binding')
|
|
140
|
+
for row in rows:
|
|
141
|
+
self.conn.execute('UPDATE transport_ingress SET state=?,batch_id=?, '
|
|
142
|
+
'native_turn_json=COALESCE(native_turn_json,?) WHERE receipt_id=?',
|
|
143
|
+
('handed_off', batch_id, native, row['receipt_id']))
|
|
144
|
+
return {'receipt_ids': ids, 'batch_id': batch_id, 'may_dispatch': may_dispatch,
|
|
145
|
+
'native_bound': native is not None, 'processing_complete': False}
|
|
146
|
+
|
|
147
|
+
def complete(self, *, native_turn, source_versions, outcome, now=None):
|
|
148
|
+
"""Call only after canonical source admission; not exposed to a model tool."""
|
|
149
|
+
if not source_versions or outcome not in {'captured', 'interrupted', 'failed'}:
|
|
150
|
+
raise ValueError('canonical_ingress_completion_required')
|
|
151
|
+
for key, value in source_versions.items():
|
|
152
|
+
_text(key, 'source_id'); _text(value, 'source_version')
|
|
153
|
+
stamp = time.time() if now is None else now
|
|
154
|
+
rows = self.conn.execute('SELECT * FROM transport_ingress WHERE native_turn_json=?',
|
|
155
|
+
(_json(native_turn),)).fetchall()
|
|
156
|
+
with self.conn:
|
|
157
|
+
for row in rows:
|
|
158
|
+
if row['state'] == 'erased':
|
|
159
|
+
continue
|
|
160
|
+
if row['state'] == 'completed':
|
|
161
|
+
if row['source_versions_json'] != _json(source_versions) or row['outcome'] != outcome:
|
|
162
|
+
raise ValueError('ingress_completion_conflict')
|
|
163
|
+
continue
|
|
164
|
+
self.conn.execute('UPDATE transport_ingress SET state=?,source_versions_json=?, '
|
|
165
|
+
'outcome=?,completed_at=? WHERE receipt_id=?',
|
|
166
|
+
('completed', _json(source_versions), outcome, stamp, row['receipt_id']))
|
|
167
|
+
return [self.view(self.get(row['receipt_id'])) for row in rows]
|
|
168
|
+
|
|
169
|
+
def for_canonical_turn(self, *, turn_id, contact_id):
|
|
170
|
+
"""Unique native tuple bound by the transport producer, never a text match."""
|
|
171
|
+
rows = self.conn.execute("SELECT * FROM transport_ingress WHERE contact_id=? "
|
|
172
|
+
"AND native_turn_json IS NOT NULL AND state!='erased'", (contact_id,)).fetchall()
|
|
173
|
+
rows = [row for row in rows if json.loads(row['native_turn_json']).get('turn_id') == turn_id]
|
|
174
|
+
tuples = {row['native_turn_json'] for row in rows}
|
|
175
|
+
return ([dict(row) for row in rows] if len(tuples) == 1 else [])
|
|
176
|
+
|
|
177
|
+
def receipts(self, *, producer, receipt_ids):
|
|
178
|
+
if len(receipt_ids) > 100:
|
|
179
|
+
raise ValueError('invalid_ingress_batch')
|
|
180
|
+
rows = [self.get(receipt) for receipt in receipt_ids]
|
|
181
|
+
if any(not row or row['producer'] != producer for row in rows):
|
|
182
|
+
raise ValueError('ingress_receipt_scope_mismatch')
|
|
183
|
+
return [self.view(row) for row in rows]
|
|
184
|
+
|
|
185
|
+
def erase_sources(self, source_ids):
|
|
186
|
+
"""Return locators root must erase at the sole payload owner, idempotently."""
|
|
187
|
+
wanted = set(source_ids)
|
|
188
|
+
rows = self.conn.execute('SELECT * FROM transport_ingress WHERE source_versions_json IS NOT NULL '
|
|
189
|
+
'OR native_turn_json IS NOT NULL').fetchall()
|
|
190
|
+
selected = [row for row in rows if wanted.intersection(json.loads(row['source_versions_json'] or '{}'))
|
|
191
|
+
or json.loads(row['native_turn_json'] or '{}').get('turn_id') in wanted]
|
|
192
|
+
with self.conn:
|
|
193
|
+
for row in selected:
|
|
194
|
+
self.conn.execute("UPDATE transport_ingress SET state='erased',metadata_json='{}',"
|
|
195
|
+
"contact_id=NULL WHERE receipt_id=?", (row['receipt_id'],))
|
|
196
|
+
return [{'receipt_id': row['receipt_id'], 'journal_ref': row['journal_ref']} for row in selected]
|
|
197
|
+
|
|
198
|
+
def observe_coverage(self, *, producer, account_id, epoch, connected_since, observed_at,
|
|
199
|
+
watermark, connected, unavailable, sequence_floor=0, now=None):
|
|
200
|
+
stamp = time.time() if now is None else float(now)
|
|
201
|
+
if (type(watermark) is not int or watermark < 0 or type(connected) is not bool
|
|
202
|
+
or type(sequence_floor) is not int or not 0 <= sequence_floor <= watermark
|
|
203
|
+
or type(unavailable) is not int or unavailable < 0
|
|
204
|
+
or not 0 < float(observed_at) <= stamp + 1
|
|
205
|
+
or (connected and (connected_since is None or not 0 < connected_since <= observed_at))):
|
|
206
|
+
raise ValueError('invalid_ingress_coverage')
|
|
207
|
+
old = self.conn.execute('SELECT * FROM transport_ingress_coverage WHERE producer=? AND account_id=?',
|
|
208
|
+
(producer, account_id)).fetchone()
|
|
209
|
+
if old and (observed_at < old['observed_at'] or (epoch == old['epoch'] and watermark < old['watermark'])):
|
|
210
|
+
raise ValueError('ingress_coverage_regression')
|
|
211
|
+
with self.conn:
|
|
212
|
+
self.conn.execute('INSERT OR REPLACE INTO transport_ingress_coverage VALUES (?,?,?,?,?,?,?,?,?)',
|
|
213
|
+
(producer, account_id, epoch, connected_since, observed_at,
|
|
214
|
+
watermark, int(connected), unavailable, sequence_floor))
|
|
215
|
+
|
|
216
|
+
def coverage(self, *, producer, account_id, contact_id, since, now=None, max_age=5,
|
|
217
|
+
until=None, observation=None):
|
|
218
|
+
"""Read current coverage or recheck a retained internal observation.
|
|
219
|
+
|
|
220
|
+
A fixed horizon excludes later activity from that earlier interval.
|
|
221
|
+
Rechecking a stored observation still queries current intake/erasure
|
|
222
|
+
records; it never treats a retained coverage hash as timeless proof.
|
|
223
|
+
"""
|
|
224
|
+
now = time.time() if now is None else now
|
|
225
|
+
row = observation if observation is not None else self.conn.execute(
|
|
226
|
+
'SELECT * FROM transport_ingress_coverage WHERE producer=? AND account_id=?',
|
|
227
|
+
(producer, account_id)).fetchone()
|
|
228
|
+
if observation is not None and (row['producer'] != producer or row['account_id'] != account_id):
|
|
229
|
+
raise ValueError('coverage_observation_scope_mismatch')
|
|
230
|
+
if until is not None and not since <= until <= now:
|
|
231
|
+
raise ValueError('invalid_coverage_interval')
|
|
232
|
+
reasons = []
|
|
233
|
+
if not row or not row['connected'] or row['connected_since'] is None or row['connected_since'] > since:
|
|
234
|
+
reasons.append('connection_interval_unknown')
|
|
235
|
+
if not row or now - row['observed_at'] > max_age:
|
|
236
|
+
reasons.append('intake_observation_stale')
|
|
237
|
+
if row:
|
|
238
|
+
admitted = self.conn.execute('SELECT COUNT(*) FROM transport_ingress WHERE producer=? '
|
|
239
|
+
'AND account_id=? AND epoch=? AND sequence>? AND sequence<=?',
|
|
240
|
+
(producer, account_id, row['epoch'], row['sequence_floor'], row['watermark'])).fetchone()[0]
|
|
241
|
+
if admitted != row['watermark'] - row['sequence_floor'] or row['unavailable']:
|
|
242
|
+
reasons.append('intake_gap')
|
|
243
|
+
activity = self.conn.execute('SELECT receipt_id,state,metadata_json FROM transport_ingress '
|
|
244
|
+
'WHERE producer=? AND account_id=? AND occurred_at>=? AND '
|
|
245
|
+
'(contact_id=? OR contact_id IS NULL)'+(' AND occurred_at<=?' if until is not None else ''),
|
|
246
|
+
(producer, account_id, since, contact_id, *([until] if until is not None else []))).fetchall()
|
|
247
|
+
activity = [item for item in activity if not json.loads(item['metadata_json']).get('from_owner')]
|
|
248
|
+
if activity:
|
|
249
|
+
reasons.append('recipient_activity_requires_review')
|
|
250
|
+
return {'observed': not reasons, 'reasons': reasons, 'observed_through': row['observed_at'] if row else None,
|
|
251
|
+
'watermark': row['watermark'] if row else None, 'activity_receipts': [r['receipt_id'] for r in activity],
|
|
252
|
+
'effect_authorized': False}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
"""Colony Contacts — World Model Bridge.
|
|
2
|
+
|
|
3
|
+
Synchronises Neo4j :Person nodes with the SQLite ContactStore so that
|
|
4
|
+
discovered people become visible to the contacts API while preserving
|
|
5
|
+
the architectural separation between curated and discovered contacts.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from typing import Any, Dict, List, Optional
|
|
12
|
+
|
|
13
|
+
from .models import Contact
|
|
14
|
+
from .store import SQLiteContactStore, _name_similarity, _normalize_email, _normalize_phone
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger("colony.contacts.world_bridge")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _person_handle_gaps(person: Dict[str, Any]) -> List[tuple[str, str]]:
|
|
20
|
+
"""Extract (gateway, address) pairs from a Person node's properties."""
|
|
21
|
+
handles: List[tuple[str, str]] = []
|
|
22
|
+
phone = person.get("phone")
|
|
23
|
+
if phone:
|
|
24
|
+
norm = _normalize_phone(str(phone))
|
|
25
|
+
if norm:
|
|
26
|
+
handles.append(("imessage", norm))
|
|
27
|
+
email = person.get("email")
|
|
28
|
+
if email:
|
|
29
|
+
norm = _normalize_email(str(email))
|
|
30
|
+
if norm:
|
|
31
|
+
handles.append(("email", norm))
|
|
32
|
+
return handles
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class WorldModelContactBridge:
|
|
36
|
+
"""Creates and maintains shadow contacts from Neo4j Person nodes.
|
|
37
|
+
|
|
38
|
+
Shadow contacts:
|
|
39
|
+
- trust_tier = "acquaintance"
|
|
40
|
+
- interaction_allowed = False
|
|
41
|
+
- import_source = "world_model"
|
|
42
|
+
- person_node_id = Person.id
|
|
43
|
+
|
|
44
|
+
The bridge deduplicates against existing curated contacts by
|
|
45
|
+
person_node_id, overlapping handles, or high name similarity.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
graph, # ColonyGraph instance
|
|
51
|
+
store: SQLiteContactStore,
|
|
52
|
+
min_signals: int = 2,
|
|
53
|
+
min_memories: int = 2,
|
|
54
|
+
name_match_threshold: float = 0.85,
|
|
55
|
+
) -> None:
|
|
56
|
+
self._graph = graph
|
|
57
|
+
self._store = store
|
|
58
|
+
self._min_signals = min_signals
|
|
59
|
+
self._min_memories = min_memories
|
|
60
|
+
self._name_match_threshold = name_match_threshold
|
|
61
|
+
|
|
62
|
+
# ── Public API ─────────────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
async def sync_person_to_contact(self, person_id: str) -> Optional[Contact]:
|
|
65
|
+
"""Ensure a single Person node is reflected as a contact.
|
|
66
|
+
|
|
67
|
+
Returns the existing or newly-created contact, or None if the
|
|
68
|
+
person does not meet the substance threshold.
|
|
69
|
+
"""
|
|
70
|
+
person = await self._graph.get_person(person_id)
|
|
71
|
+
if not person:
|
|
72
|
+
logger.debug("sync_person_to_contact: Person %s not found in graph", person_id)
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
if not person.get("name"):
|
|
76
|
+
logger.debug("sync_person_to_contact: Person %s has no name, skipping", person_id)
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
# Check substance threshold
|
|
80
|
+
handles = _person_handle_gaps(person)
|
|
81
|
+
has_handle = bool(handles)
|
|
82
|
+
|
|
83
|
+
# We also accept persons with signals/memories — graph query already
|
|
84
|
+
# filters, but for single-person sync we do a lightweight check.
|
|
85
|
+
if not has_handle:
|
|
86
|
+
# Need to verify signals/memories via graph
|
|
87
|
+
people = await self._graph.get_people_with_substance(
|
|
88
|
+
min_signals=self._min_signals,
|
|
89
|
+
min_memories=self._min_memories,
|
|
90
|
+
)
|
|
91
|
+
if not any(p["id"] == person_id for p in people):
|
|
92
|
+
logger.debug(
|
|
93
|
+
"sync_person_to_contact: Person %s lacks substance (no handle, < %d signals/memories)",
|
|
94
|
+
person_id, self._min_signals,
|
|
95
|
+
)
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
# Deduplication: check for existing contact
|
|
99
|
+
existing = await self._find_existing_contact(person_id, person.get("name"), handles)
|
|
100
|
+
if existing:
|
|
101
|
+
# Ensure person_node_id is linked
|
|
102
|
+
if not existing.person_node_id:
|
|
103
|
+
await self._store.update(existing.contact_id, person_node_id=person_id)
|
|
104
|
+
existing.person_node_id = person_id
|
|
105
|
+
logger.debug(
|
|
106
|
+
"sync_person_to_contact: Person %s matched existing contact %s",
|
|
107
|
+
person_id, existing.contact_id,
|
|
108
|
+
)
|
|
109
|
+
return existing
|
|
110
|
+
|
|
111
|
+
# Create shadow contact
|
|
112
|
+
contact = await self._store.create(
|
|
113
|
+
display_name=person.get("name"),
|
|
114
|
+
trust_tier="acquaintance",
|
|
115
|
+
interaction_allowed=False,
|
|
116
|
+
import_source="world_model",
|
|
117
|
+
notes=person.get("notes") or None,
|
|
118
|
+
)
|
|
119
|
+
await self._store.update(contact.contact_id, person_node_id=person_id)
|
|
120
|
+
contact.person_node_id = person_id
|
|
121
|
+
|
|
122
|
+
# Attach handles from Person properties
|
|
123
|
+
for gateway, address in handles:
|
|
124
|
+
try:
|
|
125
|
+
await self._store.add_handle(
|
|
126
|
+
contact.contact_id,
|
|
127
|
+
gateway,
|
|
128
|
+
address,
|
|
129
|
+
is_primary=True,
|
|
130
|
+
source="world_model",
|
|
131
|
+
confidence=0.7,
|
|
132
|
+
)
|
|
133
|
+
except ValueError:
|
|
134
|
+
# Handle already owned by another contact (possible race or
|
|
135
|
+
# overlap with curated contact created since dedup check)
|
|
136
|
+
logger.debug(
|
|
137
|
+
"sync_person_to_contact: handle conflict for %s %s on person %s",
|
|
138
|
+
gateway, address, person_id,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
logger.info(
|
|
142
|
+
"Created shadow contact %s for Person %s (%s)",
|
|
143
|
+
contact.contact_id, person_id, person.get("name"),
|
|
144
|
+
)
|
|
145
|
+
return contact
|
|
146
|
+
|
|
147
|
+
async def backfill_all_people(self) -> Dict[str, int]:
|
|
148
|
+
"""One-shot backfill of all substantive Person nodes into contacts.
|
|
149
|
+
|
|
150
|
+
Returns counters: {"created": int, "linked": int, "skipped": int}
|
|
151
|
+
"""
|
|
152
|
+
people = await self._graph.get_people_with_substance(
|
|
153
|
+
min_signals=self._min_signals,
|
|
154
|
+
min_memories=self._min_memories,
|
|
155
|
+
)
|
|
156
|
+
stats = {"created": 0, "linked": 0, "skipped": 0}
|
|
157
|
+
for person in people:
|
|
158
|
+
person_id = person["id"]
|
|
159
|
+
name = person.get("name")
|
|
160
|
+
handles = _person_handle_gaps(person)
|
|
161
|
+
|
|
162
|
+
existing = await self._find_existing_contact(person_id, name, handles)
|
|
163
|
+
if existing:
|
|
164
|
+
if not existing.person_node_id:
|
|
165
|
+
await self._store.update(existing.contact_id, person_node_id=person_id)
|
|
166
|
+
stats["linked"] += 1
|
|
167
|
+
else:
|
|
168
|
+
stats["skipped"] += 1
|
|
169
|
+
continue
|
|
170
|
+
|
|
171
|
+
contact = await self._store.create(
|
|
172
|
+
display_name=name,
|
|
173
|
+
trust_tier="acquaintance",
|
|
174
|
+
interaction_allowed=False,
|
|
175
|
+
import_source="world_model",
|
|
176
|
+
notes=person.get("notes") or None,
|
|
177
|
+
)
|
|
178
|
+
await self._store.update(contact.contact_id, person_node_id=person_id)
|
|
179
|
+
|
|
180
|
+
for gateway, address in handles:
|
|
181
|
+
try:
|
|
182
|
+
await self._store.add_handle(
|
|
183
|
+
contact.contact_id, gateway, address,
|
|
184
|
+
is_primary=True, source="world_model", confidence=0.7,
|
|
185
|
+
)
|
|
186
|
+
except ValueError:
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
stats["created"] += 1
|
|
190
|
+
logger.info(
|
|
191
|
+
"Backfill: created shadow contact %s for Person %s (%s)",
|
|
192
|
+
contact.contact_id, person_id, name,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
logger.info(
|
|
196
|
+
"Backfill complete: created=%d linked=%d skipped=%d total_people=%d",
|
|
197
|
+
stats["created"], stats["linked"], stats["skipped"], len(people),
|
|
198
|
+
)
|
|
199
|
+
return stats
|
|
200
|
+
|
|
201
|
+
async def absorb_discovered_contact(
|
|
202
|
+
self,
|
|
203
|
+
curated_contact_id: str,
|
|
204
|
+
discovered_contact: Contact,
|
|
205
|
+
) -> bool:
|
|
206
|
+
"""Absorb a discovered contact into a curated one.
|
|
207
|
+
|
|
208
|
+
Copies person_node_id and handles, then soft-deletes the discovered
|
|
209
|
+
contact. Returns True if anything was absorbed.
|
|
210
|
+
"""
|
|
211
|
+
if discovered_contact.import_source != "world_model":
|
|
212
|
+
return False
|
|
213
|
+
|
|
214
|
+
updates: Dict[str, Any] = {}
|
|
215
|
+
if discovered_contact.person_node_id and not (
|
|
216
|
+
await self._store.find_by_person_node_id(discovered_contact.person_node_id)
|
|
217
|
+
):
|
|
218
|
+
updates["person_node_id"] = discovered_contact.person_node_id
|
|
219
|
+
|
|
220
|
+
if updates:
|
|
221
|
+
await self._store.update(curated_contact_id, **updates)
|
|
222
|
+
|
|
223
|
+
# Migrate handles
|
|
224
|
+
handles = await self._store.get_handles(discovered_contact.contact_id)
|
|
225
|
+
for h in handles:
|
|
226
|
+
try:
|
|
227
|
+
await self._store.add_handle(
|
|
228
|
+
curated_contact_id,
|
|
229
|
+
h.gateway,
|
|
230
|
+
h.address,
|
|
231
|
+
is_primary=h.is_primary,
|
|
232
|
+
source=f"absorbed:{h.source}",
|
|
233
|
+
confidence=h.confidence,
|
|
234
|
+
)
|
|
235
|
+
except ValueError:
|
|
236
|
+
# Handle already on curated contact — fine
|
|
237
|
+
pass
|
|
238
|
+
|
|
239
|
+
await self._store.soft_delete(
|
|
240
|
+
discovered_contact.contact_id,
|
|
241
|
+
reason=f"absorbed_into:{curated_contact_id}",
|
|
242
|
+
performed_by="system",
|
|
243
|
+
)
|
|
244
|
+
await self._store.record_audit(
|
|
245
|
+
curated_contact_id,
|
|
246
|
+
"absorbed_discovered",
|
|
247
|
+
{
|
|
248
|
+
"absorbed_contact_id": discovered_contact.contact_id,
|
|
249
|
+
"person_node_id": discovered_contact.person_node_id,
|
|
250
|
+
},
|
|
251
|
+
)
|
|
252
|
+
logger.info(
|
|
253
|
+
"Absorbed discovered contact %s into curated %s",
|
|
254
|
+
discovered_contact.contact_id, curated_contact_id,
|
|
255
|
+
)
|
|
256
|
+
return True
|
|
257
|
+
|
|
258
|
+
async def prune_orphaned_shadows(self) -> int:
|
|
259
|
+
"""Soft-delete shadow contacts whose Person node no longer exists.
|
|
260
|
+
|
|
261
|
+
Returns the number of contacts pruned.
|
|
262
|
+
"""
|
|
263
|
+
# Fetch all shadow contacts with person_node_id
|
|
264
|
+
shadow_contacts = await self._store.list(
|
|
265
|
+
trust_tier="acquaintance", limit=10000,
|
|
266
|
+
)
|
|
267
|
+
if not shadow_contacts:
|
|
268
|
+
return 0
|
|
269
|
+
|
|
270
|
+
alive_ids = set(await self._graph.list_person_ids())
|
|
271
|
+
pruned = 0
|
|
272
|
+
for contact in shadow_contacts:
|
|
273
|
+
if not contact.person_node_id:
|
|
274
|
+
continue
|
|
275
|
+
if contact.person_node_id not in alive_ids:
|
|
276
|
+
await self._store.soft_delete(
|
|
277
|
+
contact.contact_id,
|
|
278
|
+
reason="orphaned_person_node",
|
|
279
|
+
performed_by="system",
|
|
280
|
+
)
|
|
281
|
+
pruned += 1
|
|
282
|
+
logger.info(
|
|
283
|
+
"Pruned orphaned shadow contact %s (Person %s deleted from graph)",
|
|
284
|
+
contact.contact_id, contact.person_node_id,
|
|
285
|
+
)
|
|
286
|
+
return pruned
|
|
287
|
+
|
|
288
|
+
# ── Internal helpers ─────────────────────────────────────────────────────────────
|
|
289
|
+
|
|
290
|
+
async def _find_existing_contact(
|
|
291
|
+
self,
|
|
292
|
+
person_id: str,
|
|
293
|
+
name: Optional[str],
|
|
294
|
+
handles: List[tuple[str, str]],
|
|
295
|
+
) -> Optional[Contact]:
|
|
296
|
+
"""Check if a Person already has a matching contact (curated or discovered)."""
|
|
297
|
+
# 1. Exact person_node_id match
|
|
298
|
+
contact = await self._store.find_by_person_node_id(person_id)
|
|
299
|
+
if contact:
|
|
300
|
+
return contact
|
|
301
|
+
|
|
302
|
+
# 2. Handle overlap
|
|
303
|
+
for gateway, address in handles:
|
|
304
|
+
contact = await self._store.resolve_handle(gateway, address)
|
|
305
|
+
if contact:
|
|
306
|
+
return contact
|
|
307
|
+
|
|
308
|
+
# 3. Name similarity
|
|
309
|
+
if name:
|
|
310
|
+
matches = await self._store.find_by_name(name, threshold=self._name_match_threshold)
|
|
311
|
+
if matches:
|
|
312
|
+
return matches[0]
|
|
313
|
+
|
|
314
|
+
return None
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Context gate — decide per-call whether context fits a model whole,
|
|
2
|
+
or needs chunking + retrieval (RAG) / map-reduce summarization first.
|
|
3
|
+
|
|
4
|
+
Motivation: a model's *useful* context window (the range over which exact
|
|
5
|
+
retrieval stays reliable) is often far below its advertised maximum.
|
|
6
|
+
Stuffing a 200K document into such a model silently degrades recall.
|
|
7
|
+
The gate measures the input against a configurable per-model budget
|
|
8
|
+
(``TierConfig.useful_context_tokens`` or an explicit budget) and picks
|
|
9
|
+
one of three strategies:
|
|
10
|
+
|
|
11
|
+
- ``PASS_THROUGH`` — fits; send whole.
|
|
12
|
+
- ``RETRIEVE`` — query-focused task; chunk, rank chunks against the
|
|
13
|
+
query (embeddings when available, lexical TF-IDF
|
|
14
|
+
otherwise), and pack the best chunks into budget.
|
|
15
|
+
- ``MAP_REDUCE`` — holistic task (summarize/review the whole thing);
|
|
16
|
+
summarize chunks via a caller-provided LLM callable,
|
|
17
|
+
or fall back to evenly-spaced coverage sampling.
|
|
18
|
+
|
|
19
|
+
Everything is host-agnostic and dependency-free (pure stdlib); embedding
|
|
20
|
+
and summarization are injected as optional callables so any agent can use
|
|
21
|
+
it with whatever LLM/embedding stack it already has.
|
|
22
|
+
|
|
23
|
+
Usage::
|
|
24
|
+
|
|
25
|
+
from apsimo.contextgate import prepare_context
|
|
26
|
+
|
|
27
|
+
prepared = await prepare_context(
|
|
28
|
+
content=big_document,
|
|
29
|
+
query="when did the outage start?",
|
|
30
|
+
budget_tokens=65536,
|
|
31
|
+
)
|
|
32
|
+
messages = [{"role": "user", "content": prepared.text}]
|
|
33
|
+
|
|
34
|
+
Configuration (env, all optional):
|
|
35
|
+
|
|
36
|
+
- ``COLONY_CONTEXT_GATE`` — ``auto`` (default) | ``on`` | ``off``
|
|
37
|
+
- ``COLONY_CONTEXT_GATE_HEADROOM`` — fraction of budget that triggers
|
|
38
|
+
gating (default 0.8)
|
|
39
|
+
- ``COLONY_CONTEXT_GATE_BUDGET`` — default token budget when neither
|
|
40
|
+
the caller nor the model tier provides one (default 0 = no gating)
|
|
41
|
+
- ``COLONY_CONTEXT_CHUNK_TOKENS`` — target chunk size (default 1024)
|
|
42
|
+
- ``COLONY_CONTEXT_OVERLAP_TOKENS`` — chunk overlap (default 128)
|
|
43
|
+
- ``COLONY_CONTEXT_CHARS_PER_TOKEN`` — token estimator ratio (default 4.0)
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
from apsimo.contextgate.estimate import estimate_tokens
|
|
47
|
+
from apsimo.contextgate.chunker import Chunk, chunk_text
|
|
48
|
+
from apsimo.contextgate.retrieve import rank_chunks
|
|
49
|
+
from apsimo.contextgate.gate import (
|
|
50
|
+
GateConfig,
|
|
51
|
+
GateDecision,
|
|
52
|
+
PreparedContext,
|
|
53
|
+
classify_task,
|
|
54
|
+
decide,
|
|
55
|
+
prepare_context,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
__all__ = [
|
|
59
|
+
"Chunk",
|
|
60
|
+
"GateConfig",
|
|
61
|
+
"GateDecision",
|
|
62
|
+
"PreparedContext",
|
|
63
|
+
"chunk_text",
|
|
64
|
+
"classify_task",
|
|
65
|
+
"decide",
|
|
66
|
+
"estimate_tokens",
|
|
67
|
+
"prepare_context",
|
|
68
|
+
"rank_chunks",
|
|
69
|
+
]
|