apsimo 1.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- apsimo/__init__.py +38 -0
- apsimo/__main__.py +6 -0
- apsimo/agent/__init__.py +6 -0
- apsimo/agent/client.py +276 -0
- apsimo/agent/models.py +46 -0
- apsimo/agents/__init__.py +20 -0
- apsimo/agents/models.py +264 -0
- apsimo/agents/store.py +861 -0
- apsimo/agents/websocket.py +522 -0
- apsimo/api/__init__.py +1 -0
- apsimo/api/auth_telemetry.py +287 -0
- apsimo/api/authority.py +1203 -0
- apsimo/api/contact_grants.py +347 -0
- apsimo/api/middleware.py +483 -0
- apsimo/api/routers/__init__.py +1 -0
- apsimo/api/routers/commitment_work.py +265 -0
- apsimo/api/routers/context_gate.py +123 -0
- apsimo/api/routers/executions.py +140 -0
- apsimo/api/routers/followup_plans.py +147 -0
- apsimo/api/routers/governed_actions.py +162 -0
- apsimo/api/routers/host.py +14473 -0
- apsimo/api/routers/initiative_work.py +115 -0
- apsimo/api/routers/mining.py +104 -0
- apsimo/api/routers/observations.py +110 -0
- apsimo/api/routers/social_state.py +225 -0
- apsimo/api/routers/task_queue.py +2715 -0
- apsimo/api/routers/temporal_followups.py +251 -0
- apsimo/api/routers/transport.py +110 -0
- apsimo/api/routers/transport_ingress_api.py +240 -0
- apsimo/api/schemas/__init__.py +1 -0
- apsimo/api/schemas/host.py +1949 -0
- apsimo/autonomy/cli.py +110 -0
- apsimo/autonomy/condition_worker.py +437 -0
- apsimo/autonomy/config.py +424 -0
- apsimo/autonomy/loop.py +4316 -0
- apsimo/autonomy/registry.py +339 -0
- apsimo/autonomy/scheduler.py +1822 -0
- apsimo/autonomy/synthesis.py +449 -0
- apsimo/backup.py +962 -0
- apsimo/beliefs/__init__.py +23 -0
- apsimo/beliefs/contradictions.py +109 -0
- apsimo/beliefs/decay.py +61 -0
- apsimo/beliefs/engine.py +479 -0
- apsimo/beliefs/models.py +67 -0
- apsimo/beliefs/promotion.py +41 -0
- apsimo/beliefs/resolve.py +58 -0
- apsimo/beliefs/source_claims.py +690 -0
- apsimo/beliefs/source_projection.py +883 -0
- apsimo/beliefs/source_time.py +208 -0
- apsimo/beliefs/store.py +133 -0
- apsimo/briefings/aggregators.py +824 -0
- apsimo/briefings/composer.py +420 -0
- apsimo/briefings/config.py +55 -0
- apsimo/briefings/delivery.py +439 -0
- apsimo/briefings/engagement.py +97 -0
- apsimo/briefings/engine.py +274 -0
- apsimo/briefings/enhancer.py +99 -0
- apsimo/briefings/models.py +183 -0
- apsimo/briefings/scheduler.py +382 -0
- apsimo/briefings/store.py +435 -0
- apsimo/chain/__init__.py +48 -0
- apsimo/chain/block.py +100 -0
- apsimo/chain/cli.py +704 -0
- apsimo/chain/genesis.py +443 -0
- apsimo/chain/identity.py +416 -0
- apsimo/chain/keys.py +1025 -0
- apsimo/chain/local_keys.py +187 -0
- apsimo/chain/manager.py +290 -0
- apsimo/chain/node.py +163 -0
- apsimo/chain/plugin_transactions.py +371 -0
- apsimo/chain/protocol.py +220 -0
- apsimo/chain/state_machine.py +676 -0
- apsimo/chain/storage.py +503 -0
- apsimo/chain/transactions.py +250 -0
- apsimo/chain/validation.py +397 -0
- apsimo/channels/__init__.py +1 -0
- apsimo/channels/manifest.py +31 -0
- apsimo/channels/migrations/001_channels_schema.sql +12 -0
- apsimo/channels/phone_gateways.py +42 -0
- apsimo/channels/presence.py +188 -0
- apsimo/channels/router.py +235 -0
- apsimo/channels/store.py +231 -0
- apsimo/cli.py +2688 -0
- apsimo/cognition/__init__.py +11 -0
- apsimo/cognition/charter.py +398 -0
- apsimo/cognition/drive_governance.py +3530 -0
- apsimo/cognition/evidence_pipeline.py +1627 -0
- apsimo/cognition/external_events.py +932 -0
- apsimo/cognition/goal_spine.py +3488 -0
- apsimo/cognition/introspection.py +214 -0
- apsimo/cognition/prompt.py +150 -0
- apsimo/cognition/runtime.py +108 -0
- apsimo/cognition/trigger.py +154 -0
- apsimo/commitments/__init__.py +18 -0
- apsimo/commitments/local_work.py +355 -0
- apsimo/commitments/store.py +1052 -0
- apsimo/commitments/work.py +91 -0
- apsimo/compat.py +53 -0
- apsimo/compression/__init__.py +467 -0
- apsimo/connectors/__init__.py +21 -0
- apsimo/connectors/base.py +152 -0
- apsimo/connectors/caldav_calendar.py +125 -0
- apsimo/connectors/fs_documents.py +85 -0
- apsimo/connectors/imap_email.py +138 -0
- apsimo/connectors/manager.py +218 -0
- apsimo/connectors/webhook_pull.py +88 -0
- apsimo/contacts/__init__.py +33 -0
- apsimo/contacts/comms.py +357 -0
- apsimo/contacts/config.py +79 -0
- apsimo/contacts/exporters/__init__.py +1 -0
- apsimo/contacts/exporters/vcard.py +71 -0
- apsimo/contacts/identity_links.py +251 -0
- apsimo/contacts/importer.py +280 -0
- apsimo/contacts/importers/__init__.py +1 -0
- apsimo/contacts/importers/batch.py +43 -0
- apsimo/contacts/importers/macos_contacts.py +101 -0
- apsimo/contacts/migrations/001_contacts_schema.sql +141 -0
- apsimo/contacts/migrations/002_trust_scopes.sql +36 -0
- apsimo/contacts/migrations/003_open_gateway_enum.sql +32 -0
- apsimo/contacts/migrations/004_contact_provision_operations.sql +18 -0
- apsimo/contacts/migrations/005_identity_links.sql +27 -0
- apsimo/contacts/models.py +308 -0
- apsimo/contacts/scoring.py +16 -0
- apsimo/contacts/store.py +1623 -0
- apsimo/contacts/transport_ingress.py +252 -0
- apsimo/contacts/world_bridge.py +314 -0
- apsimo/contextgate/__init__.py +69 -0
- apsimo/contextgate/chunker.py +169 -0
- apsimo/contextgate/estimate.py +54 -0
- apsimo/contextgate/gate.py +313 -0
- apsimo/contextgate/retrieve.py +115 -0
- apsimo/delivery/__init__.py +16 -0
- apsimo/delivery/bridge.py +1260 -0
- apsimo/delivery/channels.py +526 -0
- apsimo/delivery/classification.py +50 -0
- apsimo/delivery/rate_limiter.py +268 -0
- apsimo/delivery/reachout_policy.py +206 -0
- apsimo/directed/__init__.py +22 -0
- apsimo/directed/audit.py +167 -0
- apsimo/directed/intake.py +95 -0
- apsimo/directed/models.py +191 -0
- apsimo/directed/service.py +509 -0
- apsimo/directives/__init__.py +25 -0
- apsimo/directives/evidence.py +87 -0
- apsimo/directives/extractor.py +188 -0
- apsimo/directives/guard.py +364 -0
- apsimo/directives/models.py +206 -0
- apsimo/directives/service.py +372 -0
- apsimo/directives/store.py +167 -0
- apsimo/doctor.py +2173 -0
- apsimo/environment.py +43 -0
- apsimo/events/__init__.py +33 -0
- apsimo/events/broadcaster.py +98 -0
- apsimo/events/bus.py +217 -0
- apsimo/events/journal.py +863 -0
- apsimo/events/stream.py +131 -0
- apsimo/events/types.py +150 -0
- apsimo/execution_results.py +357 -0
- apsimo/feedback/__init__.py +5 -0
- apsimo/feedback/store.py +76 -0
- apsimo/feeds/__init__.py +19 -0
- apsimo/feeds/cli.py +84 -0
- apsimo/feeds/engine.py +437 -0
- apsimo/feeds/example-feed.yaml +77 -0
- apsimo/feeds/hermes_cron.py +126 -0
- apsimo/feeds/manager.py +235 -0
- apsimo/feeds/spec.py +250 -0
- apsimo/feeds/template.py +202 -0
- apsimo/gate/__init__.py +18 -0
- apsimo/gate/audit.py +61 -0
- apsimo/gate/communication_policy.py +166 -0
- apsimo/gate/config.py +72 -0
- apsimo/gate/context_provenance.py +170 -0
- apsimo/gate/env_risk.py +226 -0
- apsimo/gate/guard_audit.py +353 -0
- apsimo/gate/layers/__init__.py +1 -0
- apsimo/gate/layers/base.py +15 -0
- apsimo/gate/layers/l1_recipient.py +66 -0
- apsimo/gate/layers/l2_pii.py +134 -0
- apsimo/gate/layers/l3_cross_context.py +50 -0
- apsimo/gate/layers/l4_trust_tier.py +78 -0
- apsimo/gate/layers/l5_injection.py +199 -0
- apsimo/gate/layers/l6_review.py +86 -0
- apsimo/gate/layers/l7_delay.py +100 -0
- apsimo/gate/layers/tom2_epistemic.py +185 -0
- apsimo/gate/models.py +64 -0
- apsimo/gate/pending_dispatch.py +5 -0
- apsimo/gate/pipeline.py +206 -0
- apsimo/gate/rejection.py +259 -0
- apsimo/gate/response_guard.py +700 -0
- apsimo/gate/rulesets/injection_v1.yaml +51 -0
- apsimo/gate/surface_policy.py +189 -0
- apsimo/gate/taint.py +226 -0
- apsimo/genesis.json +9 -0
- apsimo/goals/__init__.py +100 -0
- apsimo/goals/config.py +38 -0
- apsimo/goals/decomposer.py +421 -0
- apsimo/goals/engine.py +617 -0
- apsimo/goals/inference.py +354 -0
- apsimo/goals/models.py +302 -0
- apsimo/goals/priority.py +270 -0
- apsimo/goals/queue_bridge.py +149 -0
- apsimo/goals/replan.py +450 -0
- apsimo/goals/schema.sql +89 -0
- apsimo/goals/store.py +692 -0
- apsimo/governed_actions.py +1708 -0
- apsimo/harness_integration/__init__.py +45 -0
- apsimo/harness_integration/context.py +41 -0
- apsimo/harness_integration/skills.py +231 -0
- apsimo/identity/__init__.py +26 -0
- apsimo/identity/participants.py +181 -0
- apsimo/identity/resolver.py +329 -0
- apsimo/identity_bootstrap/__init__.py +5 -0
- apsimo/identity_bootstrap/builder.py +208 -0
- apsimo/identity_bootstrap/corpus.py +443 -0
- apsimo/identity_bootstrap/models.py +54 -0
- apsimo/identity_bootstrap/runner.py +353 -0
- apsimo/identity_bootstrap/seeders/__init__.py +25 -0
- apsimo/identity_bootstrap/seeders/briefings.py +109 -0
- apsimo/identity_bootstrap/seeders/chain.py +57 -0
- apsimo/identity_bootstrap/seeders/goals.py +128 -0
- apsimo/identity_bootstrap/seeders/memory.py +191 -0
- apsimo/identity_bootstrap/seeders/neo4j_cognition.py +79 -0
- apsimo/identity_bootstrap/seeders/relationship.py +152 -0
- apsimo/identity_bootstrap/seeders/sessions.py +67 -0
- apsimo/identity_bootstrap/seeders/skills.py +92 -0
- apsimo/identity_bootstrap/seeders/task_queue.py +72 -0
- apsimo/identity_bootstrap/seeders/world_model.py +143 -0
- apsimo/identity_bootstrap/self_query.py +92 -0
- apsimo/identity_bootstrap/self_reflection.py +155 -0
- apsimo/identity_bootstrap/skill.py +37 -0
- apsimo/identity_bootstrap/verifier.py +436 -0
- apsimo/initiatives/__init__.py +20 -0
- apsimo/initiatives/action_registry.py +454 -0
- apsimo/initiatives/approval_authority.py +2105 -0
- apsimo/initiatives/approval_policy.py +123 -0
- apsimo/initiatives/assignment.py +263 -0
- apsimo/initiatives/backup_evidence.py +100 -0
- apsimo/initiatives/context_freshness.py +103 -0
- apsimo/initiatives/models.py +318 -0
- apsimo/initiatives/native_work.py +270 -0
- apsimo/initiatives/standing_approvals.py +232 -0
- apsimo/initiatives/store.py +1081 -0
- apsimo/initiatives/temporal_followup.py +410 -0
- apsimo/intelligence/__init__.py +1 -0
- apsimo/intelligence/cognition/__init__.py +24 -0
- apsimo/intelligence/cognition/gap_detector.py +148 -0
- apsimo/intelligence/cognition/metalearner.py +547 -0
- apsimo/intelligence/cognition/metrics_collector.py +217 -0
- apsimo/intelligence/cognition/performance_index.py +299 -0
- apsimo/intelligence/cognition/registry.py +192 -0
- apsimo/intelligence/cognition/strategy_adjuster.py +222 -0
- apsimo/intelligence/cognition/types.py +16 -0
- apsimo/intelligence/components/__init__.py +66 -0
- apsimo/intelligence/components/anomaly_detector.py +413 -0
- apsimo/intelligence/components/initiative_engine.py +2643 -0
- apsimo/intelligence/components/preference_learner.py +521 -0
- apsimo/intelligence/components/research_orchestrator.py +358 -0
- apsimo/intelligence/components/self_directed_thinker.py +221 -0
- apsimo/intelligence/components/self_reflector.py +252 -0
- apsimo/intelligence/components/session_continuity.py +154 -0
- apsimo/intelligence/components/task_planner.py +320 -0
- apsimo/intelligence/components/tool_learner.py +217 -0
- apsimo/intelligence/graph/__init__.py +79 -0
- apsimo/intelligence/graph/client.py +2483 -0
- apsimo/intelligence/graph/consolidator.py +405 -0
- apsimo/intelligence/graph/distiller.py +312 -0
- apsimo/intelligence/graph/migrations.py +129 -0
- apsimo/intelligence/graph/queries.py +248 -0
- apsimo/intelligence/graph/recall.py +281 -0
- apsimo/intelligence/graph/reconciler.py +144 -0
- apsimo/intelligence/graph/schema.py +337 -0
- apsimo/intelligence/graph/selection.py +252 -0
- apsimo/intelligence/learning/__init__.py +17 -0
- apsimo/intelligence/learning/continuous_learner.py +245 -0
- apsimo/intelligence/learning/feedback_store.py +321 -0
- apsimo/intelligence/mind_model/__init__.py +1 -0
- apsimo/intelligence/mind_model/graph_baseline.py +136 -0
- apsimo/intelligence/mind_model/signal_collector.py +361 -0
- apsimo/intelligence/relationships/__init__.py +11 -0
- apsimo/intelligence/relationships/profiler.py +389 -0
- apsimo/intelligence/relationships/scorer.py +560 -0
- apsimo/intelligence/relationships/signal_floor.py +66 -0
- apsimo/intelligence/relationships/trust_tiers.py +300 -0
- apsimo/intelligence/synthesis/__init__.py +40 -0
- apsimo/intelligence/synthesis/connection_discoverer.py +379 -0
- apsimo/intelligence/synthesis/cross_domain_analyzer.py +287 -0
- apsimo/intelligence/synthesis/insight_deliverer.py +171 -0
- apsimo/intelligence/synthesis/insight_store.py +79 -0
- apsimo/intelligence/synthesis/insight_validator.py +183 -0
- apsimo/intelligence/synthesis/novelty_scorer.py +267 -0
- apsimo/intelligence/turn_middleware/__init__.py +15 -0
- apsimo/intelligence/turn_middleware/memory_sync.py +119 -0
- apsimo/mcp/__init__.py +41 -0
- apsimo/mcp/__main__.py +6 -0
- apsimo/mcp/config.py +287 -0
- apsimo/mcp/server.py +501 -0
- apsimo/migrations.py +187 -0
- apsimo/mining/__init__.py +27 -0
- apsimo/mining/corpus.py +239 -0
- apsimo/mining/escalations.py +289 -0
- apsimo/mining/models.py +169 -0
- apsimo/mining/store.py +210 -0
- apsimo/models/__init__.py +30 -0
- apsimo/models/memory.py +80 -0
- apsimo/models/mesh.py +72 -0
- apsimo/models/person.py +104 -0
- apsimo/models/signal.py +108 -0
- apsimo/observations/__init__.py +15 -0
- apsimo/observations/store.py +277 -0
- apsimo/patterns/__init__.py +6 -0
- apsimo/patterns/extract.py +187 -0
- apsimo/patterns/store.py +227 -0
- apsimo/persona/__init__.py +1 -0
- apsimo/persona/engine.py +611 -0
- apsimo/persona/manifest.py +140 -0
- apsimo/projects/__init__.py +28 -0
- apsimo/projects/engine.py +1681 -0
- apsimo/projects/event_outbox.py +188 -0
- apsimo/projects/models.py +216 -0
- apsimo/projects/planner.py +181 -0
- apsimo/projects/store.py +1446 -0
- apsimo/proposals/__init__.py +12 -0
- apsimo/proposals/engine.py +114 -0
- apsimo/proposals/models.py +207 -0
- apsimo/qualification/__init__.py +1 -0
- apsimo/qualification/cases.py +75 -0
- apsimo/qualification/cli.py +51 -0
- apsimo/qualification/memory_cases.py +209 -0
- apsimo/qualification/records.py +92 -0
- apsimo/qualification/report.py +87 -0
- apsimo/qualification/runner.py +311 -0
- apsimo/qualification/structured_cases.py +131 -0
- apsimo/reasoning/__init__.py +13 -0
- apsimo/reasoning/executor.py +506 -0
- apsimo/reasoning/loop.py +373 -0
- apsimo/reasoning/native_tools/__init__.py +16 -0
- apsimo/reasoning/native_tools/calculate.py +141 -0
- apsimo/reasoning/native_tools/file_ops.py +150 -0
- apsimo/reasoning/native_tools/web_search.py +49 -0
- apsimo/reasoning/tool_policy.py +182 -0
- apsimo/redact/__init__.py +176 -0
- apsimo/repos/__init__.py +5 -0
- apsimo/repos/mirrors.py +204 -0
- apsimo/research/__init__.py +41 -0
- apsimo/research/artifact.py +482 -0
- apsimo/research/gatherer.py +387 -0
- apsimo/research/pipeline.py +513 -0
- apsimo/research/search/__init__.py +7 -0
- apsimo/research/search/base.py +41 -0
- apsimo/research/search/brave.py +59 -0
- apsimo/research/search/cache.py +51 -0
- apsimo/research/search/duckduckgo.py +103 -0
- apsimo/research/search/orchestrator.py +119 -0
- apsimo/research/search/serpapi.py +59 -0
- apsimo/research/search/tavily.py +59 -0
- apsimo/research/synthesizer.py +309 -0
- apsimo/router/__init__.py +30 -0
- apsimo/router/complexity_scorer.py +148 -0
- apsimo/router/endpoints.py +153 -0
- apsimo/router/fallback.py +58 -0
- apsimo/router/functions.py +243 -0
- apsimo/router/native_policy.py +52 -0
- apsimo/router/router.py +762 -0
- apsimo/router/self_learning.py +174 -0
- apsimo/router/tiers.py +677 -0
- apsimo/sandbox/__init__.py +21 -0
- apsimo/sandbox/backend.py +195 -0
- apsimo/sandbox/manager.py +173 -0
- apsimo/scope_bounds.py +7 -0
- apsimo/secrets/__init__.py +6 -0
- apsimo/secrets/backends/__init__.py +8 -0
- apsimo/secrets/backends/base.py +42 -0
- apsimo/secrets/backends/env.py +110 -0
- apsimo/secrets/backends/keyring.py +72 -0
- apsimo/secrets/backends/onepassword.py +232 -0
- apsimo/secrets/cli.py +191 -0
- apsimo/secrets/manager.py +160 -0
- apsimo/secrets/migration.py +101 -0
- apsimo/secrets/types.py +98 -0
- apsimo/seed.py +41 -0
- apsimo/self_model/__init__.py +37 -0
- apsimo/self_model/appraisals.py +673 -0
- apsimo/self_model/benchmark.py +1314 -0
- apsimo/self_model/brief.py +40 -0
- apsimo/self_model/event_concerns.py +1128 -0
- apsimo/self_model/execution_forecasts.py +353 -0
- apsimo/self_model/expectations.py +1595 -0
- apsimo/self_model/experiments.py +1150 -0
- apsimo/self_model/journal.py +148 -0
- apsimo/self_model/judgments.py +705 -0
- apsimo/self_model/native_outcomes.py +55 -0
- apsimo/self_model/params.py +220 -0
- apsimo/self_model/perspective.py +246 -0
- apsimo/self_model/reconcile.py +183 -0
- apsimo/self_model/reply_forecasts.py +381 -0
- apsimo/self_model/runtime_forecasts.py +296 -0
- apsimo/self_model/runtime_models.py +67 -0
- apsimo/self_model/settlement.py +207 -0
- apsimo/self_model/situation.py +1731 -0
- apsimo/self_model/store.py +883 -0
- apsimo/self_model/supervised.py +137 -0
- apsimo/self_model/thinker.py +99 -0
- apsimo/self_model/trust.py +388 -0
- apsimo/self_model/workspace.py +2388 -0
- apsimo/server.py +4197 -0
- apsimo/services/__init__.py +1 -0
- apsimo/services/agent_bridge.py +474 -0
- apsimo/services/initiative_executor.py +914 -0
- apsimo/services/instance.py +297 -0
- apsimo/sessions/__init__.py +22 -0
- apsimo/sessions/config.py +13 -0
- apsimo/sessions/context_loader.py +88 -0
- apsimo/sessions/federation_session.py +75 -0
- apsimo/sessions/isolated_session.py +98 -0
- apsimo/sessions/reports.py +84 -0
- apsimo/sessions/store.py +148 -0
- apsimo/setup.py +2818 -0
- apsimo/setup_hermes.py +879 -0
- apsimo/setup_local_work.py +218 -0
- apsimo/setup_native_goals.py +134 -0
- apsimo/setup_native_reviews.py +115 -0
- apsimo/skills/__init__.py +10 -0
- apsimo/skills/base.py +108 -0
- apsimo/skills/budget.py +28 -0
- apsimo/skills/executor.py +493 -0
- apsimo/skills/executors/__init__.py +1 -0
- apsimo/skills/executors/behavioral_correction.py +75 -0
- apsimo/skills/executors/capability_gap.py +38 -0
- apsimo/skills/executors/data_quality.py +163 -0
- apsimo/skills/executors/knowledge_acquisition.py +41 -0
- apsimo/skills/executors/operational_hygiene.py +185 -0
- apsimo/skills/executors/subsystem_health.py +169 -0
- apsimo/skills/hermes_export.py +431 -0
- apsimo/skills/index.py +123 -0
- apsimo/skills/learning/__init__.py +21 -0
- apsimo/skills/learning/novelty_detector.py +206 -0
- apsimo/skills/learning/pattern_extractor.py +199 -0
- apsimo/skills/learning/triggers.py +159 -0
- apsimo/skills/loader.py +246 -0
- apsimo/skills/migrations/002_progressive_loading.sql +6 -0
- apsimo/skills/migrations/backfill_triggers.py +20 -0
- apsimo/skills/models.py +202 -0
- apsimo/skills/packager.py +128 -0
- apsimo/skills/protocols.py +70 -0
- apsimo/skills/registry.py +191 -0
- apsimo/skills/runtime.py +58 -0
- apsimo/skills/sandbox_runner.py +229 -0
- apsimo/skills/scheduler.py +129 -0
- apsimo/skills/schema.py +79 -0
- apsimo/skills/security/__init__.py +12 -0
- apsimo/skills/security/guards.py +53 -0
- apsimo/skills/security/scanner.py +223 -0
- apsimo/skills_memory/__init__.py +26 -0
- apsimo/skills_memory/distill.py +159 -0
- apsimo/skills_memory/models.py +85 -0
- apsimo/skills_memory/retrieve.py +62 -0
- apsimo/skills_memory/store.py +172 -0
- apsimo/surprise/__init__.py +6 -0
- apsimo/surprise/accumulation.py +57 -0
- apsimo/surprise/scorer.py +102 -0
- apsimo/surprise/store.py +203 -0
- apsimo/task_queue/__init__.py +69 -0
- apsimo/task_queue/action_receipts.py +148 -0
- apsimo/task_queue/approval_relay_canary.py +108 -0
- apsimo/task_queue/config.py +85 -0
- apsimo/task_queue/contract.py +361 -0
- apsimo/task_queue/events.py +130 -0
- apsimo/task_queue/governor.py +1031 -0
- apsimo/task_queue/handlers/__init__.py +16 -0
- apsimo/task_queue/handlers/base.py +37 -0
- apsimo/task_queue/handlers/inference.py +640 -0
- apsimo/task_queue/handlers/monitoring.py +116 -0
- apsimo/task_queue/handlers/registry.py +75 -0
- apsimo/task_queue/handlers/subtask_handler.py +173 -0
- apsimo/task_queue/handlers/system_maintenance.py +147 -0
- apsimo/task_queue/mesh_integration.py +111 -0
- apsimo/task_queue/models.py +317 -0
- apsimo/task_queue/queue_manager.py +8286 -0
- apsimo/task_queue/routing.py +287 -0
- apsimo/task_queue/scheduler.py +252 -0
- apsimo/task_queue/schema.sql +197 -0
- apsimo/task_queue/work_control.py +342 -0
- apsimo/task_queue/worker.py +993 -0
- apsimo/telemetry.py +145 -0
- apsimo/tom/__init__.py +6 -0
- apsimo/tom/affect.py +387 -0
- apsimo/tom/approvals.py +171 -0
- apsimo/tom/arcs.py +896 -0
- apsimo/tom/asymmetry.py +131 -0
- apsimo/tom/eligibility.py +248 -0
- apsimo/tom/engagement.py +214 -0
- apsimo/tom/exposure.py +214 -0
- apsimo/tom/extractor.py +306 -0
- apsimo/tom/fact_adapters.py +144 -0
- apsimo/tom/facts.py +326 -0
- apsimo/tom/integration.py +592 -0
- apsimo/tom/leveled.py +118 -0
- apsimo/tom/levels.py +247 -0
- apsimo/tom/recipient_audit.py +995 -0
- apsimo/tom/recipient_simulator.py +593 -0
- apsimo/tom/source_lineage.py +93 -0
- apsimo/tom/tom2.py +277 -0
- apsimo/tom/visibility.py +559 -0
- apsimo/tom/visibility_store.py +414 -0
- apsimo/tools/__init__.py +0 -0
- apsimo/tools/definitions.py +740 -0
- apsimo/tools/handlers.py +943 -0
- apsimo/toolsmith/__init__.py +26 -0
- apsimo/toolsmith/authority.py +166 -0
- apsimo/toolsmith/engine.py +559 -0
- apsimo/toolsmith/integrity.py +100 -0
- apsimo/toolsmith/miner.py +145 -0
- apsimo/toolsmith/policy.py +110 -0
- apsimo/toolsmith/registry.py +635 -0
- apsimo/turns/__init__.py +17 -0
- apsimo/turns/audio.py +134 -0
- apsimo/turns/documents.py +235 -0
- apsimo/turns/executions.py +486 -0
- apsimo/turns/hermes_history.py +245 -0
- apsimo/turns/hermes_kanban.py +268 -0
- apsimo/turns/hermes_work.py +96 -0
- apsimo/turns/idempotency.py +752 -0
- apsimo/turns/local_work.py +115 -0
- apsimo/turns/media.py +581 -0
- apsimo/turns/reported_workers.py +196 -0
- apsimo/turns/source_annotations.py +283 -0
- apsimo/turns/source_attribution.py +154 -0
- apsimo/turns/source_read.py +351 -0
- apsimo/turns/source_vectors.py +263 -0
- apsimo/turns/video.py +210 -0
- apsimo/util/autonomy_preset.py +220 -0
- apsimo/util/instance.py +92 -0
- apsimo/util/model_output.py +25 -0
- apsimo/util/quiet_hours.py +27 -0
- apsimo/util/session_safety.py +37 -0
- apsimo/util/temporal.py +343 -0
- apsimo/vector/__init__.py +75 -0
- apsimo/vector/backfill.py +171 -0
- apsimo/vector/caption.py +114 -0
- apsimo/vector/collections.py +51 -0
- apsimo/vector/config.py +102 -0
- apsimo/vector/embedder.py +670 -0
- apsimo/vector/image_preprocess.py +406 -0
- apsimo/vector/image_store.py +296 -0
- apsimo/vector/indexes.py +162 -0
- apsimo/vector/migrate.py +334 -0
- apsimo/vector/multimodal_provider.py +417 -0
- apsimo/vector/multimodal_types.py +87 -0
- apsimo/vector/openai_provider.py +119 -0
- apsimo/vector/query.py +49 -0
- apsimo/vector/reranker.py +565 -0
- apsimo/vector/safety_image.py +159 -0
- apsimo/vector/scanner.py +197 -0
- apsimo/vector/setup.py +289 -0
- apsimo/vector/store.py +533 -0
- apsimo/vector/tiers.py +263 -0
- apsimo/work_orders.py +925 -0
- apsimo/workers/__init__.py +21 -0
- apsimo/workers/agent_bridge.py +640 -0
- apsimo/workers/colony_worker.py +382 -0
- apsimo/workers/queue_worker.py +441 -0
- apsimo/workers/skills_sync.py +152 -0
- apsimo/world_model/__init__.py +71 -0
- apsimo/world_model/causal_maintenance.py +131 -0
- apsimo/world_model/causal_policy.py +43 -0
- apsimo/world_model/causal_query.py +125 -0
- apsimo/world_model/confidence.py +54 -0
- apsimo/world_model/config.py +64 -0
- apsimo/world_model/constants.py +97 -0
- apsimo/world_model/entities.py +145 -0
- apsimo/world_model/expectation_resolvers.py +177 -0
- apsimo/world_model/extraction/__init__.py +7 -0
- apsimo/world_model/extraction/base.py +62 -0
- apsimo/world_model/extraction/conversation_extractor.py +262 -0
- apsimo/world_model/extraction/detector.py +74 -0
- apsimo/world_model/extraction/document_extractor.py +78 -0
- apsimo/world_model/extraction/formats/__init__.py +24 -0
- apsimo/world_model/extraction/formats/csv_fmt.py +68 -0
- apsimo/world_model/extraction/formats/html_fmt.py +72 -0
- apsimo/world_model/extraction/formats/json_fmt.py +68 -0
- apsimo/world_model/extraction/formats/pdf.py +43 -0
- apsimo/world_model/extraction/formats/text.py +27 -0
- apsimo/world_model/extraction/llm_extractor.py +164 -0
- apsimo/world_model/extraction/pipeline.py +73 -0
- apsimo/world_model/integrations/__init__.py +5 -0
- apsimo/world_model/integrations/mind_model_bridge.py +115 -0
- apsimo/world_model/integrations/social_intel_bridge.py +120 -0
- apsimo/world_model/jobs/__init__.py +4 -0
- apsimo/world_model/jobs/extraction_job.py +168 -0
- apsimo/world_model/llm_extract.py +572 -0
- apsimo/world_model/neo4j/__init__.py +5 -0
- apsimo/world_model/neo4j/backend.py +654 -0
- apsimo/world_model/observations.py +155 -0
- apsimo/world_model/populator.py +307 -0
- apsimo/world_model/postgres/__init__.py +1 -0
- apsimo/world_model/postgres/backend.py +683 -0
- apsimo/world_model/relationships.py +25 -0
- apsimo/world_model/resolution/__init__.py +13 -0
- apsimo/world_model/resolution/entity_resolver.py +232 -0
- apsimo/world_model/resolution/merge_audit.py +16 -0
- apsimo/world_model/resolution/merge_workflow.py +117 -0
- apsimo/world_model/source_reports.py +121 -0
- apsimo/world_model/sqlite/__init__.py +4 -0
- apsimo/world_model/sqlite/backend.py +855 -0
- apsimo/world_model/sqlite/schema.sql +132 -0
- apsimo/world_model/store.py +545 -0
- apsimo-1.3.0.dist-info/METADATA +78 -0
- apsimo-1.3.0.dist-info/RECORD +614 -0
- apsimo-1.3.0.dist-info/WHEEL +5 -0
- apsimo-1.3.0.dist-info/entry_points.txt +11 -0
- apsimo-1.3.0.dist-info/licenses/LICENSE +21 -0
- apsimo-1.3.0.dist-info/top_level.txt +2 -0
- colony_sidecar/__init__.py +4 -0
apsimo/tom/tom2.py
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"""Second-order theory of mind (tom2) — refs, never content.
|
|
2
|
+
|
|
3
|
+
Stores inferences about what a CONTACT knows or is unaware of, built on the
|
|
4
|
+
SharedFactsStore's per-contact epistemic rows.
|
|
5
|
+
|
|
6
|
+
PRIVACY INVARIANT (the reason this store exists as its own table):
|
|
7
|
+
* A tom2 row references facts by ID only (``fact_ref`` + ``evidence_refs``)
|
|
8
|
+
— cross-contact fact TEXT is never copied into an inference row, so a
|
|
9
|
+
leak of this table reveals topology ("A doesn't know something B told
|
|
10
|
+
me"), never content.
|
|
11
|
+
* ``visibility='owner'`` is the ONLY writable value; readers that are not
|
|
12
|
+
the owner scope get nothing.
|
|
13
|
+
|
|
14
|
+
Both pins are enforced at write time (ValueError, not best-effort) and are
|
|
15
|
+
regression-locked by a raw-DB regex test.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import logging
|
|
22
|
+
import os
|
|
23
|
+
import re
|
|
24
|
+
import sqlite3
|
|
25
|
+
import threading
|
|
26
|
+
import uuid
|
|
27
|
+
from datetime import datetime, timezone
|
|
28
|
+
from typing import Any, Dict, List, Optional
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
# What an inference can claim about a contact's epistemic state.
|
|
33
|
+
INFERENCE_KINDS = ("knows", "unaware_of")
|
|
34
|
+
|
|
35
|
+
OWNER_VISIBILITY = "owner"
|
|
36
|
+
|
|
37
|
+
# A fact ref is an opaque id: compact, no whitespace — anything that looks
|
|
38
|
+
# like prose is refused so text cannot be smuggled through the refs field.
|
|
39
|
+
_REF_RE = re.compile(r"^[A-Za-z0-9_.:-]{1,64}$")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _now() -> str:
|
|
43
|
+
return datetime.now(timezone.utc).isoformat()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _validate_ref(ref: Any) -> str:
|
|
47
|
+
ref = str(ref or "").strip()
|
|
48
|
+
if not _REF_RE.match(ref):
|
|
49
|
+
raise ValueError(
|
|
50
|
+
f"tom2 evidence ref {ref[:40]!r} is not an opaque id "
|
|
51
|
+
"(refs-not-content invariant)")
|
|
52
|
+
return ref
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Tom2Store:
|
|
56
|
+
"""SQLite-backed second-order inference store (refs-not-content)."""
|
|
57
|
+
|
|
58
|
+
def __init__(self, db_path: Optional[str] = None) -> None:
|
|
59
|
+
self._lock = threading.RLock()
|
|
60
|
+
self._conn = sqlite3.connect(db_path or ":memory:",
|
|
61
|
+
check_same_thread=False)
|
|
62
|
+
self._conn.row_factory = sqlite3.Row
|
|
63
|
+
with self._lock:
|
|
64
|
+
self._conn.execute("""
|
|
65
|
+
CREATE TABLE IF NOT EXISTS tom2_inferences (
|
|
66
|
+
id TEXT PRIMARY KEY,
|
|
67
|
+
contact_id TEXT NOT NULL,
|
|
68
|
+
kind TEXT NOT NULL,
|
|
69
|
+
fact_ref TEXT NOT NULL,
|
|
70
|
+
evidence_refs TEXT NOT NULL DEFAULT '[]',
|
|
71
|
+
confidence REAL NOT NULL DEFAULT 0.5,
|
|
72
|
+
visibility TEXT NOT NULL DEFAULT 'owner',
|
|
73
|
+
created_at TEXT NOT NULL,
|
|
74
|
+
updated_at TEXT NOT NULL,
|
|
75
|
+
UNIQUE(contact_id, kind, fact_ref)
|
|
76
|
+
)""")
|
|
77
|
+
self._conn.execute(
|
|
78
|
+
"CREATE INDEX IF NOT EXISTS idx_tom2_contact "
|
|
79
|
+
"ON tom2_inferences(contact_id)")
|
|
80
|
+
self._conn.commit()
|
|
81
|
+
|
|
82
|
+
# -- writes -----------------------------------------------------------
|
|
83
|
+
def record_inference(
|
|
84
|
+
self,
|
|
85
|
+
*,
|
|
86
|
+
contact_id: str,
|
|
87
|
+
kind: str,
|
|
88
|
+
fact_ref: str,
|
|
89
|
+
evidence_refs: Optional[List[str]] = None,
|
|
90
|
+
confidence: float = 0.5,
|
|
91
|
+
visibility: str = OWNER_VISIBILITY,
|
|
92
|
+
) -> Dict[str, Any]:
|
|
93
|
+
"""Upsert one inference row. Raises on any privacy-pin violation."""
|
|
94
|
+
if kind not in INFERENCE_KINDS:
|
|
95
|
+
raise ValueError(f"unknown tom2 inference kind {kind!r}")
|
|
96
|
+
if visibility != OWNER_VISIBILITY:
|
|
97
|
+
# The only writable visibility is 'owner' — wider scoping is a
|
|
98
|
+
# separate, explicitly-gated rendering decision, never storage.
|
|
99
|
+
raise ValueError(
|
|
100
|
+
"tom2 rows are owner-only; refusing visibility "
|
|
101
|
+
f"{visibility!r}")
|
|
102
|
+
contact_id = str(contact_id or "").strip()
|
|
103
|
+
if not contact_id:
|
|
104
|
+
raise ValueError("tom2 inference requires a contact_id")
|
|
105
|
+
fact_ref = _validate_ref(fact_ref)
|
|
106
|
+
refs = [_validate_ref(r) for r in (evidence_refs or [])]
|
|
107
|
+
confidence = max(0.0, min(1.0, float(confidence)))
|
|
108
|
+
now = _now()
|
|
109
|
+
with self._lock:
|
|
110
|
+
self._conn.execute(
|
|
111
|
+
"""INSERT INTO tom2_inferences
|
|
112
|
+
(id, contact_id, kind, fact_ref, evidence_refs,
|
|
113
|
+
confidence, visibility, created_at, updated_at)
|
|
114
|
+
VALUES (?,?,?,?,?,?,?,?,?)
|
|
115
|
+
ON CONFLICT(contact_id, kind, fact_ref) DO UPDATE SET
|
|
116
|
+
evidence_refs=excluded.evidence_refs,
|
|
117
|
+
confidence=excluded.confidence,
|
|
118
|
+
updated_at=excluded.updated_at""",
|
|
119
|
+
(str(uuid.uuid4()), contact_id, kind, fact_ref,
|
|
120
|
+
json.dumps(refs), confidence, OWNER_VISIBILITY, now, now))
|
|
121
|
+
self._conn.commit()
|
|
122
|
+
row = self._conn.execute(
|
|
123
|
+
"SELECT * FROM tom2_inferences WHERE contact_id=? AND kind=? "
|
|
124
|
+
"AND fact_ref=?", (contact_id, kind, fact_ref)).fetchone()
|
|
125
|
+
return self._to_dict(row)
|
|
126
|
+
|
|
127
|
+
def delete_for_fact(self, fact_ref: str) -> int:
|
|
128
|
+
"""Drop inferences referencing a fact (e.g. after fact deletion)."""
|
|
129
|
+
with self._lock:
|
|
130
|
+
cur = self._conn.execute(
|
|
131
|
+
"DELETE FROM tom2_inferences WHERE fact_ref=?", (fact_ref,))
|
|
132
|
+
self._conn.commit()
|
|
133
|
+
return cur.rowcount
|
|
134
|
+
|
|
135
|
+
# -- reads ------------------------------------------------------------
|
|
136
|
+
@staticmethod
|
|
137
|
+
def _to_dict(row: Any) -> Dict[str, Any]:
|
|
138
|
+
d = dict(row)
|
|
139
|
+
try:
|
|
140
|
+
d["evidence_refs"] = json.loads(d.get("evidence_refs") or "[]")
|
|
141
|
+
except Exception:
|
|
142
|
+
d["evidence_refs"] = []
|
|
143
|
+
return d
|
|
144
|
+
|
|
145
|
+
def list_inferences(
|
|
146
|
+
self,
|
|
147
|
+
*,
|
|
148
|
+
contact_id: Optional[str] = None,
|
|
149
|
+
kind: Optional[str] = None,
|
|
150
|
+
limit: int = 100,
|
|
151
|
+
reader_scope: str = OWNER_VISIBILITY,
|
|
152
|
+
) -> List[Dict[str, Any]]:
|
|
153
|
+
"""Owner-scoped read. Any non-owner reader scope gets nothing —
|
|
154
|
+
second-order inferences are for the owner's understanding only."""
|
|
155
|
+
if reader_scope != OWNER_VISIBILITY:
|
|
156
|
+
return []
|
|
157
|
+
clauses, params = ["visibility = ?"], [OWNER_VISIBILITY]
|
|
158
|
+
if contact_id:
|
|
159
|
+
clauses.append("contact_id = ?")
|
|
160
|
+
params.append(contact_id)
|
|
161
|
+
if kind:
|
|
162
|
+
clauses.append("kind = ?")
|
|
163
|
+
params.append(kind)
|
|
164
|
+
params.append(int(limit))
|
|
165
|
+
with self._lock:
|
|
166
|
+
rows = self._conn.execute(
|
|
167
|
+
f"SELECT * FROM tom2_inferences WHERE {' AND '.join(clauses)}"
|
|
168
|
+
" ORDER BY updated_at DESC LIMIT ?", params).fetchall()
|
|
169
|
+
return [self._to_dict(r) for r in rows]
|
|
170
|
+
|
|
171
|
+
def counts(self) -> Dict[str, Any]:
|
|
172
|
+
"""Aggregate observability (safe in any mode: numbers only)."""
|
|
173
|
+
with self._lock:
|
|
174
|
+
rows = self._conn.execute(
|
|
175
|
+
"SELECT kind, COUNT(*) n FROM tom2_inferences GROUP BY kind"
|
|
176
|
+
).fetchall()
|
|
177
|
+
contacts = self._conn.execute(
|
|
178
|
+
"SELECT COUNT(DISTINCT contact_id) n FROM tom2_inferences"
|
|
179
|
+
).fetchone()
|
|
180
|
+
by_kind = {r["kind"]: r["n"] for r in rows}
|
|
181
|
+
return {"total": sum(by_kind.values()), "by_kind": by_kind,
|
|
182
|
+
"contacts": int(contacts["n"]) if contacts else 0}
|
|
183
|
+
|
|
184
|
+
def close(self) -> None:
|
|
185
|
+
try:
|
|
186
|
+
self._conn.close()
|
|
187
|
+
except Exception:
|
|
188
|
+
pass
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# ---------------------------------------------------------------------------
|
|
192
|
+
# Cross-contact rendering gate (H3.5) — BUILT, SHIPPED DARK, UNWIRED.
|
|
193
|
+
#
|
|
194
|
+
# This is the ONLY sanctioned way a tom2 inference could ever be rendered for
|
|
195
|
+
# a non-owner contact, and it is deliberately NOT called from any live
|
|
196
|
+
# injection path (context assembly, briefings, delivery — nothing). Flipping
|
|
197
|
+
# COLONY_TOM2_CROSS_CONTEXT alone therefore changes no live behavior; wiring
|
|
198
|
+
# it anywhere is a separate owner decision that additionally requires the
|
|
199
|
+
# chat guard to be ENFORCING (the doctor warns on the incoherent combination)
|
|
200
|
+
# because "X hasn't heard this" carries implication-leak risk that only an
|
|
201
|
+
# enforcing outbound guard can backstop.
|
|
202
|
+
# ---------------------------------------------------------------------------
|
|
203
|
+
|
|
204
|
+
def tom2_cross_context_enabled() -> bool:
|
|
205
|
+
"""COLONY_TOM2_CROSS_CONTEXT (default 0): first half of the double gate.
|
|
206
|
+
Ships OFF by design; see the block comment above."""
|
|
207
|
+
return os.environ.get("COLONY_TOM2_CROSS_CONTEXT", "0").strip().lower() \
|
|
208
|
+
in ("1", "true", "yes", "on")
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _ref_visible_to(facts_store: Any, ref: str, contact_id: str) -> bool:
|
|
212
|
+
"""A fact ref is visible to a contact only when the fact ROW belongs to
|
|
213
|
+
that contact (it was shared with / told by them). Missing or unreadable
|
|
214
|
+
facts are NOT visible — fail closed."""
|
|
215
|
+
try:
|
|
216
|
+
f = facts_store.get_fact(str(ref or ""))
|
|
217
|
+
except Exception:
|
|
218
|
+
return False
|
|
219
|
+
return bool(f) and str(f.get("contact_id") or "") == str(contact_id)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def render_inference_for_contact(inference: Dict[str, Any], facts_store: Any,
|
|
223
|
+
contact_id: str) -> Optional[str]:
|
|
224
|
+
"""Render ONE inference for a non-owner contact, or None.
|
|
225
|
+
|
|
226
|
+
Second half of the double gate: EVERY ref the inference rests on
|
|
227
|
+
(fact_ref AND each evidence_ref) must be independently visible to the
|
|
228
|
+
reading contact. ANY partial visibility renders None — never a redacted
|
|
229
|
+
line, never a hint that something was withheld (a redaction IS a leak
|
|
230
|
+
of topology). Inferences about the reading contact themselves render
|
|
231
|
+
None (first-order information wearing a second-order hat).
|
|
232
|
+
"""
|
|
233
|
+
if not tom2_cross_context_enabled():
|
|
234
|
+
return None
|
|
235
|
+
if facts_store is None or not contact_id or not isinstance(inference, dict):
|
|
236
|
+
return None
|
|
237
|
+
subject_cid = str(inference.get("contact_id") or "")
|
|
238
|
+
if not subject_cid or subject_cid == str(contact_id):
|
|
239
|
+
return None
|
|
240
|
+
refs = [inference.get("fact_ref")] + list(inference.get("evidence_refs")
|
|
241
|
+
or [])
|
|
242
|
+
if not refs or any(not _ref_visible_to(facts_store, r, contact_id)
|
|
243
|
+
for r in refs):
|
|
244
|
+
return None
|
|
245
|
+
fact = facts_store.get_fact(str(inference.get("fact_ref")))
|
|
246
|
+
text = str((fact or {}).get("fact") or "").strip()
|
|
247
|
+
if not text:
|
|
248
|
+
return None
|
|
249
|
+
kind = inference.get("kind")
|
|
250
|
+
if kind == "unaware_of":
|
|
251
|
+
return f"{subject_cid} has not heard: {text[:160]}"
|
|
252
|
+
if kind == "knows":
|
|
253
|
+
return f"{subject_cid} already knows: {text[:160]}"
|
|
254
|
+
return None
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def render_for_contact(store: Any, facts_store: Any, contact_id: str,
|
|
258
|
+
*, limit: int = 5) -> Optional[str]:
|
|
259
|
+
"""Aggregate cross-contact rendering: only inferences whose EVERY ref is
|
|
260
|
+
visible to the reader make it in; None when the gate is off, nothing is
|
|
261
|
+
fully visible, or anything errors (fail closed, no partial output)."""
|
|
262
|
+
if not tom2_cross_context_enabled():
|
|
263
|
+
return None
|
|
264
|
+
if store is None or facts_store is None or not contact_id:
|
|
265
|
+
return None
|
|
266
|
+
try:
|
|
267
|
+
rows = store.list_inferences(limit=100)
|
|
268
|
+
except Exception:
|
|
269
|
+
return None
|
|
270
|
+
lines: List[str] = []
|
|
271
|
+
for r in rows:
|
|
272
|
+
line = render_inference_for_contact(r, facts_store, contact_id)
|
|
273
|
+
if line:
|
|
274
|
+
lines.append(f"- {line}")
|
|
275
|
+
if len(lines) >= limit:
|
|
276
|
+
break
|
|
277
|
+
return "\n".join(lines) if lines else None
|