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,358 @@
|
|
|
1
|
+
"""Research Orchestrator — coordinate research across multiple sources.
|
|
2
|
+
|
|
3
|
+
Capabilities:
|
|
4
|
+
- Source prioritization
|
|
5
|
+
- Result synthesis
|
|
6
|
+
- Citation tracking
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Any, Awaitable, Callable, Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SourceType(str, Enum):
|
|
20
|
+
"""Types of research sources."""
|
|
21
|
+
|
|
22
|
+
WEB = "web"
|
|
23
|
+
KNOWLEDGE_GRAPH = "knowledge_graph"
|
|
24
|
+
MEMORY = "memory"
|
|
25
|
+
API = "api"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class ResearchSource:
|
|
30
|
+
"""A research source.
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
type: Category of source
|
|
34
|
+
name: Human-readable source name
|
|
35
|
+
priority: Trust/quality weight (0-1, higher = more trusted)
|
|
36
|
+
rate_limit: Optional requests-per-minute cap
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
type: SourceType
|
|
40
|
+
name: str
|
|
41
|
+
priority: float = 0.5
|
|
42
|
+
rate_limit: Optional[int] = None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class ResearchResult:
|
|
47
|
+
"""A result from a single research source.
|
|
48
|
+
|
|
49
|
+
Attributes:
|
|
50
|
+
source: Name of the source that produced this result
|
|
51
|
+
content: The actual content/finding
|
|
52
|
+
confidence: How confident the source is (0-1)
|
|
53
|
+
timestamp: When the result was obtained
|
|
54
|
+
citations: Reference URLs or identifiers
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
source: str
|
|
58
|
+
content: str
|
|
59
|
+
confidence: float
|
|
60
|
+
timestamp: datetime = field(default_factory=datetime.now)
|
|
61
|
+
citations: List[str] = field(default_factory=list)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass
|
|
65
|
+
class ResearchReport:
|
|
66
|
+
"""Aggregated research report.
|
|
67
|
+
|
|
68
|
+
Attributes:
|
|
69
|
+
query: The original research query
|
|
70
|
+
results: Individual results from each source
|
|
71
|
+
synthesized_summary: Combined summary across all results
|
|
72
|
+
confidence: Overall confidence score (average of results)
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
query: str
|
|
76
|
+
results: List[ResearchResult] = field(default_factory=list)
|
|
77
|
+
synthesized_summary: Optional[str] = None
|
|
78
|
+
confidence: float = 0.0
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class ResearchOrchestrator:
|
|
82
|
+
"""Orchestrate multi-source research.
|
|
83
|
+
|
|
84
|
+
Manages a registry of research sources, queries them in priority
|
|
85
|
+
order, synthesizes findings, and tracks citations.
|
|
86
|
+
|
|
87
|
+
Source routing:
|
|
88
|
+
- MEMORY: queries graph memory via ``graph.recall()``
|
|
89
|
+
- KNOWLEDGE_GRAPH: returns structured knowledge graph context
|
|
90
|
+
- WEB: queries the injected ``SearchOrchestrator`` if one is provided.
|
|
91
|
+
- API: delegates to a handler registered via ``register_api_handler``.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
graph_client: Colony graph client for knowledge graph queries
|
|
95
|
+
event_bus: Colony event bus for research lifecycle events
|
|
96
|
+
search_orchestrator: Optional SearchOrchestrator for WEB sources.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def __init__(
|
|
100
|
+
self,
|
|
101
|
+
graph_client: Any = None,
|
|
102
|
+
event_bus: Any = None,
|
|
103
|
+
search_orchestrator: Any = None,
|
|
104
|
+
) -> None:
|
|
105
|
+
self.graph = graph_client
|
|
106
|
+
self.events = event_bus
|
|
107
|
+
self.search = search_orchestrator
|
|
108
|
+
self._sources: List[ResearchSource] = []
|
|
109
|
+
self._api_handlers: Dict[str, Callable[[str], Awaitable[Optional[ResearchResult]]]] = {}
|
|
110
|
+
|
|
111
|
+
def register_api_handler(
|
|
112
|
+
self,
|
|
113
|
+
name: str,
|
|
114
|
+
handler: Callable[[str], Awaitable[Optional[ResearchResult]]],
|
|
115
|
+
) -> None:
|
|
116
|
+
"""Register an async handler for an API-typed source.
|
|
117
|
+
|
|
118
|
+
The handler receives the raw query string and must return a
|
|
119
|
+
``ResearchResult`` or ``None``.
|
|
120
|
+
"""
|
|
121
|
+
self._api_handlers[name] = handler
|
|
122
|
+
|
|
123
|
+
def register_source(self, source: ResearchSource) -> None:
|
|
124
|
+
"""Register a research source.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
source: Source to add to the registry
|
|
128
|
+
"""
|
|
129
|
+
self._sources.append(source)
|
|
130
|
+
logger.debug("Registered research source: %s (%s)", source.name, source.type.value)
|
|
131
|
+
|
|
132
|
+
def unregister_source(self, name: str) -> None:
|
|
133
|
+
"""Remove a research source by name.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
name: Name of the source to remove
|
|
137
|
+
"""
|
|
138
|
+
self._sources = [s for s in self._sources if s.name != name]
|
|
139
|
+
|
|
140
|
+
async def research(self, query: str, max_sources: int = 3) -> ResearchReport:
|
|
141
|
+
"""Conduct research across registered sources.
|
|
142
|
+
|
|
143
|
+
Queries sources in priority order (up to max_sources),
|
|
144
|
+
then synthesizes results into a unified report.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
query: The research question
|
|
148
|
+
max_sources: Maximum number of sources to query
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Aggregated research report with synthesized findings
|
|
152
|
+
"""
|
|
153
|
+
sorted_sources = sorted(
|
|
154
|
+
self._sources,
|
|
155
|
+
key=lambda s: s.priority,
|
|
156
|
+
reverse=True,
|
|
157
|
+
)[:max_sources]
|
|
158
|
+
|
|
159
|
+
results: List[ResearchResult] = []
|
|
160
|
+
for source in sorted_sources:
|
|
161
|
+
result = await self._query_source(source, query)
|
|
162
|
+
if result:
|
|
163
|
+
results.append(result)
|
|
164
|
+
|
|
165
|
+
summary = await self._synthesize(results, query)
|
|
166
|
+
|
|
167
|
+
confidence = (
|
|
168
|
+
sum(r.confidence for r in results) / len(results) if results else 0.0
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
report = ResearchReport(
|
|
172
|
+
query=query,
|
|
173
|
+
results=results,
|
|
174
|
+
synthesized_summary=summary,
|
|
175
|
+
confidence=confidence,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
logger.debug(
|
|
179
|
+
"Research complete for '%s': %d results, confidence=%.2f",
|
|
180
|
+
query[:50],
|
|
181
|
+
len(results),
|
|
182
|
+
confidence,
|
|
183
|
+
)
|
|
184
|
+
return report
|
|
185
|
+
|
|
186
|
+
async def _query_source(
|
|
187
|
+
self,
|
|
188
|
+
source: ResearchSource,
|
|
189
|
+
query: str,
|
|
190
|
+
) -> Optional[ResearchResult]:
|
|
191
|
+
"""Query a single source, routing by source type.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
A ResearchResult if the source returned data, else None.
|
|
195
|
+
"""
|
|
196
|
+
try:
|
|
197
|
+
if source.type == SourceType.MEMORY:
|
|
198
|
+
return await self._query_memory_source(source, query)
|
|
199
|
+
elif source.type == SourceType.KNOWLEDGE_GRAPH:
|
|
200
|
+
return await self._query_graph_source(source, query)
|
|
201
|
+
elif source.type == SourceType.WEB:
|
|
202
|
+
return await self._query_web_source(source, query)
|
|
203
|
+
elif source.type == SourceType.API:
|
|
204
|
+
return await self._query_api_source(source, query)
|
|
205
|
+
except Exception as exc:
|
|
206
|
+
logger.warning("Source %s query failed: %s", source.name, exc)
|
|
207
|
+
return None
|
|
208
|
+
|
|
209
|
+
async def _query_web_source(
|
|
210
|
+
self,
|
|
211
|
+
source: ResearchSource,
|
|
212
|
+
query: str,
|
|
213
|
+
) -> Optional[ResearchResult]:
|
|
214
|
+
"""Query the injected SearchOrchestrator for web results."""
|
|
215
|
+
if self.search is None or not getattr(self.search, "has_providers", False):
|
|
216
|
+
return None
|
|
217
|
+
try:
|
|
218
|
+
results = await self.search.search(query, max_results=5)
|
|
219
|
+
if not results:
|
|
220
|
+
return None
|
|
221
|
+
parts: List[str] = []
|
|
222
|
+
citations: List[str] = []
|
|
223
|
+
for r in results[:5]:
|
|
224
|
+
title = getattr(r, "title", "") or ""
|
|
225
|
+
snippet = getattr(r, "snippet", "") or ""
|
|
226
|
+
if title or snippet:
|
|
227
|
+
parts.append(f"{title}: {snippet}" if title else snippet)
|
|
228
|
+
url = getattr(r, "url", "")
|
|
229
|
+
if url:
|
|
230
|
+
citations.append(url)
|
|
231
|
+
content = "\n".join(parts).strip()
|
|
232
|
+
if not content:
|
|
233
|
+
return None
|
|
234
|
+
return ResearchResult(
|
|
235
|
+
source=source.name,
|
|
236
|
+
content=content,
|
|
237
|
+
confidence=min(0.8, source.priority * 0.8),
|
|
238
|
+
citations=citations,
|
|
239
|
+
)
|
|
240
|
+
except Exception as exc:
|
|
241
|
+
logger.debug("Web source query failed: %s", exc)
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
async def _query_api_source(
|
|
245
|
+
self,
|
|
246
|
+
source: ResearchSource,
|
|
247
|
+
query: str,
|
|
248
|
+
) -> Optional[ResearchResult]:
|
|
249
|
+
"""Dispatch to a registered API handler (if any) for this source."""
|
|
250
|
+
handler = self._api_handlers.get(source.name)
|
|
251
|
+
if handler is None:
|
|
252
|
+
return None
|
|
253
|
+
try:
|
|
254
|
+
return await handler(query)
|
|
255
|
+
except Exception as exc:
|
|
256
|
+
logger.debug("API source '%s' handler failed: %s", source.name, exc)
|
|
257
|
+
return None
|
|
258
|
+
|
|
259
|
+
async def _query_memory_source(
|
|
260
|
+
self,
|
|
261
|
+
source: ResearchSource,
|
|
262
|
+
query: str,
|
|
263
|
+
) -> Optional[ResearchResult]:
|
|
264
|
+
"""Query graph memory for relevant information."""
|
|
265
|
+
try:
|
|
266
|
+
memories = await self.graph.recall(query, limit=5)
|
|
267
|
+
if not memories:
|
|
268
|
+
return None
|
|
269
|
+
# Combine up to 3 most relevant memories into one result
|
|
270
|
+
content = "\n".join(
|
|
271
|
+
m.get("content", "") if isinstance(m, dict) else str(m)
|
|
272
|
+
for m in memories[:3]
|
|
273
|
+
if m
|
|
274
|
+
)
|
|
275
|
+
if not content.strip():
|
|
276
|
+
return None
|
|
277
|
+
return ResearchResult(
|
|
278
|
+
source=source.name,
|
|
279
|
+
content=content,
|
|
280
|
+
confidence=min(0.9, source.priority * 0.9),
|
|
281
|
+
)
|
|
282
|
+
except Exception as exc:
|
|
283
|
+
logger.debug("Memory source query failed: %s", exc)
|
|
284
|
+
return None
|
|
285
|
+
|
|
286
|
+
async def _query_graph_source(
|
|
287
|
+
self,
|
|
288
|
+
source: ResearchSource,
|
|
289
|
+
query: str,
|
|
290
|
+
) -> Optional[ResearchResult]:
|
|
291
|
+
"""Query the knowledge graph using entity and type recall.
|
|
292
|
+
|
|
293
|
+
Runs RECALL_BY_ENTITY (treating the query as an entity name) and
|
|
294
|
+
RECALL_BY_TYPE (treating the query as a memory type) against Neo4j,
|
|
295
|
+
then combines up to 3 results into a single ResearchResult.
|
|
296
|
+
"""
|
|
297
|
+
from apsimo.intelligence.graph.queries import RECALL_BY_ENTITY, RECALL_BY_TYPE
|
|
298
|
+
|
|
299
|
+
try:
|
|
300
|
+
memories: List[Any] = []
|
|
301
|
+
async with self.graph.driver.session(database=self.graph.database) as session:
|
|
302
|
+
entity_result = await session.run(
|
|
303
|
+
RECALL_BY_ENTITY,
|
|
304
|
+
entity_name=query,
|
|
305
|
+
min_strength=0.1,
|
|
306
|
+
limit=5,
|
|
307
|
+
)
|
|
308
|
+
memories.extend([record["memory"] async for record in entity_result])
|
|
309
|
+
|
|
310
|
+
type_result = await session.run(
|
|
311
|
+
RECALL_BY_TYPE,
|
|
312
|
+
memory_type=query,
|
|
313
|
+
min_strength=0.1,
|
|
314
|
+
limit=5,
|
|
315
|
+
)
|
|
316
|
+
memories.extend([record["memory"] async for record in type_result])
|
|
317
|
+
|
|
318
|
+
if not memories:
|
|
319
|
+
return None
|
|
320
|
+
|
|
321
|
+
content = "\n".join(
|
|
322
|
+
m.get("content", "") if isinstance(m, dict) else str(m)
|
|
323
|
+
for m in memories[:3]
|
|
324
|
+
if m
|
|
325
|
+
)
|
|
326
|
+
if not content.strip():
|
|
327
|
+
return None
|
|
328
|
+
|
|
329
|
+
return ResearchResult(
|
|
330
|
+
source=source.name,
|
|
331
|
+
content=content,
|
|
332
|
+
confidence=min(0.85, source.priority * 0.85),
|
|
333
|
+
)
|
|
334
|
+
except Exception as exc:
|
|
335
|
+
logger.debug("Graph source query failed: %s", exc)
|
|
336
|
+
return None
|
|
337
|
+
|
|
338
|
+
async def _synthesize(
|
|
339
|
+
self,
|
|
340
|
+
results: List[ResearchResult],
|
|
341
|
+
query: str,
|
|
342
|
+
) -> Optional[str]:
|
|
343
|
+
"""Synthesize results into a coherent summary.
|
|
344
|
+
|
|
345
|
+
Combines the top results (by confidence) into a single narrative,
|
|
346
|
+
prefixed with a summary header.
|
|
347
|
+
"""
|
|
348
|
+
if not results:
|
|
349
|
+
return None
|
|
350
|
+
|
|
351
|
+
sorted_results = sorted(results, key=lambda r: r.confidence, reverse=True)
|
|
352
|
+
parts = [f"Research summary for '{query[:60]}' — {len(results)} source(s):"]
|
|
353
|
+
for r in sorted_results[:3]:
|
|
354
|
+
if r.content:
|
|
355
|
+
excerpt = r.content[:200].replace("\n", " ")
|
|
356
|
+
parts.append(f"[{r.source}] {excerpt}")
|
|
357
|
+
|
|
358
|
+
return "\n".join(parts)
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""Self-directed thinking — Colony's internal cognition phase (v0.17.0).
|
|
2
|
+
|
|
3
|
+
The InitiativeEngine's generators are data-reactive: they scan existing
|
|
4
|
+
graph state for known shapes of work (stale goals, neglected contacts,
|
|
5
|
+
failing CI). The thinker is the complement: on a slow cadence it hands
|
|
6
|
+
an LLM a snapshot of Colony's situation — goals, commitments, recent
|
|
7
|
+
work, capability gaps, open questions — and asks what work SHOULD exist
|
|
8
|
+
that nothing has surfaced yet. Output becomes ordinary initiatives that
|
|
9
|
+
flow through the same store, delivery, and approval machinery as
|
|
10
|
+
everything else.
|
|
11
|
+
|
|
12
|
+
Safety posture:
|
|
13
|
+
|
|
14
|
+
- Disabled unless ``COLONY_ENABLE_INTERNAL_THINKING=true``.
|
|
15
|
+
- Runs at most once per ``COLONY_THINKING_INTERVAL_SECS`` (default 1h),
|
|
16
|
+
not every loop tick.
|
|
17
|
+
- At most ``COLONY_THINKING_MAX_INITIATIVES`` (default 3) per cycle.
|
|
18
|
+
- Proposals are capped at priority 0.85 and may never carry an
|
|
19
|
+
``action_hint`` — thought-up work always lands as review/decide
|
|
20
|
+
initiatives, never as directly executable agent actions. Anything
|
|
21
|
+
mutating or outbound therefore still crosses the action registry and
|
|
22
|
+
the owner-approval gate before an agent can touch it.
|
|
23
|
+
- Dedup keys remember recent proposals so repeated thinking cycles
|
|
24
|
+
don't re-pitch the same idea.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import json
|
|
30
|
+
import logging
|
|
31
|
+
import os
|
|
32
|
+
import re
|
|
33
|
+
import time
|
|
34
|
+
import uuid
|
|
35
|
+
from typing import Any, Dict, List, Optional
|
|
36
|
+
|
|
37
|
+
from apsimo.intelligence.components.initiative_engine import (
|
|
38
|
+
Initiative,
|
|
39
|
+
InitiativeType,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
logger = logging.getLogger(__name__)
|
|
43
|
+
|
|
44
|
+
# Types the LLM may propose. Deliberately excludes AGENT_ACTION (and
|
|
45
|
+
# anything else that auto-executes) — see module docstring.
|
|
46
|
+
_ALLOWED_TYPES = {
|
|
47
|
+
"research": InitiativeType.RESEARCH,
|
|
48
|
+
"task": InitiativeType.TASK,
|
|
49
|
+
"project": InitiativeType.PROJECT,
|
|
50
|
+
"system": InitiativeType.SYSTEM,
|
|
51
|
+
"knowledge_acquisition": InitiativeType.KNOWLEDGE_ACQUISITION,
|
|
52
|
+
"capability_gap": InitiativeType.CAPABILITY_GAP,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
_MAX_PRIORITY = 0.85
|
|
56
|
+
_RECENT_KEY_CAP = 200
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class SelfDirectedThinker:
|
|
60
|
+
"""Generates novel initiatives from periodic LLM reflection.
|
|
61
|
+
|
|
62
|
+
Prompted through the shared cognition charter (role "thinker"); the
|
|
63
|
+
output schema REQUIRES per-item confidence (trust-engine calibration
|
|
64
|
+
input) and grounding evidence (items without it are dropped).
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def __init__(self, router: Any,
|
|
68
|
+
interval_secs: Optional[int] = None,
|
|
69
|
+
max_per_cycle: Optional[int] = None,
|
|
70
|
+
self_brief_fn: Any = None,
|
|
71
|
+
boundaries_fn: Any = None) -> None:
|
|
72
|
+
self._router = router
|
|
73
|
+
self._interval = interval_secs if interval_secs is not None else int(
|
|
74
|
+
os.environ.get("COLONY_THINKING_INTERVAL_SECS", "3600"))
|
|
75
|
+
self._max = max_per_cycle if max_per_cycle is not None else int(
|
|
76
|
+
os.environ.get("COLONY_THINKING_MAX_INITIATIVES", "3"))
|
|
77
|
+
# Optional zero-arg callables -> str, evaluated fresh each cycle.
|
|
78
|
+
self._self_brief_fn = self_brief_fn
|
|
79
|
+
self._boundaries_fn = boundaries_fn
|
|
80
|
+
self._last_run: Optional[float] = None
|
|
81
|
+
self._recent_keys: List[str] = []
|
|
82
|
+
|
|
83
|
+
# -- scheduling ---------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
def due(self, now: Optional[float] = None) -> bool:
|
|
86
|
+
now = now if now is not None else time.monotonic()
|
|
87
|
+
if self._last_run is None:
|
|
88
|
+
return True
|
|
89
|
+
return (now - self._last_run) >= self._interval
|
|
90
|
+
|
|
91
|
+
def mark_ran(self, now: Optional[float] = None) -> None:
|
|
92
|
+
self._last_run = now if now is not None else time.monotonic()
|
|
93
|
+
|
|
94
|
+
# -- thinking -----------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
async def think(self, situation: Dict[str, Any]) -> List[Initiative]:
|
|
97
|
+
"""One reflection cycle: situation report in, initiatives out."""
|
|
98
|
+
if self._router is None:
|
|
99
|
+
return []
|
|
100
|
+
prompt = self._build_messages(situation)
|
|
101
|
+
try:
|
|
102
|
+
response = await self._router.complete(
|
|
103
|
+
prompt, context={"task": "internal_thinking"})
|
|
104
|
+
content = getattr(response, "content", "") or ""
|
|
105
|
+
except Exception as exc:
|
|
106
|
+
logger.warning("Self-directed thinking LLM call failed: %s", exc)
|
|
107
|
+
return []
|
|
108
|
+
|
|
109
|
+
initiatives: List[Initiative] = []
|
|
110
|
+
for item in self._parse(content)[: self._max]:
|
|
111
|
+
initiative = self._to_initiative(item)
|
|
112
|
+
if initiative is None:
|
|
113
|
+
continue
|
|
114
|
+
if initiative.dedup_key in self._recent_keys:
|
|
115
|
+
logger.debug("Thinking dedup: %s", initiative.dedup_key)
|
|
116
|
+
continue
|
|
117
|
+
self._remember(initiative.dedup_key)
|
|
118
|
+
initiatives.append(initiative)
|
|
119
|
+
if initiatives:
|
|
120
|
+
logger.info("Self-directed thinking produced %d initiative(s): %s",
|
|
121
|
+
len(initiatives),
|
|
122
|
+
"; ".join(i.description[:60] for i in initiatives))
|
|
123
|
+
return initiatives
|
|
124
|
+
|
|
125
|
+
# -- internals ----------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
def _build_messages(self, situation: Dict[str, Any]) -> List[dict]:
|
|
128
|
+
sections = []
|
|
129
|
+
for name, value in situation.items():
|
|
130
|
+
if value in (None, "", [], {}):
|
|
131
|
+
continue
|
|
132
|
+
try:
|
|
133
|
+
rendered = json.dumps(value, default=str, indent=None)[:4000]
|
|
134
|
+
except (TypeError, ValueError):
|
|
135
|
+
rendered = str(value)[:4000]
|
|
136
|
+
sections.append(f"## {name}\n{rendered}")
|
|
137
|
+
report = "\n\n".join(sections) or "(no situation data available)"
|
|
138
|
+
|
|
139
|
+
def _call(fn) -> str:
|
|
140
|
+
if fn is None:
|
|
141
|
+
return ""
|
|
142
|
+
try:
|
|
143
|
+
return str(fn() or "")
|
|
144
|
+
except Exception:
|
|
145
|
+
return ""
|
|
146
|
+
|
|
147
|
+
from apsimo.cognition.charter import build_system_prompt
|
|
148
|
+
system = build_system_prompt(
|
|
149
|
+
"thinker",
|
|
150
|
+
self_brief=_call(self._self_brief_fn) or None,
|
|
151
|
+
boundaries=_call(self._boundaries_fn) or None,
|
|
152
|
+
max_items=self._max, allowed=sorted(_ALLOWED_TYPES))
|
|
153
|
+
return [
|
|
154
|
+
{"role": "system", "content": system},
|
|
155
|
+
{"role": "user", "content": f"SITUATION REPORT\n\n{report}"},
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
@staticmethod
|
|
159
|
+
def _parse(content: str) -> List[dict]:
|
|
160
|
+
text = content.strip()
|
|
161
|
+
# Tolerate markdown fences and leading prose despite instructions.
|
|
162
|
+
if "```" in text:
|
|
163
|
+
match = re.search(r"```(?:json)?\s*(.*?)```", text, re.DOTALL)
|
|
164
|
+
if match:
|
|
165
|
+
text = match.group(1).strip()
|
|
166
|
+
if not text.startswith("["):
|
|
167
|
+
match = re.search(r"\[.*\]", text, re.DOTALL)
|
|
168
|
+
if not match:
|
|
169
|
+
return []
|
|
170
|
+
text = match.group(0)
|
|
171
|
+
try:
|
|
172
|
+
data = json.loads(text)
|
|
173
|
+
except json.JSONDecodeError:
|
|
174
|
+
logger.warning("Thinking output was not valid JSON; dropping")
|
|
175
|
+
return []
|
|
176
|
+
return [d for d in data if isinstance(d, dict)] if isinstance(data, list) else []
|
|
177
|
+
|
|
178
|
+
def _to_initiative(self, item: dict) -> Optional[Initiative]:
|
|
179
|
+
title = str(item.get("title", "")).strip()
|
|
180
|
+
type_key = str(item.get("type", "")).strip().lower()
|
|
181
|
+
rationale = str(item.get("rationale", "")).strip()
|
|
182
|
+
evidence = str(item.get("evidence", "")).strip()
|
|
183
|
+
if not title or type_key not in _ALLOWED_TYPES:
|
|
184
|
+
return None
|
|
185
|
+
# Charter contract: a proposal without grounding evidence does not
|
|
186
|
+
# ship (the schema demands the report lines that ground it).
|
|
187
|
+
if not evidence:
|
|
188
|
+
logger.debug("Thinking item dropped (no grounding evidence): %s",
|
|
189
|
+
title[:60])
|
|
190
|
+
return None
|
|
191
|
+
try:
|
|
192
|
+
priority = float(item.get("priority", 0.5))
|
|
193
|
+
except (TypeError, ValueError):
|
|
194
|
+
priority = 0.5
|
|
195
|
+
priority = max(0.0, min(_MAX_PRIORITY, priority))
|
|
196
|
+
try:
|
|
197
|
+
confidence = max(0.0, min(1.0, float(item.get("confidence", 0.5))))
|
|
198
|
+
except (TypeError, ValueError):
|
|
199
|
+
confidence = 0.5
|
|
200
|
+
slug = re.sub(r"[^a-z0-9]+", "-", title.lower()).strip("-")[:60]
|
|
201
|
+
return Initiative(
|
|
202
|
+
id=f"init-think-{uuid.uuid4().hex[:12]}",
|
|
203
|
+
type=_ALLOWED_TYPES[type_key],
|
|
204
|
+
description=title,
|
|
205
|
+
priority=priority,
|
|
206
|
+
rationale=f"[self-directed thinking] {rationale}" if rationale
|
|
207
|
+
else "[self-directed thinking]",
|
|
208
|
+
action_hint=None, # never directly executable (see docstring)
|
|
209
|
+
dedup_key=f"thinking:{slug}",
|
|
210
|
+
# Stated confidence + evidence ride along for trust-engine
|
|
211
|
+
# calibration (stated vs realized) and provenance.
|
|
212
|
+
trigger_data={"stated_confidence": confidence,
|
|
213
|
+
"evidence": evidence[:400]},
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
def _remember(self, key: Optional[str]) -> None:
|
|
217
|
+
if not key:
|
|
218
|
+
return
|
|
219
|
+
self._recent_keys.append(key)
|
|
220
|
+
if len(self._recent_keys) > _RECENT_KEY_CAP:
|
|
221
|
+
self._recent_keys = self._recent_keys[-_RECENT_KEY_CAP:]
|