apsimo 1.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- apsimo/__init__.py +38 -0
- apsimo/__main__.py +6 -0
- apsimo/agent/__init__.py +6 -0
- apsimo/agent/client.py +276 -0
- apsimo/agent/models.py +46 -0
- apsimo/agents/__init__.py +20 -0
- apsimo/agents/models.py +264 -0
- apsimo/agents/store.py +861 -0
- apsimo/agents/websocket.py +522 -0
- apsimo/api/__init__.py +1 -0
- apsimo/api/auth_telemetry.py +287 -0
- apsimo/api/authority.py +1203 -0
- apsimo/api/contact_grants.py +347 -0
- apsimo/api/middleware.py +483 -0
- apsimo/api/routers/__init__.py +1 -0
- apsimo/api/routers/commitment_work.py +265 -0
- apsimo/api/routers/context_gate.py +123 -0
- apsimo/api/routers/executions.py +140 -0
- apsimo/api/routers/followup_plans.py +147 -0
- apsimo/api/routers/governed_actions.py +162 -0
- apsimo/api/routers/host.py +14473 -0
- apsimo/api/routers/initiative_work.py +115 -0
- apsimo/api/routers/mining.py +104 -0
- apsimo/api/routers/observations.py +110 -0
- apsimo/api/routers/social_state.py +225 -0
- apsimo/api/routers/task_queue.py +2715 -0
- apsimo/api/routers/temporal_followups.py +251 -0
- apsimo/api/routers/transport.py +110 -0
- apsimo/api/routers/transport_ingress_api.py +240 -0
- apsimo/api/schemas/__init__.py +1 -0
- apsimo/api/schemas/host.py +1949 -0
- apsimo/autonomy/cli.py +110 -0
- apsimo/autonomy/condition_worker.py +437 -0
- apsimo/autonomy/config.py +424 -0
- apsimo/autonomy/loop.py +4316 -0
- apsimo/autonomy/registry.py +339 -0
- apsimo/autonomy/scheduler.py +1822 -0
- apsimo/autonomy/synthesis.py +449 -0
- apsimo/backup.py +962 -0
- apsimo/beliefs/__init__.py +23 -0
- apsimo/beliefs/contradictions.py +109 -0
- apsimo/beliefs/decay.py +61 -0
- apsimo/beliefs/engine.py +479 -0
- apsimo/beliefs/models.py +67 -0
- apsimo/beliefs/promotion.py +41 -0
- apsimo/beliefs/resolve.py +58 -0
- apsimo/beliefs/source_claims.py +690 -0
- apsimo/beliefs/source_projection.py +883 -0
- apsimo/beliefs/source_time.py +208 -0
- apsimo/beliefs/store.py +133 -0
- apsimo/briefings/aggregators.py +824 -0
- apsimo/briefings/composer.py +420 -0
- apsimo/briefings/config.py +55 -0
- apsimo/briefings/delivery.py +439 -0
- apsimo/briefings/engagement.py +97 -0
- apsimo/briefings/engine.py +274 -0
- apsimo/briefings/enhancer.py +99 -0
- apsimo/briefings/models.py +183 -0
- apsimo/briefings/scheduler.py +382 -0
- apsimo/briefings/store.py +435 -0
- apsimo/chain/__init__.py +48 -0
- apsimo/chain/block.py +100 -0
- apsimo/chain/cli.py +704 -0
- apsimo/chain/genesis.py +443 -0
- apsimo/chain/identity.py +416 -0
- apsimo/chain/keys.py +1025 -0
- apsimo/chain/local_keys.py +187 -0
- apsimo/chain/manager.py +290 -0
- apsimo/chain/node.py +163 -0
- apsimo/chain/plugin_transactions.py +371 -0
- apsimo/chain/protocol.py +220 -0
- apsimo/chain/state_machine.py +676 -0
- apsimo/chain/storage.py +503 -0
- apsimo/chain/transactions.py +250 -0
- apsimo/chain/validation.py +397 -0
- apsimo/channels/__init__.py +1 -0
- apsimo/channels/manifest.py +31 -0
- apsimo/channels/migrations/001_channels_schema.sql +12 -0
- apsimo/channels/phone_gateways.py +42 -0
- apsimo/channels/presence.py +188 -0
- apsimo/channels/router.py +235 -0
- apsimo/channels/store.py +231 -0
- apsimo/cli.py +2688 -0
- apsimo/cognition/__init__.py +11 -0
- apsimo/cognition/charter.py +398 -0
- apsimo/cognition/drive_governance.py +3530 -0
- apsimo/cognition/evidence_pipeline.py +1627 -0
- apsimo/cognition/external_events.py +932 -0
- apsimo/cognition/goal_spine.py +3488 -0
- apsimo/cognition/introspection.py +214 -0
- apsimo/cognition/prompt.py +150 -0
- apsimo/cognition/runtime.py +108 -0
- apsimo/cognition/trigger.py +154 -0
- apsimo/commitments/__init__.py +18 -0
- apsimo/commitments/local_work.py +355 -0
- apsimo/commitments/store.py +1052 -0
- apsimo/commitments/work.py +91 -0
- apsimo/compat.py +53 -0
- apsimo/compression/__init__.py +467 -0
- apsimo/connectors/__init__.py +21 -0
- apsimo/connectors/base.py +152 -0
- apsimo/connectors/caldav_calendar.py +125 -0
- apsimo/connectors/fs_documents.py +85 -0
- apsimo/connectors/imap_email.py +138 -0
- apsimo/connectors/manager.py +218 -0
- apsimo/connectors/webhook_pull.py +88 -0
- apsimo/contacts/__init__.py +33 -0
- apsimo/contacts/comms.py +357 -0
- apsimo/contacts/config.py +79 -0
- apsimo/contacts/exporters/__init__.py +1 -0
- apsimo/contacts/exporters/vcard.py +71 -0
- apsimo/contacts/identity_links.py +251 -0
- apsimo/contacts/importer.py +280 -0
- apsimo/contacts/importers/__init__.py +1 -0
- apsimo/contacts/importers/batch.py +43 -0
- apsimo/contacts/importers/macos_contacts.py +101 -0
- apsimo/contacts/migrations/001_contacts_schema.sql +141 -0
- apsimo/contacts/migrations/002_trust_scopes.sql +36 -0
- apsimo/contacts/migrations/003_open_gateway_enum.sql +32 -0
- apsimo/contacts/migrations/004_contact_provision_operations.sql +18 -0
- apsimo/contacts/migrations/005_identity_links.sql +27 -0
- apsimo/contacts/models.py +308 -0
- apsimo/contacts/scoring.py +16 -0
- apsimo/contacts/store.py +1623 -0
- apsimo/contacts/transport_ingress.py +252 -0
- apsimo/contacts/world_bridge.py +314 -0
- apsimo/contextgate/__init__.py +69 -0
- apsimo/contextgate/chunker.py +169 -0
- apsimo/contextgate/estimate.py +54 -0
- apsimo/contextgate/gate.py +313 -0
- apsimo/contextgate/retrieve.py +115 -0
- apsimo/delivery/__init__.py +16 -0
- apsimo/delivery/bridge.py +1260 -0
- apsimo/delivery/channels.py +526 -0
- apsimo/delivery/classification.py +50 -0
- apsimo/delivery/rate_limiter.py +268 -0
- apsimo/delivery/reachout_policy.py +206 -0
- apsimo/directed/__init__.py +22 -0
- apsimo/directed/audit.py +167 -0
- apsimo/directed/intake.py +95 -0
- apsimo/directed/models.py +191 -0
- apsimo/directed/service.py +509 -0
- apsimo/directives/__init__.py +25 -0
- apsimo/directives/evidence.py +87 -0
- apsimo/directives/extractor.py +188 -0
- apsimo/directives/guard.py +364 -0
- apsimo/directives/models.py +206 -0
- apsimo/directives/service.py +372 -0
- apsimo/directives/store.py +167 -0
- apsimo/doctor.py +2173 -0
- apsimo/environment.py +43 -0
- apsimo/events/__init__.py +33 -0
- apsimo/events/broadcaster.py +98 -0
- apsimo/events/bus.py +217 -0
- apsimo/events/journal.py +863 -0
- apsimo/events/stream.py +131 -0
- apsimo/events/types.py +150 -0
- apsimo/execution_results.py +357 -0
- apsimo/feedback/__init__.py +5 -0
- apsimo/feedback/store.py +76 -0
- apsimo/feeds/__init__.py +19 -0
- apsimo/feeds/cli.py +84 -0
- apsimo/feeds/engine.py +437 -0
- apsimo/feeds/example-feed.yaml +77 -0
- apsimo/feeds/hermes_cron.py +126 -0
- apsimo/feeds/manager.py +235 -0
- apsimo/feeds/spec.py +250 -0
- apsimo/feeds/template.py +202 -0
- apsimo/gate/__init__.py +18 -0
- apsimo/gate/audit.py +61 -0
- apsimo/gate/communication_policy.py +166 -0
- apsimo/gate/config.py +72 -0
- apsimo/gate/context_provenance.py +170 -0
- apsimo/gate/env_risk.py +226 -0
- apsimo/gate/guard_audit.py +353 -0
- apsimo/gate/layers/__init__.py +1 -0
- apsimo/gate/layers/base.py +15 -0
- apsimo/gate/layers/l1_recipient.py +66 -0
- apsimo/gate/layers/l2_pii.py +134 -0
- apsimo/gate/layers/l3_cross_context.py +50 -0
- apsimo/gate/layers/l4_trust_tier.py +78 -0
- apsimo/gate/layers/l5_injection.py +199 -0
- apsimo/gate/layers/l6_review.py +86 -0
- apsimo/gate/layers/l7_delay.py +100 -0
- apsimo/gate/layers/tom2_epistemic.py +185 -0
- apsimo/gate/models.py +64 -0
- apsimo/gate/pending_dispatch.py +5 -0
- apsimo/gate/pipeline.py +206 -0
- apsimo/gate/rejection.py +259 -0
- apsimo/gate/response_guard.py +700 -0
- apsimo/gate/rulesets/injection_v1.yaml +51 -0
- apsimo/gate/surface_policy.py +189 -0
- apsimo/gate/taint.py +226 -0
- apsimo/genesis.json +9 -0
- apsimo/goals/__init__.py +100 -0
- apsimo/goals/config.py +38 -0
- apsimo/goals/decomposer.py +421 -0
- apsimo/goals/engine.py +617 -0
- apsimo/goals/inference.py +354 -0
- apsimo/goals/models.py +302 -0
- apsimo/goals/priority.py +270 -0
- apsimo/goals/queue_bridge.py +149 -0
- apsimo/goals/replan.py +450 -0
- apsimo/goals/schema.sql +89 -0
- apsimo/goals/store.py +692 -0
- apsimo/governed_actions.py +1708 -0
- apsimo/harness_integration/__init__.py +45 -0
- apsimo/harness_integration/context.py +41 -0
- apsimo/harness_integration/skills.py +231 -0
- apsimo/identity/__init__.py +26 -0
- apsimo/identity/participants.py +181 -0
- apsimo/identity/resolver.py +329 -0
- apsimo/identity_bootstrap/__init__.py +5 -0
- apsimo/identity_bootstrap/builder.py +208 -0
- apsimo/identity_bootstrap/corpus.py +443 -0
- apsimo/identity_bootstrap/models.py +54 -0
- apsimo/identity_bootstrap/runner.py +353 -0
- apsimo/identity_bootstrap/seeders/__init__.py +25 -0
- apsimo/identity_bootstrap/seeders/briefings.py +109 -0
- apsimo/identity_bootstrap/seeders/chain.py +57 -0
- apsimo/identity_bootstrap/seeders/goals.py +128 -0
- apsimo/identity_bootstrap/seeders/memory.py +191 -0
- apsimo/identity_bootstrap/seeders/neo4j_cognition.py +79 -0
- apsimo/identity_bootstrap/seeders/relationship.py +152 -0
- apsimo/identity_bootstrap/seeders/sessions.py +67 -0
- apsimo/identity_bootstrap/seeders/skills.py +92 -0
- apsimo/identity_bootstrap/seeders/task_queue.py +72 -0
- apsimo/identity_bootstrap/seeders/world_model.py +143 -0
- apsimo/identity_bootstrap/self_query.py +92 -0
- apsimo/identity_bootstrap/self_reflection.py +155 -0
- apsimo/identity_bootstrap/skill.py +37 -0
- apsimo/identity_bootstrap/verifier.py +436 -0
- apsimo/initiatives/__init__.py +20 -0
- apsimo/initiatives/action_registry.py +454 -0
- apsimo/initiatives/approval_authority.py +2105 -0
- apsimo/initiatives/approval_policy.py +123 -0
- apsimo/initiatives/assignment.py +263 -0
- apsimo/initiatives/backup_evidence.py +100 -0
- apsimo/initiatives/context_freshness.py +103 -0
- apsimo/initiatives/models.py +318 -0
- apsimo/initiatives/native_work.py +270 -0
- apsimo/initiatives/standing_approvals.py +232 -0
- apsimo/initiatives/store.py +1081 -0
- apsimo/initiatives/temporal_followup.py +410 -0
- apsimo/intelligence/__init__.py +1 -0
- apsimo/intelligence/cognition/__init__.py +24 -0
- apsimo/intelligence/cognition/gap_detector.py +148 -0
- apsimo/intelligence/cognition/metalearner.py +547 -0
- apsimo/intelligence/cognition/metrics_collector.py +217 -0
- apsimo/intelligence/cognition/performance_index.py +299 -0
- apsimo/intelligence/cognition/registry.py +192 -0
- apsimo/intelligence/cognition/strategy_adjuster.py +222 -0
- apsimo/intelligence/cognition/types.py +16 -0
- apsimo/intelligence/components/__init__.py +66 -0
- apsimo/intelligence/components/anomaly_detector.py +413 -0
- apsimo/intelligence/components/initiative_engine.py +2643 -0
- apsimo/intelligence/components/preference_learner.py +521 -0
- apsimo/intelligence/components/research_orchestrator.py +358 -0
- apsimo/intelligence/components/self_directed_thinker.py +221 -0
- apsimo/intelligence/components/self_reflector.py +252 -0
- apsimo/intelligence/components/session_continuity.py +154 -0
- apsimo/intelligence/components/task_planner.py +320 -0
- apsimo/intelligence/components/tool_learner.py +217 -0
- apsimo/intelligence/graph/__init__.py +79 -0
- apsimo/intelligence/graph/client.py +2483 -0
- apsimo/intelligence/graph/consolidator.py +405 -0
- apsimo/intelligence/graph/distiller.py +312 -0
- apsimo/intelligence/graph/migrations.py +129 -0
- apsimo/intelligence/graph/queries.py +248 -0
- apsimo/intelligence/graph/recall.py +281 -0
- apsimo/intelligence/graph/reconciler.py +144 -0
- apsimo/intelligence/graph/schema.py +337 -0
- apsimo/intelligence/graph/selection.py +252 -0
- apsimo/intelligence/learning/__init__.py +17 -0
- apsimo/intelligence/learning/continuous_learner.py +245 -0
- apsimo/intelligence/learning/feedback_store.py +321 -0
- apsimo/intelligence/mind_model/__init__.py +1 -0
- apsimo/intelligence/mind_model/graph_baseline.py +136 -0
- apsimo/intelligence/mind_model/signal_collector.py +361 -0
- apsimo/intelligence/relationships/__init__.py +11 -0
- apsimo/intelligence/relationships/profiler.py +389 -0
- apsimo/intelligence/relationships/scorer.py +560 -0
- apsimo/intelligence/relationships/signal_floor.py +66 -0
- apsimo/intelligence/relationships/trust_tiers.py +300 -0
- apsimo/intelligence/synthesis/__init__.py +40 -0
- apsimo/intelligence/synthesis/connection_discoverer.py +379 -0
- apsimo/intelligence/synthesis/cross_domain_analyzer.py +287 -0
- apsimo/intelligence/synthesis/insight_deliverer.py +171 -0
- apsimo/intelligence/synthesis/insight_store.py +79 -0
- apsimo/intelligence/synthesis/insight_validator.py +183 -0
- apsimo/intelligence/synthesis/novelty_scorer.py +267 -0
- apsimo/intelligence/turn_middleware/__init__.py +15 -0
- apsimo/intelligence/turn_middleware/memory_sync.py +119 -0
- apsimo/mcp/__init__.py +41 -0
- apsimo/mcp/__main__.py +6 -0
- apsimo/mcp/config.py +287 -0
- apsimo/mcp/server.py +501 -0
- apsimo/migrations.py +187 -0
- apsimo/mining/__init__.py +27 -0
- apsimo/mining/corpus.py +239 -0
- apsimo/mining/escalations.py +289 -0
- apsimo/mining/models.py +169 -0
- apsimo/mining/store.py +210 -0
- apsimo/models/__init__.py +30 -0
- apsimo/models/memory.py +80 -0
- apsimo/models/mesh.py +72 -0
- apsimo/models/person.py +104 -0
- apsimo/models/signal.py +108 -0
- apsimo/observations/__init__.py +15 -0
- apsimo/observations/store.py +277 -0
- apsimo/patterns/__init__.py +6 -0
- apsimo/patterns/extract.py +187 -0
- apsimo/patterns/store.py +227 -0
- apsimo/persona/__init__.py +1 -0
- apsimo/persona/engine.py +611 -0
- apsimo/persona/manifest.py +140 -0
- apsimo/projects/__init__.py +28 -0
- apsimo/projects/engine.py +1681 -0
- apsimo/projects/event_outbox.py +188 -0
- apsimo/projects/models.py +216 -0
- apsimo/projects/planner.py +181 -0
- apsimo/projects/store.py +1446 -0
- apsimo/proposals/__init__.py +12 -0
- apsimo/proposals/engine.py +114 -0
- apsimo/proposals/models.py +207 -0
- apsimo/qualification/__init__.py +1 -0
- apsimo/qualification/cases.py +75 -0
- apsimo/qualification/cli.py +51 -0
- apsimo/qualification/memory_cases.py +209 -0
- apsimo/qualification/records.py +92 -0
- apsimo/qualification/report.py +87 -0
- apsimo/qualification/runner.py +311 -0
- apsimo/qualification/structured_cases.py +131 -0
- apsimo/reasoning/__init__.py +13 -0
- apsimo/reasoning/executor.py +506 -0
- apsimo/reasoning/loop.py +373 -0
- apsimo/reasoning/native_tools/__init__.py +16 -0
- apsimo/reasoning/native_tools/calculate.py +141 -0
- apsimo/reasoning/native_tools/file_ops.py +150 -0
- apsimo/reasoning/native_tools/web_search.py +49 -0
- apsimo/reasoning/tool_policy.py +182 -0
- apsimo/redact/__init__.py +176 -0
- apsimo/repos/__init__.py +5 -0
- apsimo/repos/mirrors.py +204 -0
- apsimo/research/__init__.py +41 -0
- apsimo/research/artifact.py +482 -0
- apsimo/research/gatherer.py +387 -0
- apsimo/research/pipeline.py +513 -0
- apsimo/research/search/__init__.py +7 -0
- apsimo/research/search/base.py +41 -0
- apsimo/research/search/brave.py +59 -0
- apsimo/research/search/cache.py +51 -0
- apsimo/research/search/duckduckgo.py +103 -0
- apsimo/research/search/orchestrator.py +119 -0
- apsimo/research/search/serpapi.py +59 -0
- apsimo/research/search/tavily.py +59 -0
- apsimo/research/synthesizer.py +309 -0
- apsimo/router/__init__.py +30 -0
- apsimo/router/complexity_scorer.py +148 -0
- apsimo/router/endpoints.py +153 -0
- apsimo/router/fallback.py +58 -0
- apsimo/router/functions.py +243 -0
- apsimo/router/native_policy.py +52 -0
- apsimo/router/router.py +762 -0
- apsimo/router/self_learning.py +174 -0
- apsimo/router/tiers.py +677 -0
- apsimo/sandbox/__init__.py +21 -0
- apsimo/sandbox/backend.py +195 -0
- apsimo/sandbox/manager.py +173 -0
- apsimo/scope_bounds.py +7 -0
- apsimo/secrets/__init__.py +6 -0
- apsimo/secrets/backends/__init__.py +8 -0
- apsimo/secrets/backends/base.py +42 -0
- apsimo/secrets/backends/env.py +110 -0
- apsimo/secrets/backends/keyring.py +72 -0
- apsimo/secrets/backends/onepassword.py +232 -0
- apsimo/secrets/cli.py +191 -0
- apsimo/secrets/manager.py +160 -0
- apsimo/secrets/migration.py +101 -0
- apsimo/secrets/types.py +98 -0
- apsimo/seed.py +41 -0
- apsimo/self_model/__init__.py +37 -0
- apsimo/self_model/appraisals.py +673 -0
- apsimo/self_model/benchmark.py +1314 -0
- apsimo/self_model/brief.py +40 -0
- apsimo/self_model/event_concerns.py +1128 -0
- apsimo/self_model/execution_forecasts.py +353 -0
- apsimo/self_model/expectations.py +1595 -0
- apsimo/self_model/experiments.py +1150 -0
- apsimo/self_model/journal.py +148 -0
- apsimo/self_model/judgments.py +705 -0
- apsimo/self_model/native_outcomes.py +55 -0
- apsimo/self_model/params.py +220 -0
- apsimo/self_model/perspective.py +246 -0
- apsimo/self_model/reconcile.py +183 -0
- apsimo/self_model/reply_forecasts.py +381 -0
- apsimo/self_model/runtime_forecasts.py +296 -0
- apsimo/self_model/runtime_models.py +67 -0
- apsimo/self_model/settlement.py +207 -0
- apsimo/self_model/situation.py +1731 -0
- apsimo/self_model/store.py +883 -0
- apsimo/self_model/supervised.py +137 -0
- apsimo/self_model/thinker.py +99 -0
- apsimo/self_model/trust.py +388 -0
- apsimo/self_model/workspace.py +2388 -0
- apsimo/server.py +4197 -0
- apsimo/services/__init__.py +1 -0
- apsimo/services/agent_bridge.py +474 -0
- apsimo/services/initiative_executor.py +914 -0
- apsimo/services/instance.py +297 -0
- apsimo/sessions/__init__.py +22 -0
- apsimo/sessions/config.py +13 -0
- apsimo/sessions/context_loader.py +88 -0
- apsimo/sessions/federation_session.py +75 -0
- apsimo/sessions/isolated_session.py +98 -0
- apsimo/sessions/reports.py +84 -0
- apsimo/sessions/store.py +148 -0
- apsimo/setup.py +2818 -0
- apsimo/setup_hermes.py +879 -0
- apsimo/setup_local_work.py +218 -0
- apsimo/setup_native_goals.py +134 -0
- apsimo/setup_native_reviews.py +115 -0
- apsimo/skills/__init__.py +10 -0
- apsimo/skills/base.py +108 -0
- apsimo/skills/budget.py +28 -0
- apsimo/skills/executor.py +493 -0
- apsimo/skills/executors/__init__.py +1 -0
- apsimo/skills/executors/behavioral_correction.py +75 -0
- apsimo/skills/executors/capability_gap.py +38 -0
- apsimo/skills/executors/data_quality.py +163 -0
- apsimo/skills/executors/knowledge_acquisition.py +41 -0
- apsimo/skills/executors/operational_hygiene.py +185 -0
- apsimo/skills/executors/subsystem_health.py +169 -0
- apsimo/skills/hermes_export.py +431 -0
- apsimo/skills/index.py +123 -0
- apsimo/skills/learning/__init__.py +21 -0
- apsimo/skills/learning/novelty_detector.py +206 -0
- apsimo/skills/learning/pattern_extractor.py +199 -0
- apsimo/skills/learning/triggers.py +159 -0
- apsimo/skills/loader.py +246 -0
- apsimo/skills/migrations/002_progressive_loading.sql +6 -0
- apsimo/skills/migrations/backfill_triggers.py +20 -0
- apsimo/skills/models.py +202 -0
- apsimo/skills/packager.py +128 -0
- apsimo/skills/protocols.py +70 -0
- apsimo/skills/registry.py +191 -0
- apsimo/skills/runtime.py +58 -0
- apsimo/skills/sandbox_runner.py +229 -0
- apsimo/skills/scheduler.py +129 -0
- apsimo/skills/schema.py +79 -0
- apsimo/skills/security/__init__.py +12 -0
- apsimo/skills/security/guards.py +53 -0
- apsimo/skills/security/scanner.py +223 -0
- apsimo/skills_memory/__init__.py +26 -0
- apsimo/skills_memory/distill.py +159 -0
- apsimo/skills_memory/models.py +85 -0
- apsimo/skills_memory/retrieve.py +62 -0
- apsimo/skills_memory/store.py +172 -0
- apsimo/surprise/__init__.py +6 -0
- apsimo/surprise/accumulation.py +57 -0
- apsimo/surprise/scorer.py +102 -0
- apsimo/surprise/store.py +203 -0
- apsimo/task_queue/__init__.py +69 -0
- apsimo/task_queue/action_receipts.py +148 -0
- apsimo/task_queue/approval_relay_canary.py +108 -0
- apsimo/task_queue/config.py +85 -0
- apsimo/task_queue/contract.py +361 -0
- apsimo/task_queue/events.py +130 -0
- apsimo/task_queue/governor.py +1031 -0
- apsimo/task_queue/handlers/__init__.py +16 -0
- apsimo/task_queue/handlers/base.py +37 -0
- apsimo/task_queue/handlers/inference.py +640 -0
- apsimo/task_queue/handlers/monitoring.py +116 -0
- apsimo/task_queue/handlers/registry.py +75 -0
- apsimo/task_queue/handlers/subtask_handler.py +173 -0
- apsimo/task_queue/handlers/system_maintenance.py +147 -0
- apsimo/task_queue/mesh_integration.py +111 -0
- apsimo/task_queue/models.py +317 -0
- apsimo/task_queue/queue_manager.py +8286 -0
- apsimo/task_queue/routing.py +287 -0
- apsimo/task_queue/scheduler.py +252 -0
- apsimo/task_queue/schema.sql +197 -0
- apsimo/task_queue/work_control.py +342 -0
- apsimo/task_queue/worker.py +993 -0
- apsimo/telemetry.py +145 -0
- apsimo/tom/__init__.py +6 -0
- apsimo/tom/affect.py +387 -0
- apsimo/tom/approvals.py +171 -0
- apsimo/tom/arcs.py +896 -0
- apsimo/tom/asymmetry.py +131 -0
- apsimo/tom/eligibility.py +248 -0
- apsimo/tom/engagement.py +214 -0
- apsimo/tom/exposure.py +214 -0
- apsimo/tom/extractor.py +306 -0
- apsimo/tom/fact_adapters.py +144 -0
- apsimo/tom/facts.py +326 -0
- apsimo/tom/integration.py +592 -0
- apsimo/tom/leveled.py +118 -0
- apsimo/tom/levels.py +247 -0
- apsimo/tom/recipient_audit.py +995 -0
- apsimo/tom/recipient_simulator.py +593 -0
- apsimo/tom/source_lineage.py +93 -0
- apsimo/tom/tom2.py +277 -0
- apsimo/tom/visibility.py +559 -0
- apsimo/tom/visibility_store.py +414 -0
- apsimo/tools/__init__.py +0 -0
- apsimo/tools/definitions.py +740 -0
- apsimo/tools/handlers.py +943 -0
- apsimo/toolsmith/__init__.py +26 -0
- apsimo/toolsmith/authority.py +166 -0
- apsimo/toolsmith/engine.py +559 -0
- apsimo/toolsmith/integrity.py +100 -0
- apsimo/toolsmith/miner.py +145 -0
- apsimo/toolsmith/policy.py +110 -0
- apsimo/toolsmith/registry.py +635 -0
- apsimo/turns/__init__.py +17 -0
- apsimo/turns/audio.py +134 -0
- apsimo/turns/documents.py +235 -0
- apsimo/turns/executions.py +486 -0
- apsimo/turns/hermes_history.py +245 -0
- apsimo/turns/hermes_kanban.py +268 -0
- apsimo/turns/hermes_work.py +96 -0
- apsimo/turns/idempotency.py +752 -0
- apsimo/turns/local_work.py +115 -0
- apsimo/turns/media.py +581 -0
- apsimo/turns/reported_workers.py +196 -0
- apsimo/turns/source_annotations.py +283 -0
- apsimo/turns/source_attribution.py +154 -0
- apsimo/turns/source_read.py +351 -0
- apsimo/turns/source_vectors.py +263 -0
- apsimo/turns/video.py +210 -0
- apsimo/util/autonomy_preset.py +220 -0
- apsimo/util/instance.py +92 -0
- apsimo/util/model_output.py +25 -0
- apsimo/util/quiet_hours.py +27 -0
- apsimo/util/session_safety.py +37 -0
- apsimo/util/temporal.py +343 -0
- apsimo/vector/__init__.py +75 -0
- apsimo/vector/backfill.py +171 -0
- apsimo/vector/caption.py +114 -0
- apsimo/vector/collections.py +51 -0
- apsimo/vector/config.py +102 -0
- apsimo/vector/embedder.py +670 -0
- apsimo/vector/image_preprocess.py +406 -0
- apsimo/vector/image_store.py +296 -0
- apsimo/vector/indexes.py +162 -0
- apsimo/vector/migrate.py +334 -0
- apsimo/vector/multimodal_provider.py +417 -0
- apsimo/vector/multimodal_types.py +87 -0
- apsimo/vector/openai_provider.py +119 -0
- apsimo/vector/query.py +49 -0
- apsimo/vector/reranker.py +565 -0
- apsimo/vector/safety_image.py +159 -0
- apsimo/vector/scanner.py +197 -0
- apsimo/vector/setup.py +289 -0
- apsimo/vector/store.py +533 -0
- apsimo/vector/tiers.py +263 -0
- apsimo/work_orders.py +925 -0
- apsimo/workers/__init__.py +21 -0
- apsimo/workers/agent_bridge.py +640 -0
- apsimo/workers/colony_worker.py +382 -0
- apsimo/workers/queue_worker.py +441 -0
- apsimo/workers/skills_sync.py +152 -0
- apsimo/world_model/__init__.py +71 -0
- apsimo/world_model/causal_maintenance.py +131 -0
- apsimo/world_model/causal_policy.py +43 -0
- apsimo/world_model/causal_query.py +125 -0
- apsimo/world_model/confidence.py +54 -0
- apsimo/world_model/config.py +64 -0
- apsimo/world_model/constants.py +97 -0
- apsimo/world_model/entities.py +145 -0
- apsimo/world_model/expectation_resolvers.py +177 -0
- apsimo/world_model/extraction/__init__.py +7 -0
- apsimo/world_model/extraction/base.py +62 -0
- apsimo/world_model/extraction/conversation_extractor.py +262 -0
- apsimo/world_model/extraction/detector.py +74 -0
- apsimo/world_model/extraction/document_extractor.py +78 -0
- apsimo/world_model/extraction/formats/__init__.py +24 -0
- apsimo/world_model/extraction/formats/csv_fmt.py +68 -0
- apsimo/world_model/extraction/formats/html_fmt.py +72 -0
- apsimo/world_model/extraction/formats/json_fmt.py +68 -0
- apsimo/world_model/extraction/formats/pdf.py +43 -0
- apsimo/world_model/extraction/formats/text.py +27 -0
- apsimo/world_model/extraction/llm_extractor.py +164 -0
- apsimo/world_model/extraction/pipeline.py +73 -0
- apsimo/world_model/integrations/__init__.py +5 -0
- apsimo/world_model/integrations/mind_model_bridge.py +115 -0
- apsimo/world_model/integrations/social_intel_bridge.py +120 -0
- apsimo/world_model/jobs/__init__.py +4 -0
- apsimo/world_model/jobs/extraction_job.py +168 -0
- apsimo/world_model/llm_extract.py +572 -0
- apsimo/world_model/neo4j/__init__.py +5 -0
- apsimo/world_model/neo4j/backend.py +654 -0
- apsimo/world_model/observations.py +155 -0
- apsimo/world_model/populator.py +307 -0
- apsimo/world_model/postgres/__init__.py +1 -0
- apsimo/world_model/postgres/backend.py +683 -0
- apsimo/world_model/relationships.py +25 -0
- apsimo/world_model/resolution/__init__.py +13 -0
- apsimo/world_model/resolution/entity_resolver.py +232 -0
- apsimo/world_model/resolution/merge_audit.py +16 -0
- apsimo/world_model/resolution/merge_workflow.py +117 -0
- apsimo/world_model/source_reports.py +121 -0
- apsimo/world_model/sqlite/__init__.py +4 -0
- apsimo/world_model/sqlite/backend.py +855 -0
- apsimo/world_model/sqlite/schema.sql +132 -0
- apsimo/world_model/store.py +545 -0
- apsimo-1.3.0.dist-info/METADATA +78 -0
- apsimo-1.3.0.dist-info/RECORD +614 -0
- apsimo-1.3.0.dist-info/WHEEL +5 -0
- apsimo-1.3.0.dist-info/entry_points.txt +11 -0
- apsimo-1.3.0.dist-info/licenses/LICENSE +21 -0
- apsimo-1.3.0.dist-info/top_level.txt +2 -0
- colony_sidecar/__init__.py +4 -0
apsimo/feedback/store.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""TypeFeedbackStore -- crude outcome-driven priority feedback (item 3b / 4).
|
|
2
|
+
|
|
3
|
+
Records how the owner responded to each class of proactive work (proposals /
|
|
4
|
+
reach-outs / initiatives) and turns that into a per-type priority multiplier:
|
|
5
|
+
classes the owner acts on are boosted; classes he dismisses or ignores decay.
|
|
6
|
+
This closes the loop the CPI actuator never did, standalone and simple.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
import sqlite3
|
|
13
|
+
import threading
|
|
14
|
+
import time
|
|
15
|
+
from typing import Dict, List, Optional
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
# Multiplicative nudges per outcome, with clamps so a type never fully dies or
|
|
20
|
+
# runs away.
|
|
21
|
+
_NUDGE = {"actioned": 1.12, "acted": 1.12, "dismissed": 0.85, "ignored": 0.9,
|
|
22
|
+
"acknowledged": 1.0, "snoozed": 0.97}
|
|
23
|
+
_MIN, _MAX = 0.5, 1.5
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TypeFeedbackStore:
|
|
27
|
+
def __init__(self, db_path: Optional[str] = None) -> None:
|
|
28
|
+
self._lock = threading.RLock()
|
|
29
|
+
self._conn = sqlite3.connect(str(db_path) if db_path else ":memory:",
|
|
30
|
+
check_same_thread=False)
|
|
31
|
+
self._conn.row_factory = sqlite3.Row
|
|
32
|
+
with self._lock:
|
|
33
|
+
self._conn.execute(
|
|
34
|
+
"""CREATE TABLE IF NOT EXISTS type_feedback (
|
|
35
|
+
itype TEXT PRIMARY KEY, multiplier REAL DEFAULT 1.0,
|
|
36
|
+
actioned INTEGER DEFAULT 0, dismissed INTEGER DEFAULT 0,
|
|
37
|
+
ignored INTEGER DEFAULT 0, other INTEGER DEFAULT 0,
|
|
38
|
+
updated_at REAL
|
|
39
|
+
)""")
|
|
40
|
+
self._conn.commit()
|
|
41
|
+
|
|
42
|
+
def record(self, itype: str, outcome: str) -> float:
|
|
43
|
+
"""Record an outcome for an initiative/proposal type; return new multiplier."""
|
|
44
|
+
itype = (itype or "unknown").strip().lower()
|
|
45
|
+
outcome = (outcome or "").strip().lower()
|
|
46
|
+
nudge = _NUDGE.get(outcome, 1.0)
|
|
47
|
+
col = ("actioned" if nudge > 1.0 else
|
|
48
|
+
"dismissed" if outcome == "dismissed" else
|
|
49
|
+
"ignored" if outcome == "ignored" else "other")
|
|
50
|
+
with self._lock:
|
|
51
|
+
row = self._conn.execute(
|
|
52
|
+
"SELECT multiplier FROM type_feedback WHERE itype=?", (itype,)).fetchone()
|
|
53
|
+
mult = float(row["multiplier"]) if row else 1.0
|
|
54
|
+
mult = max(_MIN, min(_MAX, mult * nudge))
|
|
55
|
+
self._conn.execute(
|
|
56
|
+
f"""INSERT INTO type_feedback (itype, multiplier, {col}, updated_at)
|
|
57
|
+
VALUES (?, ?, 1, ?)
|
|
58
|
+
ON CONFLICT(itype) DO UPDATE SET
|
|
59
|
+
multiplier=?, {col}={col}+1, updated_at=?""",
|
|
60
|
+
(itype, mult, time.time(), mult, time.time()))
|
|
61
|
+
self._conn.commit()
|
|
62
|
+
logger.info("feedback[%s] outcome=%s -> multiplier=%.3f", itype, outcome, mult)
|
|
63
|
+
return mult
|
|
64
|
+
|
|
65
|
+
def multiplier(self, itype: str) -> float:
|
|
66
|
+
itype = (itype or "unknown").strip().lower()
|
|
67
|
+
with self._lock:
|
|
68
|
+
row = self._conn.execute(
|
|
69
|
+
"SELECT multiplier FROM type_feedback WHERE itype=?", (itype,)).fetchone()
|
|
70
|
+
return float(row["multiplier"]) if row else 1.0
|
|
71
|
+
|
|
72
|
+
def snapshot(self) -> List[Dict]:
|
|
73
|
+
with self._lock:
|
|
74
|
+
rows = self._conn.execute(
|
|
75
|
+
"SELECT * FROM type_feedback ORDER BY updated_at DESC").fetchall()
|
|
76
|
+
return [dict(r) for r in rows]
|
apsimo/feeds/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Colony Feeds — spec-driven intelligence feed pipelines.
|
|
2
|
+
|
|
3
|
+
A *feed* is a reproducible pipeline instantiated from a single YAML/JSON spec:
|
|
4
|
+
|
|
5
|
+
collect -> scriptable source adapters (X, GitHub, Hugging Face, Discourse
|
|
6
|
+
forums, arXiv, RSS) score + dedup items into a queue
|
|
7
|
+
distill -> an agent turns the queue into an actionable brief and delivers
|
|
8
|
+
it to the feed's destination (group chat, DM, or file archive)
|
|
9
|
+
digest -> optional daily rollup
|
|
10
|
+
alerts -> optional top-priority items pushed between briefs
|
|
11
|
+
discovery-> optional agent run that researches and proposes new sources
|
|
12
|
+
|
|
13
|
+
Instances are created/managed with ``colony feeds`` (see cli.py) or
|
|
14
|
+
conversationally through the ``feeds-manage`` harness plugin. All state is
|
|
15
|
+
namespaced per instance under the harness data dir so any number of feeds on
|
|
16
|
+
any topics can coexist.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from .spec import FeedSpec, SpecError # noqa: F401
|
apsimo/feeds/cli.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""``colony feeds`` — manage spec-driven feed instances.
|
|
2
|
+
|
|
3
|
+
Also runnable without an installed package (any python3 with PyYAML):
|
|
4
|
+
PYTHONPATH=<repo>/sidecar python3 -m apsimo.feeds.cli <cmd> ...
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import json
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
from . import manager
|
|
14
|
+
from .spec import FeedSpec, SpecError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def build_parser(prog: str = "colony feeds") -> argparse.ArgumentParser:
|
|
18
|
+
p = argparse.ArgumentParser(prog=prog, description="Spec-driven intelligence feeds")
|
|
19
|
+
sub = p.add_subparsers(dest="cmd", required=True)
|
|
20
|
+
|
|
21
|
+
c = sub.add_parser("create", help="Instantiate a feed from a spec file")
|
|
22
|
+
c.add_argument("spec", help="Path to the feed spec (.yaml or .json)")
|
|
23
|
+
c.add_argument("--dry-run", action="store_true",
|
|
24
|
+
help="Validate and print rendered prompts without creating anything")
|
|
25
|
+
|
|
26
|
+
v = sub.add_parser("validate", help="Validate a spec file and exit")
|
|
27
|
+
v.add_argument("spec")
|
|
28
|
+
|
|
29
|
+
sub.add_parser("list", help="List feed instances")
|
|
30
|
+
|
|
31
|
+
for name, hlp in (("status", "Show one instance's queue/brief/job state"),
|
|
32
|
+
("pause", "Pause all of an instance's jobs"),
|
|
33
|
+
("resume", "Resume all of an instance's jobs")):
|
|
34
|
+
s = sub.add_parser(name, help=hlp)
|
|
35
|
+
s.add_argument("name")
|
|
36
|
+
|
|
37
|
+
r = sub.add_parser("run", help="Run one stage of an instance now")
|
|
38
|
+
r.add_argument("name")
|
|
39
|
+
r.add_argument("stage", choices=["collect", "distill", "digest", "alert", "discovery"])
|
|
40
|
+
|
|
41
|
+
d = sub.add_parser("delete", help="Remove an instance's jobs and shims")
|
|
42
|
+
d.add_argument("name")
|
|
43
|
+
d.add_argument("--purge", action="store_true", help="Also delete the instance data dir")
|
|
44
|
+
return p
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def main(argv: list[str] | None = None, prog: str = "colony feeds") -> None:
|
|
48
|
+
args = build_parser(prog).parse_args(argv)
|
|
49
|
+
try:
|
|
50
|
+
if args.cmd == "validate":
|
|
51
|
+
spec = FeedSpec.load(args.spec)
|
|
52
|
+
print(f"OK: {spec.name} ({spec.title})")
|
|
53
|
+
elif args.cmd == "create":
|
|
54
|
+
result = manager.create(args.spec, dry_run=args.dry_run)
|
|
55
|
+
if args.dry_run:
|
|
56
|
+
for stage, prompt in result["prompts"].items():
|
|
57
|
+
print(f"===== {stage} prompt =====\n{prompt}\n")
|
|
58
|
+
print(f"dry-run OK: {result['name']} -> {result['root']}")
|
|
59
|
+
else:
|
|
60
|
+
print(json.dumps(result, indent=2))
|
|
61
|
+
elif args.cmd == "list":
|
|
62
|
+
for inst in manager.list_instances():
|
|
63
|
+
state = "paused" if inst.get("paused") else "active"
|
|
64
|
+
print(f"{inst['name']:24} {state:7} jobs={','.join(inst['jobs'])}")
|
|
65
|
+
elif args.cmd == "status":
|
|
66
|
+
print(json.dumps(manager.status(args.name), indent=2))
|
|
67
|
+
elif args.cmd == "pause":
|
|
68
|
+
manager.pause(args.name)
|
|
69
|
+
print(f"paused {args.name}")
|
|
70
|
+
elif args.cmd == "resume":
|
|
71
|
+
manager.resume(args.name)
|
|
72
|
+
print(f"resumed {args.name}")
|
|
73
|
+
elif args.cmd == "run":
|
|
74
|
+
manager.run(args.name, args.stage)
|
|
75
|
+
elif args.cmd == "delete":
|
|
76
|
+
manager.delete(args.name, purge=args.purge)
|
|
77
|
+
print(f"deleted {args.name}")
|
|
78
|
+
except SpecError as e:
|
|
79
|
+
print(str(e), file=sys.stderr)
|
|
80
|
+
sys.exit(2)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
if __name__ == "__main__":
|
|
84
|
+
main(prog="python3 -m apsimo.feeds.cli")
|
apsimo/feeds/engine.py
ADDED
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Colony feed collector engine — self-contained, stdlib-only.
|
|
3
|
+
|
|
4
|
+
This file is BOTH a package module and a deployable script: the feeds manager
|
|
5
|
+
copies it verbatim into the harness scripts dir (as ``colony-feed-engine.py``)
|
|
6
|
+
where per-instance shims execute it under any python3. Keep it stdlib-only.
|
|
7
|
+
|
|
8
|
+
Stages (selected via $COLONY_FEED_STAGE, default ``collect``):
|
|
9
|
+
collect -> run every configured source adapter, score + dedup new items,
|
|
10
|
+
overwrite the instance queue with never-seen items, write the
|
|
11
|
+
alert file when top-priority items appear
|
|
12
|
+
alert -> print pending alert text (empty stdout = silent), optionally
|
|
13
|
+
piping it through the instance send_command
|
|
14
|
+
|
|
15
|
+
Config file (path in $COLONY_FEED_CONFIG) is either the spec-rendered shape
|
|
16
|
+
produced by ``FeedSpec.engine_config()`` or a legacy hand-written shape
|
|
17
|
+
(detected by the absence of ``feed_name``) kept for pre-framework installs.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import hashlib
|
|
21
|
+
import json
|
|
22
|
+
import os
|
|
23
|
+
import re
|
|
24
|
+
import sqlite3
|
|
25
|
+
import subprocess
|
|
26
|
+
import sys
|
|
27
|
+
import time
|
|
28
|
+
import urllib.parse
|
|
29
|
+
import urllib.request
|
|
30
|
+
from datetime import datetime, timezone
|
|
31
|
+
|
|
32
|
+
UA = "Colony-Feed-Collector/1.0"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# --------------------------------------------------------------------- config
|
|
36
|
+
def load_config():
|
|
37
|
+
path = os.environ.get("COLONY_FEED_CONFIG")
|
|
38
|
+
if not path:
|
|
39
|
+
raise SystemExit("COLONY_FEED_CONFIG not set")
|
|
40
|
+
with open(os.path.expanduser(path), encoding="utf-8") as f:
|
|
41
|
+
cfg = json.load(f)
|
|
42
|
+
if "feed_name" not in cfg:
|
|
43
|
+
cfg = _normalize_legacy(cfg)
|
|
44
|
+
cfg.setdefault("benchmark_regex", r"\d+\s*(tok/s|tokens?/?s|t/s|tflops?|ms|latency|throughput)")
|
|
45
|
+
return cfg
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _normalize_legacy(cfg):
|
|
49
|
+
"""Map the pre-framework single-feed config shape onto the engine shape."""
|
|
50
|
+
home = os.path.expanduser("~")
|
|
51
|
+
sc = cfg.get("scoring", {})
|
|
52
|
+
cats = []
|
|
53
|
+
for key, points_key in (("hardware_keywords", "hardware_match"),
|
|
54
|
+
("model_keywords", "model_match"),
|
|
55
|
+
("inference_keywords", "inference_technique")):
|
|
56
|
+
kws = cfg.get(key) or []
|
|
57
|
+
if kws:
|
|
58
|
+
cats.append({"keywords": kws, "points": sc.get(points_key, 10)})
|
|
59
|
+
# boosts that were hardcoded in the original collector
|
|
60
|
+
if sc.get("agent_infra"):
|
|
61
|
+
cats.append({"keywords": ["agent", "harness", "router", "orchestrat", "mcp",
|
|
62
|
+
"tool use", "sub-agent"],
|
|
63
|
+
"points": sc["agent_infra"]})
|
|
64
|
+
if sc.get("training_relevant"):
|
|
65
|
+
cats.append({"keywords": ["training", "fine-tune", "fine tune", "lora", "qlora",
|
|
66
|
+
"distributed", "fsdp", "deepspeed"],
|
|
67
|
+
"points": sc["training_relevant"]})
|
|
68
|
+
return {
|
|
69
|
+
"feed_name": cfg.get("name", "legacy"),
|
|
70
|
+
"queue_path": cfg.get("queue_path", f"{home}/.hermes/data/feed_queue.json"),
|
|
71
|
+
"seen_db": cfg.get("seen_db", f"{home}/.hermes/data/feed_seen.db"),
|
|
72
|
+
"alert_path": cfg.get("alert_path", f"{home}/.hermes/data/p0_alert.json"),
|
|
73
|
+
"x_searches": cfg.get("x_searches", []),
|
|
74
|
+
"x_results_per_query": 15,
|
|
75
|
+
"key_accounts": cfg.get("key_accounts", []),
|
|
76
|
+
"hf_keywords": cfg.get("hf_keywords", []),
|
|
77
|
+
"github_search": cfg.get("github_search",
|
|
78
|
+
"inference OR vllm OR sglang OR speculative OR llm serving"),
|
|
79
|
+
"github_keywords": cfg.get("github_keywords", []),
|
|
80
|
+
"forum_urls": cfg.get("forum_urls", []),
|
|
81
|
+
"forum_title_keywords": cfg.get("forum_title_keywords", []),
|
|
82
|
+
"arxiv_categories": cfg.get("arxiv_categories", []),
|
|
83
|
+
"arxiv_query_terms": cfg.get("arxiv_query_terms",
|
|
84
|
+
["speculative", "inference", "quantization", "serving",
|
|
85
|
+
"efficient", "throughput", "KV cache", "mixture of experts"]),
|
|
86
|
+
"arxiv_keywords": cfg.get("arxiv_keywords", []),
|
|
87
|
+
"rss": cfg.get("rss", []),
|
|
88
|
+
"keyword_categories": cats,
|
|
89
|
+
"scoring": {
|
|
90
|
+
"account_whitelist": sc.get("account_whitelist", 10),
|
|
91
|
+
"engagement_per_100_likes": sc.get("engagement_per_100_likes", 1),
|
|
92
|
+
"engagement_per_1000_likes": sc.get("engagement_per_1000_likes", 2),
|
|
93
|
+
"engagement_max": sc.get("engagement_max", 10),
|
|
94
|
+
"human_curated": sc.get("human_curated", 20),
|
|
95
|
+
"benchmark_numbers": sc.get("benchmark_numbers", 15),
|
|
96
|
+
"code_release": sc.get("code_release", 10),
|
|
97
|
+
"open_weights": sc.get("open_weights", 10),
|
|
98
|
+
},
|
|
99
|
+
"priority_thresholds": cfg.get("priority_thresholds", {"p0": 50, "p1": 35, "p2": 20}),
|
|
100
|
+
"send_command": cfg.get("send_command", ""),
|
|
101
|
+
"alert_min_priority": cfg.get("alert_min_priority", "P0"),
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# ----------------------------------------------------------------- seen store
|
|
106
|
+
def get_db(cfg):
|
|
107
|
+
path = os.path.expanduser(cfg["seen_db"])
|
|
108
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
109
|
+
conn = sqlite3.connect(path)
|
|
110
|
+
conn.execute("PRAGMA journal_mode=WAL")
|
|
111
|
+
conn.execute("""CREATE TABLE IF NOT EXISTS seen_items (
|
|
112
|
+
id TEXT PRIMARY KEY, source TEXT, url TEXT, score INTEGER,
|
|
113
|
+
priority TEXT, raw_json TEXT, processed_at TEXT)""")
|
|
114
|
+
return conn
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def is_seen(conn, item_id):
|
|
118
|
+
return conn.execute("SELECT 1 FROM seen_items WHERE id=?", (item_id,)).fetchone() is not None
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def mark_seen(conn, item_id, source, url, score, priority, raw=""):
|
|
122
|
+
conn.execute(
|
|
123
|
+
"INSERT OR REPLACE INTO seen_items (id, source, url, score, priority, raw_json, processed_at)"
|
|
124
|
+
" VALUES (?,?,?,?,?,?,datetime('now'))",
|
|
125
|
+
(item_id, source, url, score, priority, json.dumps(raw)[:5000]))
|
|
126
|
+
conn.commit()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# -------------------------------------------------------------------- scoring
|
|
130
|
+
def score_item(text, cfg, account=None, likes=0, has_code=False,
|
|
131
|
+
has_weights=False, human_curated=False, has_benchmarks=False):
|
|
132
|
+
sc = cfg["scoring"]
|
|
133
|
+
tl = text.lower()
|
|
134
|
+
score = 0
|
|
135
|
+
for cat in cfg.get("keyword_categories", []):
|
|
136
|
+
if any(kw.lower() in tl for kw in cat.get("keywords", [])):
|
|
137
|
+
score += cat.get("points", 10)
|
|
138
|
+
if account and account.lower() in [a.lower() for a in cfg.get("key_accounts", [])]:
|
|
139
|
+
score += sc["account_whitelist"]
|
|
140
|
+
if likes > 0:
|
|
141
|
+
score += min(int(likes / 100) * sc["engagement_per_100_likes"]
|
|
142
|
+
+ int(likes / 1000) * sc["engagement_per_1000_likes"],
|
|
143
|
+
sc["engagement_max"])
|
|
144
|
+
if human_curated:
|
|
145
|
+
score += sc["human_curated"]
|
|
146
|
+
if has_benchmarks or re.search(cfg["benchmark_regex"], tl):
|
|
147
|
+
score += sc["benchmark_numbers"]
|
|
148
|
+
if has_code:
|
|
149
|
+
score += sc["code_release"]
|
|
150
|
+
if has_weights or "open weights" in tl or ("weights" in tl and "release" in tl):
|
|
151
|
+
score += sc["open_weights"]
|
|
152
|
+
th = cfg["priority_thresholds"]
|
|
153
|
+
for pri in ("p0", "p1", "p2"):
|
|
154
|
+
if score >= th.get(pri, 10**9):
|
|
155
|
+
return score, pri.upper()
|
|
156
|
+
return score, "P3"
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _fetch_json(url, timeout=15, headers=None):
|
|
160
|
+
req = urllib.request.Request(url)
|
|
161
|
+
req.add_header("User-Agent", UA)
|
|
162
|
+
for k, v in (headers or {}).items():
|
|
163
|
+
req.add_header(k, v)
|
|
164
|
+
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
165
|
+
return json.loads(resp.read().decode())
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _fetch_text(url, timeout=20):
|
|
169
|
+
req = urllib.request.Request(url)
|
|
170
|
+
req.add_header("User-Agent", UA)
|
|
171
|
+
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
172
|
+
return resp.read().decode(errors="replace")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# ------------------------------------------------------------------- adapters
|
|
176
|
+
def collect_x(cfg, conn):
|
|
177
|
+
items = []
|
|
178
|
+
for query in cfg.get("x_searches", []):
|
|
179
|
+
try:
|
|
180
|
+
result = subprocess.run(["xurl", "search", query, "-n",
|
|
181
|
+
str(cfg.get("x_results_per_query", 15))],
|
|
182
|
+
capture_output=True, text=True, timeout=30)
|
|
183
|
+
if result.returncode != 0:
|
|
184
|
+
continue
|
|
185
|
+
data = json.loads(result.stdout)
|
|
186
|
+
users = {u.get("id"): u.get("username", "")
|
|
187
|
+
for u in data.get("includes", {}).get("users", [])}
|
|
188
|
+
for tweet in data.get("data", []):
|
|
189
|
+
tid = tweet.get("id", "")
|
|
190
|
+
if not tid or is_seen(conn, f"x:{tid}"):
|
|
191
|
+
continue
|
|
192
|
+
text = tweet.get("text", "")
|
|
193
|
+
author = users.get(tweet.get("author_id"), "")
|
|
194
|
+
likes = tweet.get("public_metrics", {}).get("like_count", 0)
|
|
195
|
+
has_code = "github.com" in text or "huggingface.co" in text
|
|
196
|
+
sc, pri = score_item(text, cfg, account=author, likes=likes, has_code=has_code)
|
|
197
|
+
url = (f"https://x.com/{author}/status/{tid}" if author
|
|
198
|
+
else f"https://x.com/i/web/status/{tid}")
|
|
199
|
+
mark_seen(conn, f"x:{tid}", "x", url, sc, pri,
|
|
200
|
+
{"text": text[:500], "author": author})
|
|
201
|
+
items.append({"source": "x", "id": tid, "author": author,
|
|
202
|
+
"text": text[:500], "url": url, "score": sc,
|
|
203
|
+
"priority": pri, "likes": likes,
|
|
204
|
+
"created_at": tweet.get("created_at", "")})
|
|
205
|
+
except Exception as e:
|
|
206
|
+
print(f"X error for '{query[:40]}': {e}", file=sys.stderr)
|
|
207
|
+
time.sleep(1)
|
|
208
|
+
return items
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def collect_hf(cfg, conn):
|
|
212
|
+
kws = cfg.get("hf_keywords", [])
|
|
213
|
+
if not kws:
|
|
214
|
+
return []
|
|
215
|
+
items = []
|
|
216
|
+
try:
|
|
217
|
+
models = _fetch_json("https://huggingface.co/api/models?sort=trending&limit=30")
|
|
218
|
+
for model in models[:30]:
|
|
219
|
+
mid = model.get("id", "")
|
|
220
|
+
if not mid or is_seen(conn, f"hf:{mid}"):
|
|
221
|
+
continue
|
|
222
|
+
text = (mid + " " + " ".join(model.get("tags", []))).lower()
|
|
223
|
+
if not any(kw.lower() in text for kw in kws):
|
|
224
|
+
continue
|
|
225
|
+
sc, pri = score_item(text, cfg, has_weights=True, has_code=True)
|
|
226
|
+
url = f"https://huggingface.co/{mid}"
|
|
227
|
+
mark_seen(conn, f"hf:{mid}", "huggingface", url, sc, pri, {"name": mid})
|
|
228
|
+
items.append({"source": "huggingface", "id": mid, "url": url,
|
|
229
|
+
"text": f"Model: {mid} | Tags: {', '.join(model.get('tags', [])[:5])}"
|
|
230
|
+
f" | Downloads: {model.get('downloads', 0)}",
|
|
231
|
+
"score": sc, "priority": pri})
|
|
232
|
+
except Exception as e:
|
|
233
|
+
print(f"HF error: {e}", file=sys.stderr)
|
|
234
|
+
return items
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def collect_github(cfg, conn):
|
|
238
|
+
query = cfg.get("github_search", "")
|
|
239
|
+
if not query:
|
|
240
|
+
return []
|
|
241
|
+
items = []
|
|
242
|
+
try:
|
|
243
|
+
q = urllib.parse.quote(query)
|
|
244
|
+
data = _fetch_json("https://api.github.com/search/repositories"
|
|
245
|
+
f"?q={q}&sort=updated&order=desc&per_page=15",
|
|
246
|
+
headers={"Accept": "application/vnd.github.v3+json"})
|
|
247
|
+
kws = cfg.get("github_keywords", [])
|
|
248
|
+
for repo in data.get("items", []):
|
|
249
|
+
full = repo.get("full_name", "")
|
|
250
|
+
key = f"gh:{full}:{repo.get('updated_at', '')}"
|
|
251
|
+
if not full or is_seen(conn, key):
|
|
252
|
+
continue
|
|
253
|
+
text = (full + " " + (repo.get("description") or "")).lower()
|
|
254
|
+
if kws and not any(kw.lower() in text for kw in kws):
|
|
255
|
+
continue
|
|
256
|
+
stars = repo.get("stargazers_count", 0)
|
|
257
|
+
sc, pri = score_item(text, cfg, has_code=True, likes=min(stars, 10000))
|
|
258
|
+
url = repo.get("html_url", "")
|
|
259
|
+
mark_seen(conn, key, "github", url, sc, pri, {"name": full})
|
|
260
|
+
items.append({"source": "github", "id": full, "url": url,
|
|
261
|
+
"text": f"Repo: {full} | {repo.get('description', '')} | Stars: {stars}",
|
|
262
|
+
"score": sc, "priority": pri, "stars": stars})
|
|
263
|
+
except Exception as e:
|
|
264
|
+
print(f"GitHub error: {e}", file=sys.stderr)
|
|
265
|
+
return items
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def collect_forums(cfg, conn):
|
|
269
|
+
items = []
|
|
270
|
+
title_kws = [k.lower() for k in cfg.get("forum_title_keywords", [])]
|
|
271
|
+
for forum_url in cfg.get("forum_urls", []):
|
|
272
|
+
try:
|
|
273
|
+
data = _fetch_json(forum_url.rstrip("/") + ".json")
|
|
274
|
+
base = "/".join(forum_url.split("/")[:3])
|
|
275
|
+
for topic in data.get("topic_list", {}).get("topics", [])[:20]:
|
|
276
|
+
tid = str(topic.get("id", ""))
|
|
277
|
+
title = topic.get("title", "")
|
|
278
|
+
if not tid or is_seen(conn, f"forum:{tid}"):
|
|
279
|
+
continue
|
|
280
|
+
if title_kws and not any(kw in title.lower() for kw in title_kws):
|
|
281
|
+
continue
|
|
282
|
+
views = topic.get("views", 0)
|
|
283
|
+
sc, pri = score_item(title, cfg, has_code=True, likes=views // 100)
|
|
284
|
+
url = f"{base}/t/{topic.get('slug', '')}/{tid}"
|
|
285
|
+
mark_seen(conn, f"forum:{tid}", "forum", url, sc, pri, {"title": title})
|
|
286
|
+
items.append({"source": "forum", "id": tid, "text": title, "url": url,
|
|
287
|
+
"score": sc, "priority": pri, "views": views,
|
|
288
|
+
"replies": topic.get("posts_count", 1) - 1})
|
|
289
|
+
except Exception as e:
|
|
290
|
+
print(f"Forum error for {forum_url}: {e}", file=sys.stderr)
|
|
291
|
+
return items
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def collect_arxiv(cfg, conn):
|
|
295
|
+
kws = [k.lower() for k in cfg.get("arxiv_keywords", [])]
|
|
296
|
+
terms = cfg.get("arxiv_query_terms", [])
|
|
297
|
+
items = []
|
|
298
|
+
for cat in cfg.get("arxiv_categories", []):
|
|
299
|
+
try:
|
|
300
|
+
abs_q = "+OR+".join(f"abs:{urllib.parse.quote(t)}" for t in terms) or "abs:survey"
|
|
301
|
+
xml = _fetch_text("http://export.arxiv.org/api/query?search_query="
|
|
302
|
+
f"cat:{cat}+AND+({abs_q})"
|
|
303
|
+
"&start=0&max_results=15&sortBy=submittedDate&sortOrder=descending")
|
|
304
|
+
for entry in re.findall(r"<entry>(.*?)</entry>", xml, re.DOTALL):
|
|
305
|
+
m_id = re.search(r"<id>(.*?)</id>", entry)
|
|
306
|
+
m_title = re.search(r"<title>(.*?)</title>", entry, re.DOTALL)
|
|
307
|
+
if not m_id or not m_title:
|
|
308
|
+
continue
|
|
309
|
+
arxiv_id = m_id.group(1).split("/")[-1]
|
|
310
|
+
if is_seen(conn, f"arxiv:{arxiv_id}"):
|
|
311
|
+
continue
|
|
312
|
+
title = m_title.group(1).strip().replace("\n", " ")
|
|
313
|
+
m_sum = re.search(r"<summary>(.*?)</summary>", entry, re.DOTALL)
|
|
314
|
+
summary = m_sum.group(1).strip()[:500] if m_sum else ""
|
|
315
|
+
text = (title + " " + summary).lower()
|
|
316
|
+
if kws and not any(kw in text for kw in kws):
|
|
317
|
+
continue
|
|
318
|
+
sc, pri = score_item(title + " " + summary, cfg, has_code=True)
|
|
319
|
+
url = f"https://arxiv.org/abs/{arxiv_id}"
|
|
320
|
+
mark_seen(conn, f"arxiv:{arxiv_id}", "arxiv", url, sc, pri, {"title": title})
|
|
321
|
+
items.append({"source": "arxiv", "id": arxiv_id, "text": title,
|
|
322
|
+
"summary": summary[:300], "url": url,
|
|
323
|
+
"score": sc, "priority": pri})
|
|
324
|
+
except Exception as e:
|
|
325
|
+
print(f"arXiv error for {cat}: {e}", file=sys.stderr)
|
|
326
|
+
time.sleep(3) # arXiv rate limit: 1 req / 3s
|
|
327
|
+
return items
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def collect_rss(cfg, conn):
|
|
331
|
+
items = []
|
|
332
|
+
for feed in cfg.get("rss", []):
|
|
333
|
+
name, url = feed.get("name", "rss"), feed.get("url", "")
|
|
334
|
+
if not url:
|
|
335
|
+
continue
|
|
336
|
+
try:
|
|
337
|
+
xml = _fetch_text(url)
|
|
338
|
+
entries = (re.findall(r"<item>(.*?)</item>", xml, re.DOTALL)
|
|
339
|
+
or re.findall(r"<entry>(.*?)</entry>", xml, re.DOTALL))
|
|
340
|
+
for entry in entries[:20]:
|
|
341
|
+
m_title = re.search(r"<title[^>]*>(.*?)</title>", entry, re.DOTALL)
|
|
342
|
+
m_link = (re.search(r"<link[^>]*href=\"(.*?)\"", entry)
|
|
343
|
+
or re.search(r"<link[^>]*>(.*?)</link>", entry, re.DOTALL))
|
|
344
|
+
if not m_title:
|
|
345
|
+
continue
|
|
346
|
+
title = re.sub(r"<!\[CDATA\[|\]\]>", "", m_title.group(1)).strip()
|
|
347
|
+
link = (m_link.group(1).strip() if m_link else url)
|
|
348
|
+
key = f"rss:{hashlib.md5((name + title).encode()).hexdigest()[:16]}"
|
|
349
|
+
if is_seen(conn, key):
|
|
350
|
+
continue
|
|
351
|
+
sc, pri = score_item(title, cfg)
|
|
352
|
+
mark_seen(conn, key, f"rss:{name}", link, sc, pri, {"title": title})
|
|
353
|
+
items.append({"source": f"rss:{name}", "id": key, "text": title,
|
|
354
|
+
"url": link, "score": sc, "priority": pri})
|
|
355
|
+
except Exception as e:
|
|
356
|
+
print(f"RSS error for {name}: {e}", file=sys.stderr)
|
|
357
|
+
return items
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
# --------------------------------------------------------------------- stages
|
|
361
|
+
ADAPTERS = [("x", collect_x), ("huggingface", collect_hf), ("github", collect_github),
|
|
362
|
+
("forum", collect_forums), ("arxiv", collect_arxiv), ("rss", collect_rss)]
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def stage_collect(cfg):
|
|
366
|
+
conn = get_db(cfg)
|
|
367
|
+
all_items = []
|
|
368
|
+
for label, fn in ADAPTERS:
|
|
369
|
+
got = fn(cfg, conn)
|
|
370
|
+
all_items.extend(got)
|
|
371
|
+
print(f" {label}: {len(got)} new items", file=sys.stderr)
|
|
372
|
+
all_items.sort(key=lambda x: x["score"], reverse=True)
|
|
373
|
+
|
|
374
|
+
now = datetime.now(timezone.utc).astimezone().strftime("%Y-%m-%d %I:%M %p %Z")
|
|
375
|
+
queue_path = os.path.expanduser(cfg["queue_path"])
|
|
376
|
+
os.makedirs(os.path.dirname(queue_path), exist_ok=True)
|
|
377
|
+
with open(queue_path, "w", encoding="utf-8") as f:
|
|
378
|
+
json.dump({"feed": cfg["feed_name"], "collected_at": now,
|
|
379
|
+
"total_items": len(all_items), "items": all_items}, f, indent=2)
|
|
380
|
+
|
|
381
|
+
min_pri = cfg.get("alert_min_priority", "P0")
|
|
382
|
+
alerts = [i for i in all_items if i["priority"] <= min_pri] # 'P0' < 'P1'
|
|
383
|
+
if alerts:
|
|
384
|
+
with open(os.path.expanduser(cfg["alert_path"]), "w", encoding="utf-8") as f:
|
|
385
|
+
json.dump({"alerts": alerts, "generated_at": now, "delivered": False}, f, indent=2)
|
|
386
|
+
|
|
387
|
+
conn.close()
|
|
388
|
+
by_pri = {}
|
|
389
|
+
for i in all_items:
|
|
390
|
+
by_pri[i["priority"]] = by_pri.get(i["priority"], 0) + 1
|
|
391
|
+
print(json.dumps({"feed": cfg["feed_name"], "collected_at": now,
|
|
392
|
+
"total": len(all_items), "by_priority": by_pri,
|
|
393
|
+
"top_5": [{"text": i["text"][:100], "priority": i["priority"],
|
|
394
|
+
"url": i["url"]} for i in all_items[:5]]}, indent=2))
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def stage_alert(cfg):
|
|
398
|
+
"""Emit pending alerts once. Empty stdout = silent (no-agent cron pattern)."""
|
|
399
|
+
path = os.path.expanduser(cfg["alert_path"])
|
|
400
|
+
if not os.path.exists(path):
|
|
401
|
+
return
|
|
402
|
+
with open(path, encoding="utf-8") as f:
|
|
403
|
+
data = json.load(f)
|
|
404
|
+
if data.get("delivered") or not data.get("alerts"):
|
|
405
|
+
return
|
|
406
|
+
lines = [f"[{cfg['feed_name']}] {len(data['alerts'])} top-priority item(s):"]
|
|
407
|
+
for a in data["alerts"][:10]:
|
|
408
|
+
lines.append(f"- {a['text'][:160]}\n {a['url']}")
|
|
409
|
+
text = "\n".join(lines)
|
|
410
|
+
send_cmd = cfg.get("send_command", "")
|
|
411
|
+
if send_cmd:
|
|
412
|
+
try:
|
|
413
|
+
subprocess.run(send_cmd, shell=True, input=text.encode(), timeout=60, check=True)
|
|
414
|
+
print(f"[{cfg['feed_name']}] alert posted ({len(data['alerts'])} items)")
|
|
415
|
+
except Exception as e:
|
|
416
|
+
print(f"[{cfg['feed_name']}] alert post FAILED: {e}")
|
|
417
|
+
return # keep undelivered so the next tick retries
|
|
418
|
+
else:
|
|
419
|
+
print(text)
|
|
420
|
+
data["delivered"] = True
|
|
421
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
422
|
+
json.dump(data, f, indent=2)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def main():
|
|
426
|
+
cfg = load_config()
|
|
427
|
+
stage = os.environ.get("COLONY_FEED_STAGE", "collect")
|
|
428
|
+
if stage == "collect":
|
|
429
|
+
stage_collect(cfg)
|
|
430
|
+
elif stage == "alert":
|
|
431
|
+
stage_alert(cfg)
|
|
432
|
+
else:
|
|
433
|
+
raise SystemExit(f"unknown stage: {stage}")
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
if __name__ == "__main__":
|
|
437
|
+
main()
|