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,155 @@
|
|
|
1
|
+
"""Qualified property observations in the existing world observation journal.
|
|
2
|
+
|
|
3
|
+
Stable entities describe what a thing is. This projection describes what was
|
|
4
|
+
observed, reported or hypothesized about it, when, and for which audience.
|
|
5
|
+
Confidence is deliberately absent: repetition is not independent evidence.
|
|
6
|
+
The host authenticates observation producers and resolves canonical references.
|
|
7
|
+
"""
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
import json
|
|
10
|
+
import re
|
|
11
|
+
|
|
12
|
+
from apsimo.self_model.situation import _safe_id, _validate_scope
|
|
13
|
+
|
|
14
|
+
KINDS = ('observed', 'reported', 'hypothesis')
|
|
15
|
+
SOURCE_PREFIX = 'world-property-v1:'
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def canonical(value):
|
|
19
|
+
return json.dumps(value, sort_keys=True, separators=(',', ':'), allow_nan=False)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def epoch(value):
|
|
23
|
+
if isinstance(value, bool):
|
|
24
|
+
raise ValueError('invalid_world_observation_time')
|
|
25
|
+
if isinstance(value, (int, float)):
|
|
26
|
+
parsed = datetime.fromtimestamp(value, timezone.utc)
|
|
27
|
+
else:
|
|
28
|
+
parsed = datetime.fromisoformat(str(value).replace('Z', '+00:00'))
|
|
29
|
+
if parsed.tzinfo is None:
|
|
30
|
+
raise ValueError('world_observation_time_requires_timezone')
|
|
31
|
+
return parsed.timestamp()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def iso(value):
|
|
35
|
+
return datetime.fromtimestamp(epoch(value), timezone.utc).isoformat()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def observation(*, observation_id, entity_id, property_key, value, kind, producer,
|
|
39
|
+
evidence_refs, observed_at, fresh_until, subject_person_id,
|
|
40
|
+
viewer_scope, shareability, valid_from=None, valid_to=None, source_refs=()):
|
|
41
|
+
for key, item in [('observation_id', observation_id), ('entity_id', entity_id), ('producer', producer)]:
|
|
42
|
+
_safe_id(item, key)
|
|
43
|
+
if not isinstance(property_key, str) or not re.fullmatch('[a-z][a-z0-9_.-]{0,63}', property_key):
|
|
44
|
+
raise ValueError('invalid_world_property_key')
|
|
45
|
+
if kind not in KINDS:
|
|
46
|
+
raise ValueError('invalid_world_observation_kind')
|
|
47
|
+
if not isinstance(evidence_refs, (tuple, list)) or not 1 <= len(evidence_refs) <= 40:
|
|
48
|
+
raise ValueError('world_observation_requires_evidence')
|
|
49
|
+
refs = sorted({_safe_id(ref, 'evidence_ref') for ref in evidence_refs})
|
|
50
|
+
if not isinstance(source_refs, (list, tuple)) or len(source_refs) > 40:
|
|
51
|
+
raise ValueError('invalid_world_source_refs')
|
|
52
|
+
sources = []
|
|
53
|
+
for source in source_refs:
|
|
54
|
+
if (not isinstance(source, dict) or set(source) != {'source_id', 'source_version'}
|
|
55
|
+
or not isinstance(source['source_version'], str)
|
|
56
|
+
or not re.fullmatch('[0-9a-f]{64}', source['source_version'])):
|
|
57
|
+
raise ValueError('invalid_world_source_refs')
|
|
58
|
+
_safe_id(source['source_id'], 'source_id')
|
|
59
|
+
if 'source:' + source['source_id'] not in refs:
|
|
60
|
+
raise ValueError('world_source_ref_requires_evidence')
|
|
61
|
+
sources.append(dict(source))
|
|
62
|
+
# Preserve existing source/receipt/media identifiers; do not invent text
|
|
63
|
+
# claims or upload referenced media into this journal.
|
|
64
|
+
subject, viewer, sharing = _validate_scope(subject_person_id, viewer_scope, shareability)
|
|
65
|
+
observed, fresh = iso(observed_at), iso(fresh_until)
|
|
66
|
+
start, end = iso(valid_from if valid_from is not None else observed_at), iso(valid_to) if valid_to is not None else None
|
|
67
|
+
if epoch(fresh) < epoch(observed) or epoch(fresh) - epoch(observed) > 366 * 86400:
|
|
68
|
+
raise ValueError('world_observation_freshness_must_be_bounded')
|
|
69
|
+
if end is not None and epoch(end) <= epoch(start):
|
|
70
|
+
raise ValueError('invalid_world_observation_validity')
|
|
71
|
+
if len(canonical(value).encode()) > 8192:
|
|
72
|
+
raise ValueError('world_observation_value_too_large')
|
|
73
|
+
return dict(schema='WorldPropertyObservationV1', observation_id=observation_id,
|
|
74
|
+
entity_id=entity_id, property_key=property_key, value=value, kind=kind, producer=producer,
|
|
75
|
+
evidence_refs=refs, source_refs=sorted(sources, key=lambda s: s['source_id']),
|
|
76
|
+
observed_at=observed, fresh_until=fresh, valid_from=start, valid_to=end,
|
|
77
|
+
subject_person_id=subject, viewer_scope=viewer, shareability=sharing)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def project(records, *, entity_id, property_key, subject_person_id, viewer_scope,
|
|
81
|
+
shareability, as_of=None, coverage_limited=False):
|
|
82
|
+
"""Observed-time view. Delayed arrival cannot win over a newer observation.
|
|
83
|
+
|
|
84
|
+
Each producer's latest applicable report is considered separately. Fresh
|
|
85
|
+
observations outrank reports, which outrank hypotheses, but disagreement
|
|
86
|
+
remains explicit. A stale latest observation cannot revive its older value.
|
|
87
|
+
``as_of`` answers what held then using available evidence, not what the
|
|
88
|
+
system had already ingested then.
|
|
89
|
+
"""
|
|
90
|
+
_validate_scope(subject_person_id, viewer_scope, shareability)
|
|
91
|
+
now = epoch(as_of if as_of is not None else datetime.now(timezone.utc).isoformat())
|
|
92
|
+
visible = []
|
|
93
|
+
for row in records:
|
|
94
|
+
if (row.get('schema') != 'WorldPropertyObservationV1' or row.get('entity_id') != entity_id
|
|
95
|
+
or row.get('property_key') != property_key):
|
|
96
|
+
continue
|
|
97
|
+
if (row['subject_person_id'], row['viewer_scope'], row['shareability']) != (subject_person_id, viewer_scope, shareability):
|
|
98
|
+
continue
|
|
99
|
+
if epoch(row['observed_at']) > now or epoch(row['valid_from']) > now:
|
|
100
|
+
continue
|
|
101
|
+
visible.append(row)
|
|
102
|
+
# Repackaging one receipt under fresh IDs cannot renew freshness or create
|
|
103
|
+
# independent corroboration. Keep its first observed occurrence per value.
|
|
104
|
+
dedup = {}
|
|
105
|
+
for row in sorted(visible, key=lambda r: (epoch(r['observed_at']), r['observation_id'])):
|
|
106
|
+
key = (row['producer'], row['kind'], tuple(row['evidence_refs']), canonical(row['value']))
|
|
107
|
+
dedup.setdefault(key, row)
|
|
108
|
+
latest = {}
|
|
109
|
+
for row in dedup.values():
|
|
110
|
+
key = (row['producer'], row['kind'])
|
|
111
|
+
moment = epoch(row['observed_at'])
|
|
112
|
+
if key not in latest or moment > epoch(latest[key][0]['observed_at']):
|
|
113
|
+
latest[key] = [row]
|
|
114
|
+
elif moment == epoch(latest[key][0]['observed_at']):
|
|
115
|
+
latest[key].append(row)
|
|
116
|
+
candidates = [dict(row, freshness='invalidated' if row.get('invalidated') else 'fresh' if epoch(row['fresh_until']) > now and
|
|
117
|
+
(row['valid_to'] is None or epoch(row['valid_to']) > now) else 'stale')
|
|
118
|
+
for rows in latest.values() for row in rows]
|
|
119
|
+
current = [row for row in candidates if row['freshness'] == 'fresh']
|
|
120
|
+
best = next(([r for r in current if r['kind'] == kind] for kind in KINDS
|
|
121
|
+
if any(r['kind'] == kind for r in current)), [])
|
|
122
|
+
values = {canonical(row['value']) for row in best}
|
|
123
|
+
state = 'conflicted' if len(values) > 1 else 'current' if best else 'stale' if any(
|
|
124
|
+
r['freshness'] == 'stale' for r in candidates) else 'unknown'
|
|
125
|
+
if coverage_limited:
|
|
126
|
+
state = 'unknown'
|
|
127
|
+
selected = best if state == 'current' else []
|
|
128
|
+
value = selected[0]['value'] if selected else None
|
|
129
|
+
refs = sorted({ref for row in best for ref in row['evidence_refs']})
|
|
130
|
+
return dict(schema='WorldPropertyStateV1', entity_id=entity_id, property_key=property_key,
|
|
131
|
+
subject_person_id=subject_person_id, viewer_scope=viewer_scope, shareability=shareability,
|
|
132
|
+
state=state, value=value, kind=selected[0]['kind'] if selected else None,
|
|
133
|
+
as_of=datetime.fromtimestamp(now, timezone.utc).isoformat(), time_basis='observed_at',
|
|
134
|
+
evidence_refs=refs, observations=sorted(candidates, key=lambda r: (r['producer'], r['kind'], r['observation_id'])),
|
|
135
|
+
has_disagreement=len({canonical(r['value']) for r in current}) > 1,
|
|
136
|
+
coverage_limited=coverage_limited, authority_granted=False)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def compact_situation(snapshot, *, limit=12):
|
|
140
|
+
"""Scoped present-tense facts for context; stale values never become current."""
|
|
141
|
+
if snapshot is None:
|
|
142
|
+
return dict(schema='WorldSituationViewV1', state='unknown', facts=[], stale=[], authority_granted=False)
|
|
143
|
+
limit = max(1, min(int(limit), 32))
|
|
144
|
+
def compact(fact):
|
|
145
|
+
return {key: fact.public()[key] for key in ('observation_id', 'category', 'entity_id', 'state', 'active',
|
|
146
|
+
'observed_at', 'fresh_until', 'freshness', 'evidence_refs', 'attributes')}
|
|
147
|
+
facts = [compact(f) for f in snapshot.facts if f.freshness == 'fresh']
|
|
148
|
+
stale = [dict(entity_id=f.entity_id, category=f.category, state='unknown', last_observed_state=f.state,
|
|
149
|
+
observed_at=f.observed_at, fresh_until=f.fresh_until, evidence_refs=list(f.evidence_refs))
|
|
150
|
+
for f in snapshot.facts if f.freshness != 'fresh']
|
|
151
|
+
return dict(schema='WorldSituationViewV1', snapshot_id=snapshot.snapshot_id, as_of=snapshot.as_of,
|
|
152
|
+
subject_person_id=snapshot.subject_person_id, viewer_scope=snapshot.viewer_scope,
|
|
153
|
+
shareability=snapshot.shareability, state='current' if facts else 'stale' if stale else 'unknown',
|
|
154
|
+
facts=facts[:limit], stale=stale[:limit], omitted=max(0, len(facts)-limit)+max(0, len(stale)-limit),
|
|
155
|
+
authority_granted=False)
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
"""WorldModelPopulator -- turn conversation into structured world knowledge.
|
|
2
|
+
|
|
3
|
+
Wires the (already-existing, rule-based, zero-LLM) ConversationExtractor to the
|
|
4
|
+
world-model store so Colony actually learns the owner's people, companies,
|
|
5
|
+
projects and products from what he says. This was the missing link that left
|
|
6
|
+
the world model empty.
|
|
7
|
+
|
|
8
|
+
Observability-first and reversible:
|
|
9
|
+
* mode "off" -- do nothing.
|
|
10
|
+
* mode "shadow" -- extract + resolve + log EXACTLY what it WOULD populate,
|
|
11
|
+
write nothing (default).
|
|
12
|
+
* mode "live" -- upsert entities (dedup via EntityResolver) + light
|
|
13
|
+
relationships into the local world-model store only.
|
|
14
|
+
|
|
15
|
+
Every candidate is boundary-checked: if the owner set a directive to leave a
|
|
16
|
+
subject alone, it is never added to the world model.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import logging
|
|
22
|
+
import os
|
|
23
|
+
import re
|
|
24
|
+
from dataclasses import dataclass, field
|
|
25
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
26
|
+
|
|
27
|
+
from apsimo.world_model.extraction.conversation_extractor import (
|
|
28
|
+
ConversationExtractor, ExtractionCandidate,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def populate_mode() -> str:
|
|
35
|
+
from apsimo.util.autonomy_preset import resolve
|
|
36
|
+
return resolve("COLONY_WORLD_POPULATE_MODE",
|
|
37
|
+
("off", "shadow", "live"), "shadow")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class PopulationReport:
|
|
42
|
+
source_id: str = ""
|
|
43
|
+
mode: str = "shadow"
|
|
44
|
+
created: List[Dict[str, Any]] = field(default_factory=list) # {name,type,confidence}
|
|
45
|
+
merged: List[Dict[str, Any]] = field(default_factory=list) # {name,type,into}
|
|
46
|
+
proposed: List[Dict[str, Any]] = field(default_factory=list)
|
|
47
|
+
relationships: List[Dict[str, Any]] = field(default_factory=list) # {source,rel,target}
|
|
48
|
+
skipped_boundary: List[str] = field(default_factory=list)
|
|
49
|
+
|
|
50
|
+
def total(self) -> int:
|
|
51
|
+
return len(self.created) + len(self.merged) + len(self.proposed)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# Person <-> Company work link cues (person <cue> company).
|
|
55
|
+
_WORK_CUE = re.compile(
|
|
56
|
+
r"\b(?:works?\s+(?:at|for)|employed\s+at|ceo\s+of|cto\s+of|founder\s+of|"
|
|
57
|
+
r"co-?founder\s+of|runs?|leads?|heads?|at)\b", re.IGNORECASE,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Lowercase connectives that only appear inside a sentence, never in a real
|
|
61
|
+
# entity NAME. Their presence marks a candidate as a sentence fragment.
|
|
62
|
+
_FRAGMENT_WORDS = frozenset({
|
|
63
|
+
"the", "a", "an", "who", "whom", "that", "which", "and", "or", "but",
|
|
64
|
+
"at", "in", "on", "of", "for", "to", "with", "about", "met", "meet",
|
|
65
|
+
"works", "work", "working", "is", "are", "was", "were", "said", "says",
|
|
66
|
+
"from", "by", "joined", "join", "called", "told", "asked",
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _looks_like_fragment(name: str) -> bool:
|
|
71
|
+
"""A real entity name is a few proper tokens, not a clause."""
|
|
72
|
+
toks = name.split()
|
|
73
|
+
if len(toks) > 5:
|
|
74
|
+
return True
|
|
75
|
+
lows = {t.lower().strip(".,") for t in toks}
|
|
76
|
+
return bool(lows & _FRAGMENT_WORDS)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# Generic role/system words + calendar words that rule-based NER mis-tags as
|
|
80
|
+
# people/places. Deployment-specific role names (the assistant persona, host
|
|
81
|
+
# agent, etc.) are added from config/env at runtime -- never hardcoded here.
|
|
82
|
+
_GENERIC_NOISE = frozenset({
|
|
83
|
+
"agent", "assistant", "system", "user", "bot", "ai",
|
|
84
|
+
"gpt", "model", "human", "owner",
|
|
85
|
+
"monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday",
|
|
86
|
+
"january", "february", "march", "april", "may", "june", "july", "august",
|
|
87
|
+
"september", "october", "november", "december", "today", "tomorrow",
|
|
88
|
+
"yesterday", "morning", "evening", "tonight", "week", "month", "year",
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _noise_names() -> frozenset:
|
|
93
|
+
"""Generic role/calendar noise + any deployment-specific role names from
|
|
94
|
+
env (persona/agent name, plus COLONY_WORLD_NOISE_NAMES)."""
|
|
95
|
+
extra = set()
|
|
96
|
+
for var in ("COLONY_PERSONA_NAME", "COLONY_AGENT_NAME"):
|
|
97
|
+
v = os.environ.get(var, "").strip().lower()
|
|
98
|
+
if v:
|
|
99
|
+
extra.add(v)
|
|
100
|
+
for t in os.environ.get("COLONY_WORLD_NOISE_NAMES", "").split(","):
|
|
101
|
+
t = t.strip().lower()
|
|
102
|
+
if t:
|
|
103
|
+
extra.add(t)
|
|
104
|
+
return _GENERIC_NOISE | extra
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# Common English nouns/verbs that never form part of a real person's name.
|
|
108
|
+
# Title-cased operational phrases ("Root Cause", "Orphan Messages") are the
|
|
109
|
+
# dominant person-noise class observed in live data.
|
|
110
|
+
_NON_NAME_WORDS = frozenset({
|
|
111
|
+
"cause", "root", "message", "messages", "session", "sessions",
|
|
112
|
+
"initiative", "initiatives", "operation", "operations", "orphan",
|
|
113
|
+
"fresh", "spawn", "error", "errors", "task", "tasks", "queue", "memory",
|
|
114
|
+
"review", "reviews", "summary", "report", "reports", "update", "updates",
|
|
115
|
+
"status", "issue", "issues", "problem", "problems", "fix", "fixes",
|
|
116
|
+
"bug", "bugs", "test", "tests", "daily", "weekly", "briefing", "digest",
|
|
117
|
+
"reminder", "alert", "alerts", "notification", "delivery", "channel",
|
|
118
|
+
"gateway", "bridge", "worker", "workers", "pending", "stale", "self",
|
|
119
|
+
"improvement", "cleanup", "backlog", "audit", "check", "checks",
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _is_low_quality(name: str, etype: str) -> bool:
|
|
124
|
+
"""High-precision gate for world-model population.
|
|
125
|
+
|
|
126
|
+
Rule-based NER is noisy on conversation; drop the dominant noise classes so
|
|
127
|
+
the world model is trustworthy rather than full of sentence-initial words.
|
|
128
|
+
"""
|
|
129
|
+
low = name.strip().lower()
|
|
130
|
+
_noise = _noise_names()
|
|
131
|
+
if low in _noise:
|
|
132
|
+
return True
|
|
133
|
+
# URLs, paths, emails, and placeholder fragments are never entity NAMES
|
|
134
|
+
# (they belong in external_ids/properties, not as canonical names).
|
|
135
|
+
if ("://" in low or low.startswith(("http", "www."))
|
|
136
|
+
or low.endswith(("...", "/")) or "/" in name.strip()):
|
|
137
|
+
return True
|
|
138
|
+
toks = name.split()
|
|
139
|
+
# Any token being a role/system/greeting word taints the whole name
|
|
140
|
+
# (e.g. a greeting + the assistant's own name, or "Agent ...").
|
|
141
|
+
tok_lows = {t.lower().strip(".,") for t in toks}
|
|
142
|
+
if tok_lows & (_noise | {"hey", "hi", "hello", "yo", "ok", "okay", "thanks"}):
|
|
143
|
+
return True
|
|
144
|
+
# Persons/locations must be multi-token proper names (single capitalized
|
|
145
|
+
# words at sentence start are the main noise source). Companies/products
|
|
146
|
+
# come from structured signals (org-suffix, domain, url) and are kept.
|
|
147
|
+
if etype in ("person", "location") and len(toks) < 2:
|
|
148
|
+
return True
|
|
149
|
+
# A "person" whose name contains a common operational noun is a
|
|
150
|
+
# title-cased phrase, not a human ("Root Cause", "Orphan Messages").
|
|
151
|
+
if etype == "person" and tok_lows & _NON_NAME_WORDS:
|
|
152
|
+
return True
|
|
153
|
+
return False
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class WorldModelPopulator:
|
|
157
|
+
def __init__(
|
|
158
|
+
self,
|
|
159
|
+
store: Any,
|
|
160
|
+
directive_manager: Any = None,
|
|
161
|
+
mode: Optional[str] = None,
|
|
162
|
+
min_confidence: float = 0.30,
|
|
163
|
+
) -> None:
|
|
164
|
+
self._store = store
|
|
165
|
+
self._directives = directive_manager
|
|
166
|
+
self._mode = mode or populate_mode()
|
|
167
|
+
self._min_conf = min_confidence
|
|
168
|
+
self._extractor = ConversationExtractor()
|
|
169
|
+
self._resolver = None
|
|
170
|
+
if store is not None:
|
|
171
|
+
try:
|
|
172
|
+
from apsimo.world_model.resolution.entity_resolver import EntityResolver
|
|
173
|
+
self._resolver = EntityResolver(store)
|
|
174
|
+
except Exception as exc:
|
|
175
|
+
logger.debug("EntityResolver unavailable: %s", exc)
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
def mode(self) -> str:
|
|
179
|
+
return self._mode
|
|
180
|
+
|
|
181
|
+
def _boundary_ok(self, name: str) -> bool:
|
|
182
|
+
if self._directives is None:
|
|
183
|
+
# No directive manager configured: nothing to check against
|
|
184
|
+
# (distinct from "checked and allowed" — see log line).
|
|
185
|
+
logger.debug("world populate boundary UNCHECKED for %r "
|
|
186
|
+
"(no directive manager)", name)
|
|
187
|
+
return True
|
|
188
|
+
try:
|
|
189
|
+
from apsimo.directives import Action
|
|
190
|
+
return self._directives.check(Action(kind="populate", text=name, target=name)).allowed
|
|
191
|
+
except Exception:
|
|
192
|
+
# Fail closed, matching directives.guard.boundary_fail_closed():
|
|
193
|
+
# an error while evaluating an owner boundary must refuse.
|
|
194
|
+
logger.warning("world populate boundary check errored for %r "
|
|
195
|
+
"— failing closed", name, exc_info=True)
|
|
196
|
+
return False
|
|
197
|
+
|
|
198
|
+
async def populate_from_text(self, text: str, source_id: str) -> PopulationReport:
|
|
199
|
+
report = PopulationReport(source_id=source_id, mode=self._mode)
|
|
200
|
+
if self._mode == "off" or self._store is None or not text:
|
|
201
|
+
return report
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
extraction = await self._extractor.extract(text, source_id)
|
|
205
|
+
except Exception as exc:
|
|
206
|
+
logger.debug("world populate extract failed: %s", exc)
|
|
207
|
+
return report
|
|
208
|
+
|
|
209
|
+
# Dedup within the message + confidence + boundary filter.
|
|
210
|
+
candidates: List[ExtractionCandidate] = []
|
|
211
|
+
seen = set()
|
|
212
|
+
for c in extraction.entities:
|
|
213
|
+
key = (c.text.strip().lower(), c.entity_type)
|
|
214
|
+
if not c.text.strip() or key in seen or c.confidence < self._min_conf:
|
|
215
|
+
continue
|
|
216
|
+
seen.add(key)
|
|
217
|
+
# Reject sentence-fragment "names" and low-quality/noise candidates
|
|
218
|
+
# that slip through rule-based NER (high-precision population).
|
|
219
|
+
if _looks_like_fragment(c.text) or _is_low_quality(c.text, c.entity_type):
|
|
220
|
+
continue
|
|
221
|
+
if not self._boundary_ok(c.text):
|
|
222
|
+
report.skipped_boundary.append(c.text)
|
|
223
|
+
continue
|
|
224
|
+
candidates.append(c)
|
|
225
|
+
|
|
226
|
+
name_to_type: Dict[str, str] = {}
|
|
227
|
+
for c in candidates:
|
|
228
|
+
name_to_type[c.text] = c.entity_type
|
|
229
|
+
await self._handle_candidate(c, report)
|
|
230
|
+
|
|
231
|
+
self._infer_relationships(text, candidates, report)
|
|
232
|
+
return report
|
|
233
|
+
|
|
234
|
+
async def _handle_candidate(self, c: ExtractionCandidate, report: PopulationReport) -> None:
|
|
235
|
+
action = "create"
|
|
236
|
+
matched = None
|
|
237
|
+
if self._resolver is not None:
|
|
238
|
+
try:
|
|
239
|
+
res = await self._resolver.resolve(c, c.entity_type)
|
|
240
|
+
action = getattr(res.action, "value", str(res.action))
|
|
241
|
+
matched = res.matched_entity_id
|
|
242
|
+
except Exception as exc:
|
|
243
|
+
logger.debug("resolve failed for %r: %s", c.text, exc)
|
|
244
|
+
|
|
245
|
+
rec = {"name": c.text, "type": c.entity_type, "confidence": round(c.confidence, 2)}
|
|
246
|
+
if action == "merge":
|
|
247
|
+
rec["into"] = matched
|
|
248
|
+
report.merged.append(rec)
|
|
249
|
+
if self._mode == "live" and matched:
|
|
250
|
+
try:
|
|
251
|
+
await self._store.add_entity_alias(matched, c.text)
|
|
252
|
+
except Exception:
|
|
253
|
+
pass
|
|
254
|
+
# Repeat mention (covers exact-match, external-id and alias
|
|
255
|
+
# resolves — they all arrive here as MERGE): the entity was
|
|
256
|
+
# just seen again, so touch last_seen / mention_count /
|
|
257
|
+
# confidence. Reinforcement only — a repeat-mention must never
|
|
258
|
+
# make an entity MORE prunable than a single mention.
|
|
259
|
+
try:
|
|
260
|
+
await self._store.reinforce_entity(matched)
|
|
261
|
+
except Exception as exc:
|
|
262
|
+
logger.debug("reinforce_entity failed for %r: %s", matched, exc)
|
|
263
|
+
return
|
|
264
|
+
if action == "propose":
|
|
265
|
+
rec["near"] = matched
|
|
266
|
+
report.proposed.append(rec)
|
|
267
|
+
# in live mode a proposal still creates the candidate + queues review,
|
|
268
|
+
# but to stay conservative we only create on a clean CREATE.
|
|
269
|
+
return
|
|
270
|
+
# CREATE
|
|
271
|
+
report.created.append(rec)
|
|
272
|
+
if self._mode == "live":
|
|
273
|
+
try:
|
|
274
|
+
from apsimo.world_model.entities import ENTITY_CLASS_MAP, BaseEntity
|
|
275
|
+
from apsimo.world_model.sqlite.backend import _generate_id
|
|
276
|
+
cls = ENTITY_CLASS_MAP.get(c.entity_type, BaseEntity)
|
|
277
|
+
ent = cls(id=_generate_id("we"), name=c.text,
|
|
278
|
+
entity_type=c.entity_type, confidence=c.confidence)
|
|
279
|
+
await self._store.upsert_entity(ent)
|
|
280
|
+
except Exception as exc:
|
|
281
|
+
logger.debug("upsert_entity failed for %r: %s", c.text, exc)
|
|
282
|
+
|
|
283
|
+
def _infer_relationships(self, text: str, candidates: List[ExtractionCandidate],
|
|
284
|
+
report: PopulationReport) -> None:
|
|
285
|
+
"""Light, conservative relationship inference (name-level in the report).
|
|
286
|
+
|
|
287
|
+
Only person<->company WORKS_AT when a clear work cue links them, and
|
|
288
|
+
person<->person KNOWS co-occurrence at low confidence. Written in live
|
|
289
|
+
mode is deferred to LLM-assist; here we surface them for observability.
|
|
290
|
+
"""
|
|
291
|
+
persons = [c for c in candidates if c.entity_type == "person"]
|
|
292
|
+
companies = [c for c in candidates if c.entity_type == "company"]
|
|
293
|
+
for p in persons:
|
|
294
|
+
for co in companies:
|
|
295
|
+
lo, hi = sorted([p.start_char, co.start_char])
|
|
296
|
+
between = text[lo:hi]
|
|
297
|
+
if _WORK_CUE.search(between) or _WORK_CUE.search(
|
|
298
|
+
text[max(0, p.start_char - 20):p.start_char]
|
|
299
|
+
):
|
|
300
|
+
report.relationships.append(
|
|
301
|
+
{"source": p.text, "rel": "WM_WORKS_AT", "target": co.text,
|
|
302
|
+
"confidence": 0.5})
|
|
303
|
+
for i in range(len(persons)):
|
|
304
|
+
for j in range(i + 1, len(persons)):
|
|
305
|
+
report.relationships.append(
|
|
306
|
+
{"source": persons[i].text, "rel": "WM_KNOWS",
|
|
307
|
+
"target": persons[j].text, "confidence": 0.3})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Postgres backend for World Model
|