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,513 @@
|
|
|
1
|
+
"""Research-to-Artifact Pipeline — 6-stage orchestrator.
|
|
2
|
+
|
|
3
|
+
Stages:
|
|
4
|
+
1. DECOMPOSE — parse goal into sub-tasks with GoalDecomposer
|
|
5
|
+
2. GATHER — collect evidence from web, graph, documents, email
|
|
6
|
+
3. SYNTHESIZE — cross-reference evidence into insights
|
|
7
|
+
4. OUTLINE — build structured artifact outline
|
|
8
|
+
5. PRODUCE — render artifact in requested format
|
|
9
|
+
6. REVIEW — quality and safety gate (ResponseGate)
|
|
10
|
+
|
|
11
|
+
Usage::
|
|
12
|
+
|
|
13
|
+
pipeline = ResearchPipeline()
|
|
14
|
+
run = await pipeline.run(
|
|
15
|
+
goal="Research competitor pricing",
|
|
16
|
+
format=ArtifactFormat.MARKDOWN,
|
|
17
|
+
)
|
|
18
|
+
print(run.artifact.content)
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import logging
|
|
24
|
+
import secrets
|
|
25
|
+
import time
|
|
26
|
+
from dataclasses import dataclass, field
|
|
27
|
+
from datetime import datetime, timezone
|
|
28
|
+
from enum import Enum
|
|
29
|
+
from typing import Any, Dict, List, Optional
|
|
30
|
+
|
|
31
|
+
from apsimo.research.gatherer import EvidencePackage, GatherConfig, SourceGatherer
|
|
32
|
+
from apsimo.research.synthesizer import ResearchSynthesizer, SynthesisConfig, SynthesisReport
|
|
33
|
+
from apsimo.research.artifact import (
|
|
34
|
+
Artifact,
|
|
35
|
+
ArtifactFormat,
|
|
36
|
+
ArtifactOutline,
|
|
37
|
+
ArtifactRenderer,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
# Pipeline state models
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class PipelineStage(str, Enum):
|
|
49
|
+
DECOMPOSE = "decompose"
|
|
50
|
+
GATHER = "gather"
|
|
51
|
+
SYNTHESIZE = "synthesize"
|
|
52
|
+
OUTLINE = "outline"
|
|
53
|
+
PRODUCE = "produce"
|
|
54
|
+
REVIEW = "review"
|
|
55
|
+
DONE = "done"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class PipelineStatus(str, Enum):
|
|
59
|
+
PENDING = "pending"
|
|
60
|
+
RUNNING = "running"
|
|
61
|
+
COMPLETED = "completed"
|
|
62
|
+
FAILED = "failed"
|
|
63
|
+
PAUSED = "paused" # awaiting outline approval
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class ReviewResult:
|
|
68
|
+
"""Outcome of the Stage 6 review gate."""
|
|
69
|
+
|
|
70
|
+
passed: bool
|
|
71
|
+
pii_clean: bool = True
|
|
72
|
+
injection_clean: bool = True
|
|
73
|
+
hallucination_flags: List[str] = field(default_factory=list)
|
|
74
|
+
rerender_required: bool = False
|
|
75
|
+
resynth_required: bool = False
|
|
76
|
+
gate_notes: str = ""
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
class PipelineRun:
|
|
81
|
+
"""Live state of a single pipeline execution."""
|
|
82
|
+
|
|
83
|
+
id: str
|
|
84
|
+
goal: str
|
|
85
|
+
format: ArtifactFormat
|
|
86
|
+
status: PipelineStatus
|
|
87
|
+
current_stage: PipelineStage
|
|
88
|
+
|
|
89
|
+
# Intermediate results
|
|
90
|
+
evidence: EvidencePackage = field(default_factory=list)
|
|
91
|
+
synthesis: Optional[SynthesisReport] = None
|
|
92
|
+
outline: Optional[ArtifactOutline] = None
|
|
93
|
+
artifact: Optional[Artifact] = None
|
|
94
|
+
review: Optional[ReviewResult] = None
|
|
95
|
+
|
|
96
|
+
# Timing
|
|
97
|
+
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
98
|
+
updated_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
99
|
+
completed_at: Optional[datetime] = None
|
|
100
|
+
|
|
101
|
+
# Stage durations (seconds)
|
|
102
|
+
stage_durations: Dict[str, float] = field(default_factory=dict)
|
|
103
|
+
|
|
104
|
+
# Error info
|
|
105
|
+
error: Optional[str] = None
|
|
106
|
+
|
|
107
|
+
# Config carried through
|
|
108
|
+
document_ids: List[str] = field(default_factory=list)
|
|
109
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
110
|
+
|
|
111
|
+
def touch(self, stage: Optional[PipelineStage] = None) -> None:
|
|
112
|
+
self.updated_at = datetime.now(timezone.utc)
|
|
113
|
+
if stage:
|
|
114
|
+
self.current_stage = stage
|
|
115
|
+
|
|
116
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
117
|
+
return {
|
|
118
|
+
"id": self.id,
|
|
119
|
+
"goal": self.goal,
|
|
120
|
+
"format": self.format.value,
|
|
121
|
+
"status": self.status.value,
|
|
122
|
+
"current_stage": self.current_stage.value,
|
|
123
|
+
"evidence_count": len(self.evidence),
|
|
124
|
+
"synthesis_confidence": (
|
|
125
|
+
self.synthesis.synthesis_confidence if self.synthesis else None
|
|
126
|
+
),
|
|
127
|
+
"insight_count": (
|
|
128
|
+
len(self.synthesis.insights) if self.synthesis else None
|
|
129
|
+
),
|
|
130
|
+
"artifact_id": self.artifact.id if self.artifact else None,
|
|
131
|
+
"artifact_word_count": self.artifact.word_count if self.artifact else None,
|
|
132
|
+
"review_passed": self.review.passed if self.review else None,
|
|
133
|
+
"created_at": self.created_at.isoformat(),
|
|
134
|
+
"updated_at": self.updated_at.isoformat(),
|
|
135
|
+
"completed_at": self.completed_at.isoformat() if self.completed_at else None,
|
|
136
|
+
"stage_durations": self.stage_durations,
|
|
137
|
+
"error": self.error,
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# ---------------------------------------------------------------------------
|
|
142
|
+
# Pipeline configuration
|
|
143
|
+
# ---------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@dataclass
|
|
147
|
+
class PipelineConfig:
|
|
148
|
+
"""Full pipeline configuration."""
|
|
149
|
+
|
|
150
|
+
gather: GatherConfig = field(default_factory=GatherConfig)
|
|
151
|
+
synthesis: SynthesisConfig = field(default_factory=SynthesisConfig)
|
|
152
|
+
|
|
153
|
+
require_outline_approval: bool = False
|
|
154
|
+
outline_approval_timeout_seconds: float = 300.0
|
|
155
|
+
|
|
156
|
+
# Review settings
|
|
157
|
+
review_l6_enabled: bool = True
|
|
158
|
+
review_hallucination_threshold: float = 0.70
|
|
159
|
+
review_pii_action: str = "redact" # "redact" | "block"
|
|
160
|
+
|
|
161
|
+
# Delivery
|
|
162
|
+
l7_cancel_window_seconds: float = 30.0
|
|
163
|
+
persist_to_graph: bool = True
|
|
164
|
+
|
|
165
|
+
# Stage timeouts (seconds)
|
|
166
|
+
timeout_decompose: float = 30.0
|
|
167
|
+
timeout_gather: float = 120.0
|
|
168
|
+
timeout_synthesize: float = 90.0
|
|
169
|
+
timeout_outline: float = 45.0
|
|
170
|
+
timeout_produce: float = 180.0
|
|
171
|
+
timeout_review: float = 60.0
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# ---------------------------------------------------------------------------
|
|
175
|
+
# In-memory run store (used when no persistent backend is available)
|
|
176
|
+
# ---------------------------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
_run_store: Dict[str, PipelineRun] = {}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# ---------------------------------------------------------------------------
|
|
182
|
+
# Pipeline
|
|
183
|
+
# ---------------------------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class ResearchPipeline:
|
|
187
|
+
"""6-stage Research-to-Artifact Pipeline.
|
|
188
|
+
|
|
189
|
+
Each stage is implemented as an async method. ``run()`` chains all
|
|
190
|
+
stages sequentially, recording stage durations and propagating errors.
|
|
191
|
+
|
|
192
|
+
The pipeline is designed to be resilient: failures in optional stages
|
|
193
|
+
(e.g. the review gate) are logged and skipped rather than aborting the
|
|
194
|
+
entire run.
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
def __init__(
|
|
198
|
+
self,
|
|
199
|
+
config: Optional[PipelineConfig] = None,
|
|
200
|
+
*,
|
|
201
|
+
graph: Any = None,
|
|
202
|
+
allow_fallback_graph: bool = True,
|
|
203
|
+
) -> None:
|
|
204
|
+
self.config = config or PipelineConfig()
|
|
205
|
+
self._gatherer = SourceGatherer(
|
|
206
|
+
self.config.gather,
|
|
207
|
+
graph=graph,
|
|
208
|
+
allow_fallback_graph=allow_fallback_graph,
|
|
209
|
+
)
|
|
210
|
+
self._synthesizer = ResearchSynthesizer(self.config.synthesis)
|
|
211
|
+
self._renderer = ArtifactRenderer()
|
|
212
|
+
|
|
213
|
+
# ------------------------------------------------------------------
|
|
214
|
+
# Public entry point
|
|
215
|
+
# ------------------------------------------------------------------
|
|
216
|
+
|
|
217
|
+
async def run(
|
|
218
|
+
self,
|
|
219
|
+
goal: str,
|
|
220
|
+
format: ArtifactFormat = ArtifactFormat.MARKDOWN,
|
|
221
|
+
document_ids: Optional[List[str]] = None,
|
|
222
|
+
run_id: Optional[str] = None,
|
|
223
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
224
|
+
) -> PipelineRun:
|
|
225
|
+
"""Execute all 6 pipeline stages and return the completed run.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
goal: Free-text research goal.
|
|
229
|
+
format: Desired output artifact format.
|
|
230
|
+
document_ids: Optional document IDs to ingest in Stage 2.
|
|
231
|
+
run_id: Optional pre-assigned run ID.
|
|
232
|
+
metadata: Optional metadata to attach to the run.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
A ``PipelineRun`` with ``status=COMPLETED`` (or FAILED on error).
|
|
236
|
+
"""
|
|
237
|
+
run = PipelineRun(
|
|
238
|
+
id=run_id or ("rap-" + secrets.token_hex(8)),
|
|
239
|
+
goal=goal,
|
|
240
|
+
format=format,
|
|
241
|
+
status=PipelineStatus.RUNNING,
|
|
242
|
+
current_stage=PipelineStage.DECOMPOSE,
|
|
243
|
+
document_ids=document_ids or [],
|
|
244
|
+
metadata=metadata or {},
|
|
245
|
+
)
|
|
246
|
+
_run_store[run.id] = run
|
|
247
|
+
logger.info("ResearchPipeline: starting run %s goal=%r format=%s", run.id, goal, format.value)
|
|
248
|
+
|
|
249
|
+
try:
|
|
250
|
+
# Stage 1: Decompose
|
|
251
|
+
await self._stage_decompose(run)
|
|
252
|
+
|
|
253
|
+
# Stage 2: Gather
|
|
254
|
+
await self._stage_gather(run)
|
|
255
|
+
|
|
256
|
+
# Stage 3: Synthesize
|
|
257
|
+
await self._stage_synthesize(run)
|
|
258
|
+
|
|
259
|
+
# Stage 4: Outline
|
|
260
|
+
await self._stage_outline(run)
|
|
261
|
+
|
|
262
|
+
# Stage 5: Produce
|
|
263
|
+
await self._stage_produce(run)
|
|
264
|
+
|
|
265
|
+
# Stage 6: Review
|
|
266
|
+
await self._stage_review(run)
|
|
267
|
+
|
|
268
|
+
run.status = PipelineStatus.COMPLETED
|
|
269
|
+
run.current_stage = PipelineStage.DONE
|
|
270
|
+
run.completed_at = datetime.now(timezone.utc)
|
|
271
|
+
logger.info(
|
|
272
|
+
"ResearchPipeline: run %s completed in %.1fs",
|
|
273
|
+
run.id,
|
|
274
|
+
sum(run.stage_durations.values()),
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
except Exception as exc:
|
|
278
|
+
run.status = PipelineStatus.FAILED
|
|
279
|
+
run.error = str(exc)
|
|
280
|
+
logger.error("ResearchPipeline: run %s failed at stage %s: %s", run.id, run.current_stage, exc)
|
|
281
|
+
|
|
282
|
+
run.touch()
|
|
283
|
+
return run
|
|
284
|
+
|
|
285
|
+
# ------------------------------------------------------------------
|
|
286
|
+
# Stage 1: Decompose
|
|
287
|
+
# ------------------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
async def _stage_decompose(self, run: PipelineRun) -> None:
|
|
290
|
+
"""Parse goal text and validate format/constraints."""
|
|
291
|
+
t0 = time.monotonic()
|
|
292
|
+
run.touch(PipelineStage.DECOMPOSE)
|
|
293
|
+
|
|
294
|
+
# Validation: goal must be non-empty
|
|
295
|
+
if not run.goal or not run.goal.strip():
|
|
296
|
+
raise ValueError("Research goal cannot be empty")
|
|
297
|
+
|
|
298
|
+
# Optionally use GoalDecomposer to validate / enrich the goal
|
|
299
|
+
try:
|
|
300
|
+
from apsimo.goals.decomposer import GoalDecomposer
|
|
301
|
+
decomposer = GoalDecomposer()
|
|
302
|
+
dag = decomposer.decompose_text(run.goal)
|
|
303
|
+
run.metadata["goal_dag_id"] = getattr(dag, "id", None)
|
|
304
|
+
except Exception as exc:
|
|
305
|
+
logger.debug("GoalDecomposer unavailable (%s), continuing without DAG", exc)
|
|
306
|
+
|
|
307
|
+
run.stage_durations[PipelineStage.DECOMPOSE.value] = time.monotonic() - t0
|
|
308
|
+
logger.debug("Stage DECOMPOSE: %.2fs", run.stage_durations[PipelineStage.DECOMPOSE.value])
|
|
309
|
+
|
|
310
|
+
# ------------------------------------------------------------------
|
|
311
|
+
# Stage 2: Gather
|
|
312
|
+
# ------------------------------------------------------------------
|
|
313
|
+
|
|
314
|
+
async def _stage_gather(self, run: PipelineRun) -> None:
|
|
315
|
+
"""Collect evidence from all enabled sources in parallel."""
|
|
316
|
+
t0 = time.monotonic()
|
|
317
|
+
run.touch(PipelineStage.GATHER)
|
|
318
|
+
|
|
319
|
+
evidence = await self._gatherer.gather(
|
|
320
|
+
query=run.goal,
|
|
321
|
+
document_ids=run.document_ids or None,
|
|
322
|
+
)
|
|
323
|
+
run.evidence = evidence
|
|
324
|
+
|
|
325
|
+
run.stage_durations[PipelineStage.GATHER.value] = time.monotonic() - t0
|
|
326
|
+
logger.debug(
|
|
327
|
+
"Stage GATHER: %d items in %.2fs",
|
|
328
|
+
len(evidence),
|
|
329
|
+
run.stage_durations[PipelineStage.GATHER.value],
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
# ------------------------------------------------------------------
|
|
333
|
+
# Stage 3: Synthesize
|
|
334
|
+
# ------------------------------------------------------------------
|
|
335
|
+
|
|
336
|
+
async def _stage_synthesize(self, run: PipelineRun) -> None:
|
|
337
|
+
"""Cross-reference evidence and extract insights."""
|
|
338
|
+
t0 = time.monotonic()
|
|
339
|
+
run.touch(PipelineStage.SYNTHESIZE)
|
|
340
|
+
|
|
341
|
+
synthesis = await self._synthesizer.synthesize(
|
|
342
|
+
evidence=run.evidence,
|
|
343
|
+
goal_id=run.id,
|
|
344
|
+
goal_text=run.goal,
|
|
345
|
+
)
|
|
346
|
+
run.synthesis = synthesis
|
|
347
|
+
|
|
348
|
+
run.stage_durations[PipelineStage.SYNTHESIZE.value] = time.monotonic() - t0
|
|
349
|
+
logger.debug(
|
|
350
|
+
"Stage SYNTHESIZE: %d insights, confidence=%.2f in %.2fs",
|
|
351
|
+
len(synthesis.insights),
|
|
352
|
+
synthesis.synthesis_confidence,
|
|
353
|
+
run.stage_durations[PipelineStage.SYNTHESIZE.value],
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
# ------------------------------------------------------------------
|
|
357
|
+
# Stage 4: Outline
|
|
358
|
+
# ------------------------------------------------------------------
|
|
359
|
+
|
|
360
|
+
async def _stage_outline(self, run: PipelineRun) -> None:
|
|
361
|
+
"""Build the structured artifact outline."""
|
|
362
|
+
t0 = time.monotonic()
|
|
363
|
+
run.touch(PipelineStage.OUTLINE)
|
|
364
|
+
|
|
365
|
+
assert run.synthesis is not None
|
|
366
|
+
outline = self._renderer.build_outline(
|
|
367
|
+
synthesis=run.synthesis,
|
|
368
|
+
goal_text=run.goal,
|
|
369
|
+
format=run.format,
|
|
370
|
+
)
|
|
371
|
+
run.outline = outline
|
|
372
|
+
|
|
373
|
+
run.stage_durations[PipelineStage.OUTLINE.value] = time.monotonic() - t0
|
|
374
|
+
logger.debug(
|
|
375
|
+
"Stage OUTLINE: %d sections in %.2fs",
|
|
376
|
+
len(outline.sections),
|
|
377
|
+
run.stage_durations[PipelineStage.OUTLINE.value],
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
# ------------------------------------------------------------------
|
|
381
|
+
# Stage 5: Produce
|
|
382
|
+
# ------------------------------------------------------------------
|
|
383
|
+
|
|
384
|
+
async def _stage_produce(self, run: PipelineRun) -> None:
|
|
385
|
+
"""Render the artifact in the requested format."""
|
|
386
|
+
t0 = time.monotonic()
|
|
387
|
+
run.touch(PipelineStage.PRODUCE)
|
|
388
|
+
|
|
389
|
+
assert run.outline is not None
|
|
390
|
+
assert run.synthesis is not None
|
|
391
|
+
artifact = self._renderer.render(
|
|
392
|
+
outline=run.outline,
|
|
393
|
+
synthesis=run.synthesis,
|
|
394
|
+
goal_text=run.goal,
|
|
395
|
+
)
|
|
396
|
+
run.artifact = artifact
|
|
397
|
+
|
|
398
|
+
run.stage_durations[PipelineStage.PRODUCE.value] = time.monotonic() - t0
|
|
399
|
+
logger.debug(
|
|
400
|
+
"Stage PRODUCE: format=%s words=%d in %.2fs",
|
|
401
|
+
artifact.format.value,
|
|
402
|
+
artifact.word_count,
|
|
403
|
+
run.stage_durations[PipelineStage.PRODUCE.value],
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
# ------------------------------------------------------------------
|
|
407
|
+
# Stage 6: Review
|
|
408
|
+
# ------------------------------------------------------------------
|
|
409
|
+
|
|
410
|
+
async def _stage_review(self, run: PipelineRun) -> None:
|
|
411
|
+
"""Run artifact through the response gate."""
|
|
412
|
+
t0 = time.monotonic()
|
|
413
|
+
run.touch(PipelineStage.REVIEW)
|
|
414
|
+
|
|
415
|
+
assert run.artifact is not None
|
|
416
|
+
review = await self._run_review_gate(run.artifact, run)
|
|
417
|
+
run.review = review
|
|
418
|
+
|
|
419
|
+
if not review.passed:
|
|
420
|
+
if review.resynth_required:
|
|
421
|
+
# Re-synthesize flagged sections (simplified: log and continue)
|
|
422
|
+
logger.warning("Review: resynth required — skipping in this run")
|
|
423
|
+
elif not review.rerender_required:
|
|
424
|
+
raise RuntimeError(
|
|
425
|
+
f"Artifact failed review gate: {review.gate_notes}"
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
run.stage_durations[PipelineStage.REVIEW.value] = time.monotonic() - t0
|
|
429
|
+
logger.debug(
|
|
430
|
+
"Stage REVIEW: passed=%s in %.2fs",
|
|
431
|
+
review.passed,
|
|
432
|
+
run.stage_durations[PipelineStage.REVIEW.value],
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
async def _run_review_gate(
|
|
436
|
+
self,
|
|
437
|
+
artifact: Artifact,
|
|
438
|
+
run: PipelineRun,
|
|
439
|
+
) -> ReviewResult:
|
|
440
|
+
"""Content-safety review of a research artifact.
|
|
441
|
+
|
|
442
|
+
A research artifact has no recipient/trust-tier, so the full
|
|
443
|
+
ResponseGate (recipient verification, cross-context, trust tiering)
|
|
444
|
+
does not apply — but its PII scanner (L2) and injection detector (L5)
|
|
445
|
+
do. Run those two layers directly. (The previous code constructed
|
|
446
|
+
ResponseGate() with the wrong arguments, threw on every call, and
|
|
447
|
+
silently degraded to a 4-string substring scan.)"""
|
|
448
|
+
try:
|
|
449
|
+
from apsimo.gate.layers.l2_pii import PIIScanner
|
|
450
|
+
from apsimo.gate.layers.l5_injection import InjectionDetector
|
|
451
|
+
from apsimo.gate.config import GateConfig
|
|
452
|
+
from apsimo.gate.models import GatePayload, TrustTier
|
|
453
|
+
|
|
454
|
+
cfg = GateConfig()
|
|
455
|
+
payload = GatePayload(
|
|
456
|
+
response_text=artifact.content,
|
|
457
|
+
target_contact_id="", target_gateway="",
|
|
458
|
+
session_id=str(run.metadata.get("session_id", "research")),
|
|
459
|
+
trust_tier=TrustTier.PERIPHERAL,
|
|
460
|
+
mentioned_entities=frozenset(),
|
|
461
|
+
turn_id=str(getattr(run, "run_id", "") or "research"),
|
|
462
|
+
incoming_message_text="",
|
|
463
|
+
)
|
|
464
|
+
pii = await PIIScanner(cfg).check(payload)
|
|
465
|
+
inj = await InjectionDetector(cfg).check(payload)
|
|
466
|
+
pii_clean = not pii.blocked
|
|
467
|
+
injection_clean = not inj.blocked
|
|
468
|
+
notes = []
|
|
469
|
+
if not pii_clean:
|
|
470
|
+
notes.append(f"pii:{pii.code}")
|
|
471
|
+
if not injection_clean:
|
|
472
|
+
notes.append(f"injection:{inj.reason or inj.code}")
|
|
473
|
+
return ReviewResult(
|
|
474
|
+
passed=pii_clean and injection_clean,
|
|
475
|
+
pii_clean=pii_clean,
|
|
476
|
+
injection_clean=injection_clean,
|
|
477
|
+
gate_notes="; ".join(notes) or "clean",
|
|
478
|
+
)
|
|
479
|
+
except Exception as exc:
|
|
480
|
+
logger.warning("research review gate failed (%s); "
|
|
481
|
+
"falling back to injection-marker scan", exc)
|
|
482
|
+
|
|
483
|
+
# Fallback only when the gate layers are genuinely unavailable.
|
|
484
|
+
content = artifact.content
|
|
485
|
+
injection_markers = ["<script", "javascript:", "data:text/html", "eval("]
|
|
486
|
+
injection_found = any(m in content.lower() for m in injection_markers)
|
|
487
|
+
return ReviewResult(
|
|
488
|
+
passed=not injection_found,
|
|
489
|
+
pii_clean=True,
|
|
490
|
+
injection_clean=not injection_found,
|
|
491
|
+
gate_notes="basic-review" if not injection_found else "injection-detected",
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
# ------------------------------------------------------------------
|
|
495
|
+
# Run management helpers
|
|
496
|
+
# ------------------------------------------------------------------
|
|
497
|
+
|
|
498
|
+
def get_run(self, run_id: str) -> Optional[PipelineRun]:
|
|
499
|
+
"""Retrieve a run by ID from the in-memory store."""
|
|
500
|
+
return _run_store.get(run_id)
|
|
501
|
+
|
|
502
|
+
def list_runs(
|
|
503
|
+
self,
|
|
504
|
+
status: Optional[str] = None,
|
|
505
|
+
limit: int = 50,
|
|
506
|
+
offset: int = 0,
|
|
507
|
+
) -> List[PipelineRun]:
|
|
508
|
+
"""List runs from the in-memory store with optional status filter."""
|
|
509
|
+
runs = list(_run_store.values())
|
|
510
|
+
if status:
|
|
511
|
+
runs = [r for r in runs if r.status.value == status]
|
|
512
|
+
runs.sort(key=lambda r: r.created_at, reverse=True)
|
|
513
|
+
return runs[offset: offset + limit]
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Colony web search integration."""
|
|
2
|
+
|
|
3
|
+
from apsimo.research.search.base import SearchProvider, SearchResult
|
|
4
|
+
from apsimo.research.search.orchestrator import SearchOrchestrator
|
|
5
|
+
from apsimo.research.search.cache import SearchCache
|
|
6
|
+
|
|
7
|
+
__all__ = ["SearchProvider", "SearchResult", "SearchOrchestrator", "SearchCache"]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Base classes for search providers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class SearchResult:
|
|
13
|
+
"""A single search result."""
|
|
14
|
+
|
|
15
|
+
title: str
|
|
16
|
+
url: str
|
|
17
|
+
snippet: str
|
|
18
|
+
content: Optional[str] = None # Full page content if available
|
|
19
|
+
source: str = ""
|
|
20
|
+
rank: int = 0
|
|
21
|
+
retrieved_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SearchProvider(ABC):
|
|
25
|
+
"""Base class for search API providers."""
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
async def search(self, query: str, max_results: int = 5) -> list[SearchResult]:
|
|
29
|
+
"""Execute a search query and return results."""
|
|
30
|
+
...
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def name(self) -> str:
|
|
35
|
+
"""Provider identifier (e.g., 'tavily', 'serpapi')."""
|
|
36
|
+
...
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def rate_limit_per_minute(self) -> int:
|
|
40
|
+
"""Maximum requests per minute."""
|
|
41
|
+
return 30
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Brave Search API provider."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from apsimo.research.search.base import SearchProvider, SearchResult
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
import httpx
|
|
14
|
+
HAS_HTTPX = True
|
|
15
|
+
except ImportError:
|
|
16
|
+
HAS_HTTPX = False
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BraveSearchProvider(SearchProvider):
|
|
20
|
+
"""Brave Search API — privacy-focused search."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, api_key: str):
|
|
23
|
+
self._api_key = api_key
|
|
24
|
+
self._base_url = "https://api.search.brave.com/res/v1/web/search"
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def name(self) -> str:
|
|
28
|
+
return "brave"
|
|
29
|
+
|
|
30
|
+
async def search(self, query: str, max_results: int = 5) -> list[SearchResult]:
|
|
31
|
+
if not HAS_HTTPX:
|
|
32
|
+
logger.warning("httpx not installed — cannot use Brave Search")
|
|
33
|
+
return []
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
async with httpx.AsyncClient(timeout=15) as client:
|
|
37
|
+
resp = await client.get(self._base_url, params={
|
|
38
|
+
"q": query,
|
|
39
|
+
"count": max_results,
|
|
40
|
+
}, headers={
|
|
41
|
+
"X-Subscription-Token": self._api_key,
|
|
42
|
+
"Accept": "application/json",
|
|
43
|
+
})
|
|
44
|
+
resp.raise_for_status()
|
|
45
|
+
data = resp.json()
|
|
46
|
+
results = []
|
|
47
|
+
for i, r in enumerate(data.get("web", {}).get("results", [])[:max_results]):
|
|
48
|
+
results.append(SearchResult(
|
|
49
|
+
title=r.get("title", ""),
|
|
50
|
+
url=r.get("url", ""),
|
|
51
|
+
snippet=r.get("description", ""),
|
|
52
|
+
content=None,
|
|
53
|
+
source="brave",
|
|
54
|
+
rank=i,
|
|
55
|
+
))
|
|
56
|
+
return results
|
|
57
|
+
except Exception as e:
|
|
58
|
+
logger.warning("Brave Search failed: %s", e)
|
|
59
|
+
return []
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Search result cache to avoid duplicate API calls."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import logging
|
|
7
|
+
import time
|
|
8
|
+
from typing import Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
from apsimo.research.search.base import SearchResult
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SearchCache:
|
|
16
|
+
"""In-memory search result cache with TTL."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, ttl_seconds: int = 3600, max_entries: int = 500):
|
|
19
|
+
self._ttl = ttl_seconds
|
|
20
|
+
self._max_entries = max_entries
|
|
21
|
+
self._cache: Dict[str, tuple[float, List[SearchResult]]] = {}
|
|
22
|
+
|
|
23
|
+
def _key(self, query: str) -> str:
|
|
24
|
+
return hashlib.sha256(query.lower().strip().encode()).hexdigest()
|
|
25
|
+
|
|
26
|
+
def get(self, query: str) -> Optional[List[SearchResult]]:
|
|
27
|
+
key = self._key(query)
|
|
28
|
+
entry = self._cache.get(key)
|
|
29
|
+
if entry is None:
|
|
30
|
+
return None
|
|
31
|
+
timestamp, results = entry
|
|
32
|
+
if time.time() - timestamp > self._ttl:
|
|
33
|
+
del self._cache[key]
|
|
34
|
+
return None
|
|
35
|
+
return results
|
|
36
|
+
|
|
37
|
+
def put(self, query: str, results: List[SearchResult]) -> None:
|
|
38
|
+
# Evict oldest entries if at capacity
|
|
39
|
+
if len(self._cache) >= self._max_entries:
|
|
40
|
+
oldest_key = min(self._cache, key=lambda k: self._cache[k][0])
|
|
41
|
+
del self._cache[oldest_key]
|
|
42
|
+
|
|
43
|
+
key = self._key(query)
|
|
44
|
+
self._cache[key] = (time.time(), results)
|
|
45
|
+
|
|
46
|
+
def clear(self) -> None:
|
|
47
|
+
self._cache.clear()
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def size(self) -> int:
|
|
51
|
+
return len(self._cache)
|