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,16 @@
|
|
|
1
|
+
"""Concrete JobHandler subclasses for Colony's distributed task queue."""
|
|
2
|
+
|
|
3
|
+
from apsimo.task_queue.handlers.inference import (
|
|
4
|
+
InferenceHandler,
|
|
5
|
+
ThoughtOnlyInferenceHandler,
|
|
6
|
+
)
|
|
7
|
+
from apsimo.task_queue.handlers.monitoring import MonitoringHandler
|
|
8
|
+
from apsimo.task_queue.handlers.system_maintenance import SystemMaintenanceHandler
|
|
9
|
+
from apsimo.task_queue.handlers.registry import build_default_handlers
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"InferenceHandler", "ThoughtOnlyInferenceHandler",
|
|
13
|
+
"MonitoringHandler",
|
|
14
|
+
"SystemMaintenanceHandler",
|
|
15
|
+
"build_default_handlers",
|
|
16
|
+
]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Shared helpers for job handlers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Any, Dict
|
|
8
|
+
|
|
9
|
+
from apsimo.task_queue.worker import JobHandler
|
|
10
|
+
from apsimo.task_queue.models import Job
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def run_subprocess(
|
|
16
|
+
cmd: list[str], timeout: float
|
|
17
|
+
) -> tuple[int, str, str]:
|
|
18
|
+
"""Run a subprocess, capture stdout/stderr, and return (returncode, stdout, stderr)."""
|
|
19
|
+
proc = await asyncio.create_subprocess_exec(
|
|
20
|
+
*cmd,
|
|
21
|
+
stdout=asyncio.subprocess.PIPE,
|
|
22
|
+
stderr=asyncio.subprocess.PIPE,
|
|
23
|
+
)
|
|
24
|
+
try:
|
|
25
|
+
stdout_b, stderr_b = await asyncio.wait_for(
|
|
26
|
+
proc.communicate(), timeout=timeout
|
|
27
|
+
)
|
|
28
|
+
except asyncio.TimeoutError:
|
|
29
|
+
proc.kill()
|
|
30
|
+
await proc.communicate()
|
|
31
|
+
raise asyncio.TimeoutError(
|
|
32
|
+
f"Subprocess {cmd[0]!r} timed out after {timeout}s"
|
|
33
|
+
)
|
|
34
|
+
return proc.returncode, stdout_b.decode(), stderr_b.decode()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
__all__ = ["JobHandler", "Job", "run_subprocess"]
|
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
"""InferenceHandler — context-enriched LLM inference via the Colony router."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import secrets
|
|
10
|
+
import time
|
|
11
|
+
import uuid as _uuid_mod
|
|
12
|
+
from types import SimpleNamespace
|
|
13
|
+
from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
|
14
|
+
|
|
15
|
+
from apsimo.task_queue.handlers.base import JobHandler, Job
|
|
16
|
+
from apsimo.task_queue.models import JobType
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from apsimo.router.router import LLMRouter
|
|
20
|
+
from apsimo.world_model.store import WorldModelStore
|
|
21
|
+
from apsimo.world_model.entities import BaseEntity
|
|
22
|
+
from apsimo.contacts.store import ContactStore
|
|
23
|
+
from apsimo.contacts.models import Contact
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
_COLONY_IDENTITY = (
|
|
28
|
+
"You are Apsimo, an intelligent personal AI assistant. "
|
|
29
|
+
"You help the user manage their relationships, tasks, and knowledge. "
|
|
30
|
+
"You have access to context about the user's world and the people in it."
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
_ENTITY_TYPE_MAP = {
|
|
34
|
+
"person": "PersonEntity",
|
|
35
|
+
"company": "CompanyEntity",
|
|
36
|
+
"location": "LocationEntity",
|
|
37
|
+
"product": "ProductEntity",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _build_system_prompt(
|
|
42
|
+
contact: Optional["Contact"],
|
|
43
|
+
wm_entities: List["BaseEntity"],
|
|
44
|
+
explicit_prompt: Optional[str],
|
|
45
|
+
) -> str:
|
|
46
|
+
parts = [_COLONY_IDENTITY]
|
|
47
|
+
|
|
48
|
+
if contact:
|
|
49
|
+
name = (
|
|
50
|
+
contact.display_name
|
|
51
|
+
or " ".join(p for p in [contact.given_name, contact.family_name] if p)
|
|
52
|
+
or "this contact"
|
|
53
|
+
)
|
|
54
|
+
lines = [f"\nContact context: You are communicating with {name}."]
|
|
55
|
+
lines.append(f"Recorded standing: {contact.trust_tier}. Follow the request's actual authority boundary.")
|
|
56
|
+
if contact.organization:
|
|
57
|
+
lines.append(f"Organization: {contact.organization}")
|
|
58
|
+
if contact.notes:
|
|
59
|
+
lines.append(f"Notes: {contact.notes}")
|
|
60
|
+
parts.append("\n".join(lines))
|
|
61
|
+
|
|
62
|
+
if wm_entities:
|
|
63
|
+
entity_lines = ["\nRelevant context from your world model:"]
|
|
64
|
+
for e in wm_entities[:5]:
|
|
65
|
+
desc = f"- {e.name} ({e.entity_type})"
|
|
66
|
+
bio = getattr(e, "bio_summary", None) or getattr(e, "description", None)
|
|
67
|
+
if bio:
|
|
68
|
+
desc += f": {bio}"
|
|
69
|
+
entity_lines.append(desc)
|
|
70
|
+
parts.append("\n".join(entity_lines))
|
|
71
|
+
|
|
72
|
+
if explicit_prompt:
|
|
73
|
+
parts.append(f"\n{explicit_prompt}")
|
|
74
|
+
|
|
75
|
+
return "\n".join(parts)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
async def _update_world_model_async(
|
|
79
|
+
wm: "WorldModelStore",
|
|
80
|
+
user_text: str,
|
|
81
|
+
assistant_text: str,
|
|
82
|
+
source_id: str,
|
|
83
|
+
) -> None:
|
|
84
|
+
"""Extract entities from the exchange and write to world model.
|
|
85
|
+
|
|
86
|
+
Runs fire-and-forget. All errors are swallowed to never block the response.
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
from apsimo.world_model.extraction.conversation_extractor import ConversationExtractor
|
|
90
|
+
from apsimo.world_model.entities import (
|
|
91
|
+
PersonEntity, CompanyEntity, LocationEntity, ProductEntity,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
type_cls_map = {
|
|
95
|
+
"person": PersonEntity,
|
|
96
|
+
"company": CompanyEntity,
|
|
97
|
+
"location": LocationEntity,
|
|
98
|
+
"product": ProductEntity,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
extractor = ConversationExtractor(min_message_length=20)
|
|
102
|
+
full_text = f"{user_text}\n{assistant_text}"
|
|
103
|
+
result = await extractor.extract(full_text, source_id=source_id)
|
|
104
|
+
|
|
105
|
+
for candidate in result.entities:
|
|
106
|
+
if candidate.confidence < 0.20:
|
|
107
|
+
continue
|
|
108
|
+
try:
|
|
109
|
+
existing = await wm.find_entities(
|
|
110
|
+
candidate.text,
|
|
111
|
+
entity_type=candidate.entity_type,
|
|
112
|
+
min_confidence=0.20,
|
|
113
|
+
limit=1,
|
|
114
|
+
)
|
|
115
|
+
if existing:
|
|
116
|
+
await wm.add_observation(
|
|
117
|
+
existing[0].id,
|
|
118
|
+
None,
|
|
119
|
+
f"Mentioned in conversation: {candidate.context_window}",
|
|
120
|
+
source="inference",
|
|
121
|
+
)
|
|
122
|
+
else:
|
|
123
|
+
entity_cls = type_cls_map.get(candidate.entity_type, PersonEntity)
|
|
124
|
+
ts = int(time.time() * 1000)
|
|
125
|
+
rand = secrets.token_hex(6)
|
|
126
|
+
new_entity = entity_cls(
|
|
127
|
+
id=f"we-{ts}-{rand}",
|
|
128
|
+
name=candidate.text,
|
|
129
|
+
confidence=candidate.confidence,
|
|
130
|
+
)
|
|
131
|
+
await wm.upsert_entity(new_entity)
|
|
132
|
+
await wm.add_observation(
|
|
133
|
+
new_entity.id,
|
|
134
|
+
None,
|
|
135
|
+
f"First mentioned in conversation: {candidate.context_window}",
|
|
136
|
+
source="inference",
|
|
137
|
+
)
|
|
138
|
+
logger.debug(
|
|
139
|
+
"World model: created entity %r (%s)",
|
|
140
|
+
candidate.text,
|
|
141
|
+
candidate.entity_type,
|
|
142
|
+
)
|
|
143
|
+
except Exception:
|
|
144
|
+
logger.debug(
|
|
145
|
+
"World model upsert failed for %r", candidate.text, exc_info=True
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
except Exception:
|
|
149
|
+
logger.debug("Post-inference world model update failed", exc_info=True)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class _InferenceGateSessionStore:
|
|
153
|
+
"""Minimal session-store adapter for running the ResponseGate on task-queue jobs.
|
|
154
|
+
|
|
155
|
+
Inference jobs are internal and have no real gateway session. This adapter
|
|
156
|
+
creates ephemeral session records so that L1 (RecipientVerifier) and L3
|
|
157
|
+
(CrossContextDetector) can operate without failing on missing-session lookups.
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
def __init__(self) -> None:
|
|
161
|
+
self._sessions: dict = {}
|
|
162
|
+
|
|
163
|
+
def register(self, session_id: str, contact_id: str, gateway: str) -> None:
|
|
164
|
+
"""Register an ephemeral session before calling gate.evaluate()."""
|
|
165
|
+
self._sessions[session_id] = SimpleNamespace(contact_id=contact_id, gateway=gateway)
|
|
166
|
+
|
|
167
|
+
async def get(self, session_id: str):
|
|
168
|
+
return self._sessions.get(session_id)
|
|
169
|
+
|
|
170
|
+
async def get_contact_gateways(self, contact_id: str) -> set:
|
|
171
|
+
return {"api"}
|
|
172
|
+
|
|
173
|
+
async def get_recent_other_sessions(
|
|
174
|
+
self, exclude_session_id: str, lookback_hours: int
|
|
175
|
+
) -> dict:
|
|
176
|
+
return {}
|
|
177
|
+
|
|
178
|
+
async def get_display_name(self, contact_id: str) -> str:
|
|
179
|
+
return contact_id
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class InferenceHandler(JobHandler):
|
|
183
|
+
"""Run an LLM inference request via the Colony router.
|
|
184
|
+
|
|
185
|
+
Job payload keys:
|
|
186
|
+
messages (list[dict], optional): OpenAI-format messages list. Used as-is.
|
|
187
|
+
prompt (str, optional): Plain string prompt; wrapped into a user message.
|
|
188
|
+
One of ``messages`` or ``prompt`` must be provided.
|
|
189
|
+
model_tier (str): "small" | "medium" | "large" (default: "small").
|
|
190
|
+
system_prompt (str, optional): Additional system context appended after
|
|
191
|
+
the enriched Colony identity + contact + world model prompt.
|
|
192
|
+
max_tokens (int, optional): Limit (currently passed as context hint).
|
|
193
|
+
contact_id (str, optional): If provided, contact record is fetched and
|
|
194
|
+
included in the system prompt (name, trust tier, relationship score).
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
{"result": str, "tokens_used": int, "model": str}
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
def __init__(
|
|
201
|
+
self,
|
|
202
|
+
router: "LLMRouter",
|
|
203
|
+
world_model_store: Optional["WorldModelStore"] = None,
|
|
204
|
+
contact_store: Optional["ContactStore"] = None,
|
|
205
|
+
response_gate: Optional[Any] = None,
|
|
206
|
+
gate_session_store: Optional["_InferenceGateSessionStore"] = None,
|
|
207
|
+
) -> None:
|
|
208
|
+
self._router = router
|
|
209
|
+
self._wm = world_model_store
|
|
210
|
+
self._cs = contact_store
|
|
211
|
+
self._wm_connected = False
|
|
212
|
+
self._gate = response_gate
|
|
213
|
+
self._gate_sessions = gate_session_store
|
|
214
|
+
self._background_tasks: set = set() # strong refs to fire-and-forget tasks
|
|
215
|
+
|
|
216
|
+
async def _ensure_wm_connected(self) -> None:
|
|
217
|
+
"""Connect the world model store on first use.
|
|
218
|
+
|
|
219
|
+
If no store was injected, create one from the default config so that
|
|
220
|
+
inference jobs self-initialize world model support without requiring
|
|
221
|
+
changes to startup code.
|
|
222
|
+
"""
|
|
223
|
+
if self._wm_connected:
|
|
224
|
+
return
|
|
225
|
+
if self._wm is None:
|
|
226
|
+
try:
|
|
227
|
+
from apsimo.world_model.store import WorldModelStore
|
|
228
|
+
from apsimo.world_model.config import WorldModelConfig
|
|
229
|
+
import os
|
|
230
|
+
colony_home = os.environ.get("COLONY_HOME", os.path.expanduser("~/.colony"))
|
|
231
|
+
db_path = os.path.join(colony_home, "world_model.db")
|
|
232
|
+
self._wm = WorldModelStore(WorldModelConfig(sqlite_path=db_path))
|
|
233
|
+
except Exception:
|
|
234
|
+
logger.warning("Could not create WorldModelStore", exc_info=True)
|
|
235
|
+
return
|
|
236
|
+
try:
|
|
237
|
+
await self._wm.connect()
|
|
238
|
+
self._wm_connected = True
|
|
239
|
+
except Exception:
|
|
240
|
+
logger.warning("Could not connect world model store", exc_info=True)
|
|
241
|
+
self._wm = None
|
|
242
|
+
|
|
243
|
+
async def _execute_thought(self, job: Job) -> Dict[str, Any]:
|
|
244
|
+
"""Execute exactly the digest-bound ThoughtJobV1 prompt contract."""
|
|
245
|
+
|
|
246
|
+
from apsimo.cognition.goal_spine import (
|
|
247
|
+
ThoughtJobV1,
|
|
248
|
+
bind_thought_output,
|
|
249
|
+
)
|
|
250
|
+
from apsimo.router.tiers import ModelTier
|
|
251
|
+
|
|
252
|
+
thought = ThoughtJobV1.from_payload(job.payload)
|
|
253
|
+
if job.job_id != thought.thought_job_id:
|
|
254
|
+
raise ValueError("thought queue job ID does not match authority digest")
|
|
255
|
+
messages = [
|
|
256
|
+
{"role": "system", "content": thought.system_prompt},
|
|
257
|
+
{"role": "user", "content": thought.prompt},
|
|
258
|
+
]
|
|
259
|
+
response = await self._router.complete(
|
|
260
|
+
messages,
|
|
261
|
+
force_tier=(
|
|
262
|
+
None if getattr(self._router, "supports_function_routing", False) is True
|
|
263
|
+
else ModelTier.SMALL
|
|
264
|
+
),
|
|
265
|
+
context={
|
|
266
|
+
"task": "thought_job",
|
|
267
|
+
"max_output_tokens": thought.max_output_tokens,
|
|
268
|
+
},
|
|
269
|
+
)
|
|
270
|
+
usage = response.usage or {}
|
|
271
|
+
tokens_used = int(usage.get("total_tokens", 0) or 0)
|
|
272
|
+
completion_tokens = int(
|
|
273
|
+
usage.get("completion_tokens", 0) or 0
|
|
274
|
+
)
|
|
275
|
+
if completion_tokens > thought.max_output_tokens:
|
|
276
|
+
raise ValueError("thought output exceeded its token budget")
|
|
277
|
+
bound_output = bind_thought_output(response.content, thought)
|
|
278
|
+
canonical_output = json.dumps(
|
|
279
|
+
bound_output.payload,
|
|
280
|
+
sort_keys=True,
|
|
281
|
+
separators=(",", ":"),
|
|
282
|
+
ensure_ascii=False,
|
|
283
|
+
allow_nan=False,
|
|
284
|
+
)
|
|
285
|
+
return {
|
|
286
|
+
"status": "completed",
|
|
287
|
+
"summary": canonical_output,
|
|
288
|
+
"action_plane": {"state": "completed"},
|
|
289
|
+
"result": canonical_output,
|
|
290
|
+
"thought_output": canonical_output,
|
|
291
|
+
"tokens_used": tokens_used,
|
|
292
|
+
"completion_tokens": completion_tokens,
|
|
293
|
+
"model": response.model_id,
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async def _gate_context(
|
|
297
|
+
self,
|
|
298
|
+
messages: list[dict],
|
|
299
|
+
user_text: str,
|
|
300
|
+
force_tier: Optional[Any],
|
|
301
|
+
payload: Dict[str, Any],
|
|
302
|
+
*,
|
|
303
|
+
router_context: Optional[Dict[str, Any]] = None,
|
|
304
|
+
) -> list[dict]:
|
|
305
|
+
"""Shrink oversized message content to the target tier's useful window.
|
|
306
|
+
|
|
307
|
+
Uses the context gate (:mod:`apsimo.contextgate`): when the
|
|
308
|
+
assembled messages exceed the tier's ``useful_context_tokens`` the
|
|
309
|
+
largest message content is chunked and retrieved/sampled down to
|
|
310
|
+
budget. No-op when the gate is off, the budget is unknown, or the
|
|
311
|
+
input already fits. Jobs may opt out via ``payload["context_gate"]
|
|
312
|
+
= "off"`` and may pass an explicit ``payload["query"]`` to focus
|
|
313
|
+
retrieval.
|
|
314
|
+
"""
|
|
315
|
+
if payload.get("context_gate") == "off":
|
|
316
|
+
return messages
|
|
317
|
+
try:
|
|
318
|
+
from apsimo.contextgate import (
|
|
319
|
+
GateConfig,
|
|
320
|
+
estimate_tokens,
|
|
321
|
+
prepare_context,
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
gcfg = GateConfig.from_env()
|
|
325
|
+
if gcfg.mode == "off":
|
|
326
|
+
return messages
|
|
327
|
+
|
|
328
|
+
# Budget from the tier the call will actually use
|
|
329
|
+
tier = force_tier
|
|
330
|
+
if force_tier is None and getattr(self._router, 'supports_function_routing', False) is True:
|
|
331
|
+
tier_cfg = self._router.function_config(context=router_context)
|
|
332
|
+
tier = getattr(tier_cfg, "tier", None)
|
|
333
|
+
else:
|
|
334
|
+
if tier is None:
|
|
335
|
+
try:
|
|
336
|
+
tier = self._router.route(user_text, router_context or {})[0]
|
|
337
|
+
except Exception:
|
|
338
|
+
return messages
|
|
339
|
+
tier_cfg = getattr(self._router, "tier_config", lambda _t: None)(tier)
|
|
340
|
+
budget = getattr(tier_cfg, "useful_context_tokens", 0) or gcfg.default_budget_tokens
|
|
341
|
+
if budget <= 0:
|
|
342
|
+
return messages
|
|
343
|
+
|
|
344
|
+
est_total = sum(
|
|
345
|
+
estimate_tokens(str(m.get("content") or "")) for m in messages
|
|
346
|
+
)
|
|
347
|
+
if est_total <= budget * gcfg.headroom:
|
|
348
|
+
return messages
|
|
349
|
+
|
|
350
|
+
# Gate the single largest content block (usually the document)
|
|
351
|
+
idx = max(
|
|
352
|
+
range(len(messages)),
|
|
353
|
+
key=lambda i: len(str(messages[i].get("content") or "")),
|
|
354
|
+
)
|
|
355
|
+
big = str(messages[idx].get("content") or "")
|
|
356
|
+
# Query focus: explicit payload query wins; if the oversized
|
|
357
|
+
# message is itself the user turn, its tail usually carries the
|
|
358
|
+
# actual question, otherwise the whole user text is the query.
|
|
359
|
+
query = payload.get("query") or (
|
|
360
|
+
user_text[-1000:] if big == user_text else user_text[:2000]
|
|
361
|
+
)
|
|
362
|
+
overhead = est_total - estimate_tokens(big)
|
|
363
|
+
prepared = await prepare_context(
|
|
364
|
+
content=big,
|
|
365
|
+
query=query,
|
|
366
|
+
budget_tokens=max(1, budget - overhead),
|
|
367
|
+
task_kind=payload.get("task_kind"),
|
|
368
|
+
config=gcfg,
|
|
369
|
+
)
|
|
370
|
+
from apsimo.contextgate import GateDecision
|
|
371
|
+
|
|
372
|
+
if prepared.decision != GateDecision.PASS_THROUGH:
|
|
373
|
+
gated = list(messages)
|
|
374
|
+
gated[idx] = {**messages[idx], "content": prepared.text}
|
|
375
|
+
logger.info(
|
|
376
|
+
"Inference context gated: %s, %d -> %d est tokens (tier=%s)",
|
|
377
|
+
prepared.decision.value,
|
|
378
|
+
prepared.est_tokens_in,
|
|
379
|
+
prepared.est_tokens_out,
|
|
380
|
+
getattr(tier, "value", tier),
|
|
381
|
+
)
|
|
382
|
+
return gated
|
|
383
|
+
return messages
|
|
384
|
+
except Exception:
|
|
385
|
+
logger.warning("Context gate failed — sending messages ungated", exc_info=True)
|
|
386
|
+
return messages
|
|
387
|
+
|
|
388
|
+
async def execute(self, job: Job) -> Dict[str, Any]:
|
|
389
|
+
payload = job.payload
|
|
390
|
+
if (
|
|
391
|
+
job.job_type is JobType.THOUGHT
|
|
392
|
+
or payload.get("schema") == "ThoughtJobV1"
|
|
393
|
+
):
|
|
394
|
+
if job.job_type is not JobType.THOUGHT:
|
|
395
|
+
raise ValueError("ThoughtJobV1 requires the thought job type")
|
|
396
|
+
return await self._execute_thought(job)
|
|
397
|
+
cognition_read_only = payload.get("cognition_read_only") is True
|
|
398
|
+
read_capabilities = set(payload.get("allowed_read_capabilities") or ())
|
|
399
|
+
if cognition_read_only:
|
|
400
|
+
invalid = [
|
|
401
|
+
capability for capability in read_capabilities
|
|
402
|
+
if capability not in {
|
|
403
|
+
"concerns:read", "directives:read", "memory:read",
|
|
404
|
+
"projects:read", "reasoning", "situation:read",
|
|
405
|
+
"web:read", "world_model:read",
|
|
406
|
+
}
|
|
407
|
+
]
|
|
408
|
+
if invalid:
|
|
409
|
+
raise ValueError("read-only cognition job contains invalid capabilities")
|
|
410
|
+
model_tier = payload.get("model_tier")
|
|
411
|
+
contact_id: Optional[str] = payload.get("contact_id")
|
|
412
|
+
explicit_system = payload.get("system_prompt")
|
|
413
|
+
|
|
414
|
+
# ── Resolve tier ───────────────────────────────────────────────────
|
|
415
|
+
force_tier = None
|
|
416
|
+
try:
|
|
417
|
+
from apsimo.router.tiers import ModelTier
|
|
418
|
+
force_tier = ModelTier(model_tier)
|
|
419
|
+
except (ImportError, ValueError):
|
|
420
|
+
pass
|
|
421
|
+
|
|
422
|
+
# ── Extract user text ──────────────────────────────────────────────
|
|
423
|
+
if "messages" in payload:
|
|
424
|
+
messages_in: list[dict] = list(payload["messages"])
|
|
425
|
+
user_text = next(
|
|
426
|
+
(m.get("content", "") for m in reversed(messages_in) if m.get("role") == "user"),
|
|
427
|
+
"",
|
|
428
|
+
)
|
|
429
|
+
else:
|
|
430
|
+
user_text = payload.get("prompt", "")
|
|
431
|
+
messages_in = None
|
|
432
|
+
|
|
433
|
+
# ── Ensure world model is connected ────────────────────────────────
|
|
434
|
+
if not cognition_read_only or "world_model:read" in read_capabilities:
|
|
435
|
+
await self._ensure_wm_connected()
|
|
436
|
+
|
|
437
|
+
# ── Contact lookup ─────────────────────────────────────────────────
|
|
438
|
+
contact: Optional["Contact"] = None
|
|
439
|
+
if contact_id and self._cs and not cognition_read_only:
|
|
440
|
+
try:
|
|
441
|
+
contact = await self._cs.get(contact_id)
|
|
442
|
+
except Exception:
|
|
443
|
+
logger.debug("Contact lookup failed for %s", contact_id, exc_info=True)
|
|
444
|
+
|
|
445
|
+
# ── World model entity search ──────────────────────────────────────
|
|
446
|
+
# Extract entity names from the user message first, then look each up
|
|
447
|
+
# individually. Passing the full message as an FTS query fails because
|
|
448
|
+
# stop words prevent name matching.
|
|
449
|
+
wm_entities: List["BaseEntity"] = []
|
|
450
|
+
if self._wm and user_text and (
|
|
451
|
+
not cognition_read_only or "world_model:read" in read_capabilities
|
|
452
|
+
):
|
|
453
|
+
try:
|
|
454
|
+
from apsimo.world_model.extraction.conversation_extractor import (
|
|
455
|
+
ConversationExtractor,
|
|
456
|
+
)
|
|
457
|
+
extractor = ConversationExtractor(min_message_length=5)
|
|
458
|
+
extraction = await extractor.extract(user_text, source_id="pre-inference")
|
|
459
|
+
seen_ids: set = set()
|
|
460
|
+
for candidate in extraction.entities[:8]:
|
|
461
|
+
hits = await self._wm.find_entities(
|
|
462
|
+
candidate.text, limit=2, min_confidence=0.20
|
|
463
|
+
)
|
|
464
|
+
for e in hits:
|
|
465
|
+
if e.id not in seen_ids:
|
|
466
|
+
seen_ids.add(e.id)
|
|
467
|
+
wm_entities.append(e)
|
|
468
|
+
if len(wm_entities) >= 5:
|
|
469
|
+
break
|
|
470
|
+
if len(wm_entities) >= 5:
|
|
471
|
+
break
|
|
472
|
+
except Exception:
|
|
473
|
+
logger.debug("World model query failed", exc_info=True)
|
|
474
|
+
|
|
475
|
+
# ── Build enriched system prompt ───────────────────────────────────
|
|
476
|
+
enriched_system = _build_system_prompt(contact, wm_entities, explicit_system)
|
|
477
|
+
|
|
478
|
+
# ── Assemble messages ──────────────────────────────────────────────
|
|
479
|
+
if messages_in is not None:
|
|
480
|
+
messages: list[dict] = messages_in
|
|
481
|
+
if not any(m.get("role") == "system" for m in messages):
|
|
482
|
+
messages.insert(0, {"role": "system", "content": enriched_system})
|
|
483
|
+
else:
|
|
484
|
+
messages = [
|
|
485
|
+
{"role": "system", "content": enriched_system},
|
|
486
|
+
{"role": "user", "content": user_text},
|
|
487
|
+
]
|
|
488
|
+
|
|
489
|
+
router_context: Dict[str, Any] = {}
|
|
490
|
+
if cognition_read_only:
|
|
491
|
+
router_context = {
|
|
492
|
+
"task": "thought_job",
|
|
493
|
+
"max_output_tokens": int(payload.get("max_output_tokens") or 768),
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
# ── Context gate: fit input to the dispatch role's useful window ──
|
|
497
|
+
messages = await self._gate_context(
|
|
498
|
+
messages, user_text, force_tier, payload, router_context=router_context,
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
# ── LLM call ──────────────────────────────────────────────────────
|
|
502
|
+
response = await self._router.complete(
|
|
503
|
+
messages, force_tier=force_tier, context=router_context,
|
|
504
|
+
)
|
|
505
|
+
tokens_used = response.usage.get("total_tokens", 0) if response.usage else 0
|
|
506
|
+
completion_tokens = (
|
|
507
|
+
response.usage.get("completion_tokens", 0) if response.usage else 0
|
|
508
|
+
)
|
|
509
|
+
if cognition_read_only and completion_tokens > int(
|
|
510
|
+
payload.get("max_output_tokens") or 768
|
|
511
|
+
):
|
|
512
|
+
raise ValueError("thought output exceeded its token budget")
|
|
513
|
+
|
|
514
|
+
# ── GAP-14: Log tier selection + feed RouterSelfLearner ────────────
|
|
515
|
+
# Logging lets ops trace routing decisions; record_outcome lets the
|
|
516
|
+
# self-learner improve future tier thresholds from inference-path data.
|
|
517
|
+
logger.info(
|
|
518
|
+
"Inference tier=%s model=%s tokens=%d cost_usd=%.6f latency_ms=%d job_id=%s",
|
|
519
|
+
response.tier_used.value,
|
|
520
|
+
response.model_id,
|
|
521
|
+
tokens_used,
|
|
522
|
+
response.cost_usd,
|
|
523
|
+
response.latency_ms,
|
|
524
|
+
getattr(job, "job_id", "?"),
|
|
525
|
+
)
|
|
526
|
+
# Latency/tokens above are telemetry. Do not self-award a perfect
|
|
527
|
+
# quality label merely because the model returned; RouterSelfLearner
|
|
528
|
+
# must wait for downstream or owner evidence.
|
|
529
|
+
|
|
530
|
+
# Queue inference is an observation path. It must not hide a world-
|
|
531
|
+
# model mutation behind a read-looking completion. Entity ingestion
|
|
532
|
+
# remains owned by the explicit conversation/memory ingestion paths.
|
|
533
|
+
job_id_str = str(getattr(job, "job_id", None) or getattr(job, "id", _uuid_mod.uuid4()))
|
|
534
|
+
|
|
535
|
+
# ── Response Gate evaluation ────────────────────────────────────────
|
|
536
|
+
if (not cognition_read_only
|
|
537
|
+
and self._gate is not None and self._gate_sessions is not None):
|
|
538
|
+
from apsimo.gate.models import GatePayload
|
|
539
|
+
from apsimo.intelligence.relationships.trust_tiers import TrustTier
|
|
540
|
+
|
|
541
|
+
session_id = payload.get("session_id") or f"inf-{job_id_str}"
|
|
542
|
+
gateway = payload.get("gateway", "api")
|
|
543
|
+
gate_contact_id = contact_id or "internal"
|
|
544
|
+
|
|
545
|
+
# Resolve trust tier from contact record (fall back to PERIPHERAL)
|
|
546
|
+
tier = TrustTier.PERIPHERAL
|
|
547
|
+
if contact is not None:
|
|
548
|
+
try:
|
|
549
|
+
tier = TrustTier(contact.trust_tier)
|
|
550
|
+
except (ValueError, TypeError):
|
|
551
|
+
pass
|
|
552
|
+
|
|
553
|
+
self._gate_sessions.register(session_id, gate_contact_id, gateway)
|
|
554
|
+
gate_payload = GatePayload(
|
|
555
|
+
response_text=response.content,
|
|
556
|
+
target_contact_id=gate_contact_id,
|
|
557
|
+
target_gateway=gateway,
|
|
558
|
+
session_id=session_id,
|
|
559
|
+
trust_tier=tier,
|
|
560
|
+
mentioned_entities=frozenset(e.name for e in wm_entities),
|
|
561
|
+
turn_id=job_id_str,
|
|
562
|
+
incoming_message_text=user_text,
|
|
563
|
+
)
|
|
564
|
+
try:
|
|
565
|
+
gate_decision = await self._gate.evaluate(gate_payload)
|
|
566
|
+
if gate_decision.blocked:
|
|
567
|
+
logger.warning(
|
|
568
|
+
"Gate blocked inference response: layer=%d reason=%s turn_id=%s",
|
|
569
|
+
gate_decision.blocking_layer,
|
|
570
|
+
gate_decision.block_reason,
|
|
571
|
+
job_id_str,
|
|
572
|
+
)
|
|
573
|
+
return {
|
|
574
|
+
"status": "skipped",
|
|
575
|
+
"summary": "response blocked by the configured response gate",
|
|
576
|
+
"action_plane": {"state": "skipped"},
|
|
577
|
+
"result": f"[Response blocked by gate layer {gate_decision.blocking_layer}: {gate_decision.block_reason}]",
|
|
578
|
+
"tokens_used": tokens_used,
|
|
579
|
+
"model": response.model_id,
|
|
580
|
+
"gate_blocked": True,
|
|
581
|
+
"gate_reason": gate_decision.block_reason,
|
|
582
|
+
"gate_layer": gate_decision.blocking_layer,
|
|
583
|
+
}
|
|
584
|
+
logger.debug(
|
|
585
|
+
"Gate passed inference response: turn_id=%s layers_evaluated=7",
|
|
586
|
+
job_id_str,
|
|
587
|
+
)
|
|
588
|
+
except Exception:
|
|
589
|
+
logger.warning(
|
|
590
|
+
"Gate evaluation failed; applying recipient fallback: turn_id=%s",
|
|
591
|
+
job_id_str,
|
|
592
|
+
exc_info=True,
|
|
593
|
+
)
|
|
594
|
+
owner_id = os.environ.get(
|
|
595
|
+
"COLONY_OWNER_PERSON_ID",
|
|
596
|
+
os.environ.get("COLONY_OWNER_CONTACT_ID", "owner"),
|
|
597
|
+
).strip() or "owner"
|
|
598
|
+
if contact_id and contact_id not in {"internal", owner_id}:
|
|
599
|
+
return {
|
|
600
|
+
"status": "skipped",
|
|
601
|
+
"summary": (
|
|
602
|
+
"response held because the recipient gate was "
|
|
603
|
+
"unavailable"
|
|
604
|
+
),
|
|
605
|
+
"action_plane": {"state": "skipped"},
|
|
606
|
+
"result": "[Response held for recipient review]",
|
|
607
|
+
"tokens_used": tokens_used,
|
|
608
|
+
"model": response.model_id,
|
|
609
|
+
"gate_blocked": True,
|
|
610
|
+
"gate_reason": "recipient_gate_unavailable",
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
return {
|
|
614
|
+
"status": "completed",
|
|
615
|
+
"summary": response.content,
|
|
616
|
+
"action_plane": {"state": "completed"},
|
|
617
|
+
"result": response.content,
|
|
618
|
+
"tokens_used": tokens_used,
|
|
619
|
+
"completion_tokens": completion_tokens,
|
|
620
|
+
"model": response.model_id,
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
class ThoughtOnlyInferenceHandler(InferenceHandler):
|
|
625
|
+
"""P3 handler that refuses every non-ThoughtJobV1 lane.
|
|
626
|
+
|
|
627
|
+
This is a deployment boundary, not merely a branch in the generic
|
|
628
|
+
inference handler. A worker carrying only this handler can advertise only
|
|
629
|
+
``thought`` and cannot race general workers for other queue lanes.
|
|
630
|
+
"""
|
|
631
|
+
|
|
632
|
+
thought_only = True
|
|
633
|
+
handler_contract = "thought_only:v1"
|
|
634
|
+
|
|
635
|
+
async def execute(self, job: Job) -> Dict[str, Any]:
|
|
636
|
+
if job.job_type is not JobType.THOUGHT:
|
|
637
|
+
raise ValueError("thought-only handler refuses non-thought job type")
|
|
638
|
+
if (job.payload or {}).get("schema") != "ThoughtJobV1":
|
|
639
|
+
raise ValueError("thought-only handler requires ThoughtJobV1")
|
|
640
|
+
return await self._execute_thought(job)
|