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,91 @@
|
|
|
1
|
+
"""Atomic, renewable undertakings in the existing commitment database.
|
|
2
|
+
|
|
3
|
+
These leases coordinate explicit work. They never authorize an external effect
|
|
4
|
+
or claim that an obligation has been fulfilled.
|
|
5
|
+
"""
|
|
6
|
+
from contextlib import closing
|
|
7
|
+
import time
|
|
8
|
+
import uuid
|
|
9
|
+
|
|
10
|
+
from apsimo.commitments.store import OPEN_STATUSES
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CommitmentWork:
|
|
14
|
+
def __init__(self, store, *, clock=time.time, lease_seconds=120):
|
|
15
|
+
self.store, self.clock = store, clock
|
|
16
|
+
self.lease_seconds = lease_seconds
|
|
17
|
+
with closing(store._connect()) as conn, conn:
|
|
18
|
+
conn.execute('''CREATE TABLE IF NOT EXISTS commitment_work (
|
|
19
|
+
commitment_id TEXT PRIMARY KEY, claim_id TEXT NOT NULL,
|
|
20
|
+
principal_id TEXT NOT NULL, contact_id TEXT NOT NULL,
|
|
21
|
+
session_id TEXT NOT NULL, task_id TEXT NOT NULL, turn_id TEXT NOT NULL,
|
|
22
|
+
state TEXT NOT NULL, last_observed_at REAL NOT NULL, lease_until REAL NOT NULL)''')
|
|
23
|
+
|
|
24
|
+
def _view(self, commitment, row, now):
|
|
25
|
+
result = {'commitment_id': commitment['id'], 'description': commitment['description'],
|
|
26
|
+
'commitment_status': commitment['status'], 'work_state': 'unclaimed'}
|
|
27
|
+
if row:
|
|
28
|
+
result.update({key: row[key] for key in ('session_id', 'turn_id', 'last_observed_at', 'lease_until')})
|
|
29
|
+
result['work_state'] = row['state'] if row['state'] != 'held' or row['lease_until'] > now else 'expired'
|
|
30
|
+
result['observation_age_seconds'] = round(max(0, now - row['last_observed_at']), 1)
|
|
31
|
+
if commitment['status'] not in OPEN_STATUSES:
|
|
32
|
+
result['work_state'] = 'obligation_closed'
|
|
33
|
+
return result
|
|
34
|
+
|
|
35
|
+
def operate(self, commitment_id, *, operation, principal_id, contact_id,
|
|
36
|
+
session_id, task_id, turn_id, claim_id=''):
|
|
37
|
+
now = self.clock()
|
|
38
|
+
holder = (principal_id, contact_id, session_id, task_id, turn_id)
|
|
39
|
+
with closing(self.store._connect()) as conn, conn:
|
|
40
|
+
conn.execute('BEGIN IMMEDIATE')
|
|
41
|
+
commitment = conn.execute('SELECT * FROM commitments WHERE id=? AND person_id=?',
|
|
42
|
+
(commitment_id, contact_id)).fetchone()
|
|
43
|
+
if commitment is None:
|
|
44
|
+
raise KeyError('unknown commitment')
|
|
45
|
+
row = conn.execute('SELECT * FROM commitment_work WHERE commitment_id=?', (commitment_id,)).fetchone()
|
|
46
|
+
def result(accepted, reason, token=''):
|
|
47
|
+
current = conn.execute('SELECT * FROM commitment_work WHERE commitment_id=?', (commitment_id,)).fetchone()
|
|
48
|
+
return {**self._view(commitment, current, now), 'accepted': accepted, 'reason': reason,
|
|
49
|
+
**({'claim_id': token} if token else {}), 'effect_authorized': False}
|
|
50
|
+
if operation == 'status':
|
|
51
|
+
return result(True, 'observed')
|
|
52
|
+
if commitment['status'] not in OPEN_STATUSES:
|
|
53
|
+
return result(False, 'obligation_closed')
|
|
54
|
+
same_holder = row is not None and tuple(row[key] for key in
|
|
55
|
+
('principal_id', 'contact_id', 'session_id', 'task_id', 'turn_id')) == holder
|
|
56
|
+
if operation == 'claim':
|
|
57
|
+
if row and row['state'] == 'held' and row['lease_until'] > now:
|
|
58
|
+
return result(same_holder, 'already_held' if same_holder else 'undertaken_elsewhere',
|
|
59
|
+
row['claim_id'] if same_holder else '')
|
|
60
|
+
token = uuid.uuid4().hex
|
|
61
|
+
conn.execute('''INSERT INTO commitment_work VALUES (?,?,?,?,?,?,?,'held',?,?)
|
|
62
|
+
ON CONFLICT(commitment_id) DO UPDATE SET claim_id=excluded.claim_id,
|
|
63
|
+
principal_id=excluded.principal_id,contact_id=excluded.contact_id,
|
|
64
|
+
session_id=excluded.session_id,task_id=excluded.task_id,turn_id=excluded.turn_id,
|
|
65
|
+
state='held',last_observed_at=excluded.last_observed_at,lease_until=excluded.lease_until''',
|
|
66
|
+
(commitment_id, token, *holder, now, now + self.lease_seconds))
|
|
67
|
+
return result(True, 'claimed', token)
|
|
68
|
+
if not (same_holder and row['state'] == 'held' and claim_id and row['claim_id'] == claim_id):
|
|
69
|
+
return result(False, 'claim_superseded')
|
|
70
|
+
if operation == 'renew':
|
|
71
|
+
# Extending an unchallenged holder is safe even after inactivity;
|
|
72
|
+
# a racing reclaim changes the token in this same SQLite lock.
|
|
73
|
+
conn.execute('UPDATE commitment_work SET last_observed_at=?,lease_until=? WHERE commitment_id=?',
|
|
74
|
+
(now, now + self.lease_seconds, commitment_id))
|
|
75
|
+
return result(True, 'renewed', claim_id)
|
|
76
|
+
if operation == 'release':
|
|
77
|
+
conn.execute("UPDATE commitment_work SET state='released',last_observed_at=?,lease_until=? WHERE commitment_id=?",
|
|
78
|
+
(now, now, commitment_id))
|
|
79
|
+
return result(True, 'released')
|
|
80
|
+
raise ValueError('unknown work operation')
|
|
81
|
+
|
|
82
|
+
def for_commitments(self, commitment_ids, *, contact_id):
|
|
83
|
+
if not commitment_ids:
|
|
84
|
+
return {}
|
|
85
|
+
ids = list(dict.fromkeys(commitment_ids))[:20]
|
|
86
|
+
with closing(self.store._connect()) as conn:
|
|
87
|
+
rows = conn.execute('SELECT w.*,c.description,c.status FROM commitment_work w '
|
|
88
|
+
'JOIN commitments c ON c.id=w.commitment_id WHERE c.person_id=? AND c.id IN (' +
|
|
89
|
+
','.join('?' for _ in ids) + ')', [contact_id, *ids]).fetchall()
|
|
90
|
+
return {row['commitment_id']: self._view({'id': row['commitment_id'],
|
|
91
|
+
'description': row['description'], 'status': row['status']}, row, self.clock()) for row in rows}
|
apsimo/compat.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Legacy import names resolve to the canonical module, including its state."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import importlib
|
|
5
|
+
import importlib.abc
|
|
6
|
+
import importlib.util
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class _AliasLoader(importlib.abc.Loader):
|
|
11
|
+
def __init__(self, canonical, spec):
|
|
12
|
+
self.canonical = canonical
|
|
13
|
+
self.canonical_spec = spec
|
|
14
|
+
|
|
15
|
+
def create_module(self, spec):
|
|
16
|
+
return importlib.import_module(self.canonical)
|
|
17
|
+
|
|
18
|
+
def exec_module(self, module):
|
|
19
|
+
# module_from_spec assigns the alias spec to the returned module.
|
|
20
|
+
# Restore its real identity rather than executing its source again.
|
|
21
|
+
module.__spec__ = self.canonical_spec
|
|
22
|
+
module.__loader__ = self.canonical_spec.loader
|
|
23
|
+
|
|
24
|
+
def get_code(self, fullname):
|
|
25
|
+
# runpy uses get_code for `python -m legacy_package.entrypoint`.
|
|
26
|
+
return self.canonical_spec.loader.get_code(self.canonical)
|
|
27
|
+
|
|
28
|
+
def is_package(self, fullname):
|
|
29
|
+
return self.canonical_spec.submodule_search_locations is not None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class _AliasFinder(importlib.abc.MetaPathFinder):
|
|
33
|
+
def __init__(self, legacy, canonical):
|
|
34
|
+
self.legacy, self.canonical = legacy, canonical
|
|
35
|
+
|
|
36
|
+
def find_spec(self, fullname, path=None, target=None):
|
|
37
|
+
if not fullname.startswith(self.legacy + '.'):
|
|
38
|
+
return None
|
|
39
|
+
canonical = self.canonical + fullname[len(self.legacy):]
|
|
40
|
+
spec = importlib.util.find_spec(canonical)
|
|
41
|
+
if spec is None:
|
|
42
|
+
return None
|
|
43
|
+
loader = _AliasLoader(canonical, spec)
|
|
44
|
+
return importlib.util.spec_from_loader(fullname, loader,
|
|
45
|
+
origin=spec.origin, is_package=spec.submodule_search_locations is not None)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def register_module_alias(legacy: str, canonical: str) -> None:
|
|
49
|
+
"""Install a lazy alias once, without eagerly importing optional subsystems."""
|
|
50
|
+
if not any(getattr(finder, 'legacy', None) == legacy
|
|
51
|
+
and getattr(finder, 'canonical', None) == canonical for finder in sys.meta_path):
|
|
52
|
+
sys.meta_path.insert(0, _AliasFinder(legacy, canonical))
|
|
53
|
+
sys.modules[legacy] = importlib.import_module(canonical)
|
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
"""Adaptive context compression for Colony.
|
|
2
|
+
|
|
3
|
+
Compresses enriched-context sections to fit within a token budget
|
|
4
|
+
while preserving the most relevant information. Three compression
|
|
5
|
+
modes with increasing aggression:
|
|
6
|
+
|
|
7
|
+
- conservative: drop lowest-priority sections
|
|
8
|
+
- balanced: drop sections + truncate body text
|
|
9
|
+
- aggressive: drop + truncate + summarize with LLM
|
|
10
|
+
|
|
11
|
+
Compression is off by default. Enable via:
|
|
12
|
+
COLONY_COMPRESSION_MODE=conservative|balanced|aggressive
|
|
13
|
+
|
|
14
|
+
Or per-request via the ``compression`` field on EnrichedContextRequest.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import logging
|
|
20
|
+
import math
|
|
21
|
+
import os
|
|
22
|
+
import re
|
|
23
|
+
from dataclasses import dataclass, field
|
|
24
|
+
from enum import Enum
|
|
25
|
+
from typing import Any, Dict, List, Optional, Sequence
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
# Configuration
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
class CompressionMode(str, Enum):
|
|
35
|
+
OFF = "off"
|
|
36
|
+
CONSERVATIVE = "conservative"
|
|
37
|
+
BALANCED = "balanced"
|
|
38
|
+
AGGRESSIVE = "aggressive"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class CompressionConfig:
|
|
43
|
+
mode: CompressionMode = CompressionMode.OFF
|
|
44
|
+
max_tokens: int = 4000
|
|
45
|
+
truncate_ratio: float = 0.6 # Fraction of body to keep when truncating
|
|
46
|
+
min_section_tokens: int = 50 # Don't truncate below this
|
|
47
|
+
relevance_boost: float = 1.5 # Boost multiplier for query-relevant sections
|
|
48
|
+
preserve_ids: List[str] = field(default_factory=lambda: [
|
|
49
|
+
"colony-identity", # Always keep identity
|
|
50
|
+
])
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _default_config() -> CompressionConfig:
|
|
54
|
+
mode_str = os.environ.get("COLONY_COMPRESSION_MODE", "off").lower()
|
|
55
|
+
mode = CompressionMode(mode_str) if mode_str in (m.value for m in CompressionMode) else CompressionMode.OFF
|
|
56
|
+
max_tokens = int(os.environ.get("COLONY_COMPRESSION_MAX_TOKENS", "4000"))
|
|
57
|
+
return CompressionConfig(mode=mode, max_tokens=max_tokens)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# ---------------------------------------------------------------------------
|
|
61
|
+
# Token estimation
|
|
62
|
+
# ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
def estimate_tokens(text: str) -> int:
|
|
65
|
+
"""Rough token count: whitespace-split words + 15% overhead for subwords/punct."""
|
|
66
|
+
if not text:
|
|
67
|
+
return 0
|
|
68
|
+
return max(1, int(len(text.split()) * 1.15))
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
# Relevance scoring
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
def _tokenize(text: str) -> set[str]:
|
|
76
|
+
"""Lowercase, strip punctuation, split into word set."""
|
|
77
|
+
return set(re.findall(r"\w+", text.lower()))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def relevance_score(
|
|
81
|
+
section_body: str,
|
|
82
|
+
query: str,
|
|
83
|
+
priority: int = 50,
|
|
84
|
+
preserve: bool = False,
|
|
85
|
+
) -> float:
|
|
86
|
+
"""Score a section's relevance to the query.
|
|
87
|
+
|
|
88
|
+
Combines keyword overlap with priority weighting.
|
|
89
|
+
Preserved sections get a massive boost.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Float score — higher means more worth keeping.
|
|
93
|
+
"""
|
|
94
|
+
if preserve:
|
|
95
|
+
return 1000.0
|
|
96
|
+
|
|
97
|
+
query_tokens = _tokenize(query)
|
|
98
|
+
if not query_tokens:
|
|
99
|
+
return float(priority)
|
|
100
|
+
|
|
101
|
+
body_tokens = _tokenize(section_body)
|
|
102
|
+
if not body_tokens:
|
|
103
|
+
return float(priority) * 0.5
|
|
104
|
+
|
|
105
|
+
# Jaccard-like overlap
|
|
106
|
+
overlap = len(query_tokens & body_tokens)
|
|
107
|
+
recall = overlap / len(query_tokens)
|
|
108
|
+
precision = overlap / max(len(body_tokens), 1)
|
|
109
|
+
f1 = 2 * recall * precision / (recall + precision) if (recall + precision) > 0 else 0.0
|
|
110
|
+
|
|
111
|
+
# Combine: 60% keyword relevance + 40% priority (normalized 0-1)
|
|
112
|
+
priority_norm = priority / 100.0
|
|
113
|
+
score = 0.6 * f1 + 0.4 * priority_norm
|
|
114
|
+
|
|
115
|
+
return score
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# ---------------------------------------------------------------------------
|
|
119
|
+
# Compression tiers
|
|
120
|
+
# ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
@dataclass
|
|
123
|
+
class SectionInfo:
|
|
124
|
+
"""Internal tracking for a section through compression."""
|
|
125
|
+
id: str
|
|
126
|
+
title: str
|
|
127
|
+
body: str
|
|
128
|
+
priority: int
|
|
129
|
+
tokens: int
|
|
130
|
+
score: float
|
|
131
|
+
preserved: bool = False
|
|
132
|
+
dropped: bool = False
|
|
133
|
+
truncated: bool = False
|
|
134
|
+
original_tokens: int = 0
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def compress_sections(
|
|
138
|
+
sections: List[Dict[str, Any]],
|
|
139
|
+
query: str = "",
|
|
140
|
+
config: Optional[CompressionConfig] = None,
|
|
141
|
+
override_mode: Optional[CompressionMode] = None,
|
|
142
|
+
) -> Dict[str, Any]:
|
|
143
|
+
"""Compress enriched-context sections to fit a token budget.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
sections: List of ContextSection dicts (id, title, body, priority).
|
|
147
|
+
query: The user message / query for relevance scoring.
|
|
148
|
+
config: Compression config (uses env defaults if None).
|
|
149
|
+
override_mode: Per-request mode override from the API.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
Dict with:
|
|
153
|
+
- sections: compressed list of section dicts
|
|
154
|
+
- stats: compression statistics
|
|
155
|
+
- metadata: debug info
|
|
156
|
+
"""
|
|
157
|
+
cfg = config or _default_config()
|
|
158
|
+
mode = override_mode or cfg.mode
|
|
159
|
+
|
|
160
|
+
if mode == CompressionMode.OFF or not sections:
|
|
161
|
+
return {
|
|
162
|
+
"sections": sections,
|
|
163
|
+
"stats": _stats(sections, sections, mode.value),
|
|
164
|
+
"metadata": {"mode": mode.value, "applied": False},
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
# Build section info with scores
|
|
168
|
+
infos: List[SectionInfo] = []
|
|
169
|
+
for s in sections:
|
|
170
|
+
body = s.get("body", "")
|
|
171
|
+
title = s.get("title", "") or ""
|
|
172
|
+
priority = s.get("priority") or 50
|
|
173
|
+
sid = s.get("id", "")
|
|
174
|
+
preserve = sid in cfg.preserve_ids
|
|
175
|
+
tokens = estimate_tokens(f"{title} {body}")
|
|
176
|
+
|
|
177
|
+
infos.append(SectionInfo(
|
|
178
|
+
id=sid,
|
|
179
|
+
title=title,
|
|
180
|
+
body=body,
|
|
181
|
+
priority=priority,
|
|
182
|
+
tokens=tokens,
|
|
183
|
+
original_tokens=tokens,
|
|
184
|
+
score=relevance_score(body, query, priority, preserve),
|
|
185
|
+
preserved=preserve,
|
|
186
|
+
))
|
|
187
|
+
|
|
188
|
+
# Sort by score descending (highest relevance first)
|
|
189
|
+
infos.sort(key=lambda i: i.score, reverse=True)
|
|
190
|
+
|
|
191
|
+
budget = cfg.max_tokens
|
|
192
|
+
|
|
193
|
+
# --- Tier 1: Drop low-relevance sections ---
|
|
194
|
+
if mode in (CompressionMode.CONSERVATIVE, CompressionMode.BALANCED, CompressionMode.AGGRESSIVE):
|
|
195
|
+
infos = _tier1_drop(infos, budget)
|
|
196
|
+
|
|
197
|
+
# --- Tier 2: Truncate body text of surviving sections ---
|
|
198
|
+
if mode in (CompressionMode.BALANCED, CompressionMode.AGGRESSIVE):
|
|
199
|
+
infos = _tier2_truncate(infos, budget, cfg)
|
|
200
|
+
|
|
201
|
+
# --- Tier 3: tighter truncation (aggressive only, sync path) ---
|
|
202
|
+
# LLM summarization is the preferred aggressive tactic — see the async
|
|
203
|
+
# ``compress_sections_with_llm`` wrapper below. When no LLM is
|
|
204
|
+
# available we fall back to this deterministic tight-truncation tier
|
|
205
|
+
# so aggressive mode always produces *some* size reduction.
|
|
206
|
+
if mode == CompressionMode.AGGRESSIVE:
|
|
207
|
+
infos = _tier3_tight_truncate(infos, budget, cfg)
|
|
208
|
+
|
|
209
|
+
# Build result
|
|
210
|
+
result_sections = []
|
|
211
|
+
for info in infos:
|
|
212
|
+
if info.dropped:
|
|
213
|
+
continue
|
|
214
|
+
result_sections.append({
|
|
215
|
+
"id": info.id,
|
|
216
|
+
"title": info.title,
|
|
217
|
+
"body": info.body,
|
|
218
|
+
"priority": info.priority,
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
"sections": result_sections,
|
|
223
|
+
"stats": _stats(sections, result_sections, mode.value),
|
|
224
|
+
"metadata": {
|
|
225
|
+
"mode": mode.value,
|
|
226
|
+
"applied": True,
|
|
227
|
+
"dropped": [i.id for i in infos if i.dropped],
|
|
228
|
+
"truncated": [i.id for i in infos if i.truncated],
|
|
229
|
+
},
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _tier1_drop(infos: List[SectionInfo], budget: int) -> List[SectionInfo]:
|
|
234
|
+
"""Drop lowest-scoring sections until we fit the budget.
|
|
235
|
+
|
|
236
|
+
Only drops sections if there are enough others to potentially
|
|
237
|
+
fit the budget. If we'd have to drop everything, leave them
|
|
238
|
+
all for tier2 truncation instead.
|
|
239
|
+
"""
|
|
240
|
+
kept = [i for i in infos if not i.dropped]
|
|
241
|
+
total = sum(i.tokens for i in kept)
|
|
242
|
+
if total <= budget:
|
|
243
|
+
return infos
|
|
244
|
+
|
|
245
|
+
# Don't drop below 1 section — let truncation handle it
|
|
246
|
+
droppable = [i for i in kept if not i.preserved]
|
|
247
|
+
if len(droppable) >= len(kept):
|
|
248
|
+
# Everything is droppable; only drop if we'd still have >= 1 left
|
|
249
|
+
pass
|
|
250
|
+
|
|
251
|
+
for info in reversed(infos):
|
|
252
|
+
if info.preserved or info.dropped:
|
|
253
|
+
continue
|
|
254
|
+
# Don't drop the last non-preserved section
|
|
255
|
+
remaining = sum(1 for i in infos if not i.dropped and not i.preserved and i is not info)
|
|
256
|
+
if remaining < 1 and not any(i.preserved for i in infos if not i.dropped):
|
|
257
|
+
break
|
|
258
|
+
info.dropped = True
|
|
259
|
+
total -= info.tokens
|
|
260
|
+
if total <= budget:
|
|
261
|
+
break
|
|
262
|
+
|
|
263
|
+
return infos
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _tier2_truncate(infos: List[SectionInfo], budget: int, cfg: CompressionConfig) -> List[SectionInfo]:
|
|
267
|
+
"""Truncate body text of sections still over budget."""
|
|
268
|
+
kept = [i for i in infos if not i.dropped]
|
|
269
|
+
total = sum(i.tokens for i in kept)
|
|
270
|
+
if total <= budget:
|
|
271
|
+
return infos
|
|
272
|
+
|
|
273
|
+
# Calculate how much we need to cut
|
|
274
|
+
overage = total - budget
|
|
275
|
+
# Distribute truncation proportionally, skipping preserved
|
|
276
|
+
truncatable = [i for i in kept if not i.preserved and i.tokens > cfg.min_section_tokens]
|
|
277
|
+
if not truncatable:
|
|
278
|
+
return infos
|
|
279
|
+
|
|
280
|
+
truncatable_tokens = sum(i.tokens for i in truncatable)
|
|
281
|
+
for info in truncatable:
|
|
282
|
+
# Proportional share of overage
|
|
283
|
+
share = overage * (info.tokens / truncatable_tokens)
|
|
284
|
+
target_tokens = max(cfg.min_section_tokens, int(info.tokens - share))
|
|
285
|
+
# Truncate body text to approximate target
|
|
286
|
+
target_chars = int(len(info.body) * (target_tokens / max(info.tokens, 1)))
|
|
287
|
+
info.body = _truncate_text(info.body, target_chars)
|
|
288
|
+
info.tokens = estimate_tokens(f"{info.title} {info.body}")
|
|
289
|
+
info.truncated = True
|
|
290
|
+
|
|
291
|
+
return infos
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _tier3_tight_truncate(infos: List[SectionInfo], budget: int, cfg: CompressionConfig) -> List[SectionInfo]:
|
|
295
|
+
"""Aggressive mode: tighter truncation if still over budget."""
|
|
296
|
+
kept = [i for i in infos if not i.dropped]
|
|
297
|
+
total = sum(i.tokens for i in kept)
|
|
298
|
+
if total <= budget:
|
|
299
|
+
return infos
|
|
300
|
+
|
|
301
|
+
# Cut all truncatable sections to min_section_tokens
|
|
302
|
+
for info in kept:
|
|
303
|
+
if info.preserved or info.tokens <= cfg.min_section_tokens:
|
|
304
|
+
continue
|
|
305
|
+
target_chars = int(len(info.body) * (cfg.min_section_tokens / max(info.tokens, 1)))
|
|
306
|
+
info.body = _truncate_text(info.body, target_chars)
|
|
307
|
+
info.tokens = estimate_tokens(f"{info.title} {info.body}")
|
|
308
|
+
info.truncated = True
|
|
309
|
+
|
|
310
|
+
return infos
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
# ---------------------------------------------------------------------------
|
|
314
|
+
# Helpers
|
|
315
|
+
# ---------------------------------------------------------------------------
|
|
316
|
+
|
|
317
|
+
def _truncate_text(text: str, max_chars: int) -> str:
|
|
318
|
+
"""Truncate text to max_chars, breaking at sentence boundary if possible."""
|
|
319
|
+
if len(text) <= max_chars:
|
|
320
|
+
return text
|
|
321
|
+
|
|
322
|
+
# Try to break at the last sentence ending before max_chars
|
|
323
|
+
truncated = text[:max_chars]
|
|
324
|
+
# Check for sentence endings in reverse order of preference
|
|
325
|
+
for sep in (". ", "! ", "? ", "\n"):
|
|
326
|
+
last = truncated.rfind(sep)
|
|
327
|
+
if last > max_chars * 0.5:
|
|
328
|
+
return truncated[: last + 1].strip() # Keep the period, drop trailing space
|
|
329
|
+
|
|
330
|
+
# Fall back to word boundary
|
|
331
|
+
last_space = truncated.rfind(" ")
|
|
332
|
+
if last_space > max_chars * 0.5:
|
|
333
|
+
return truncated[:last_space].strip() + " ..."
|
|
334
|
+
|
|
335
|
+
return truncated.strip() + " ..."
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _stats(
|
|
339
|
+
original: List[Dict[str, Any]],
|
|
340
|
+
result: List[Dict[str, Any]],
|
|
341
|
+
mode: str,
|
|
342
|
+
) -> Dict[str, Any]:
|
|
343
|
+
"""Compute before/after compression statistics."""
|
|
344
|
+
orig_tokens = sum(estimate_tokens(s.get("body", "")) for s in original)
|
|
345
|
+
result_tokens = sum(estimate_tokens(s.get("body", "")) for s in result)
|
|
346
|
+
ratio = result_tokens / orig_tokens if orig_tokens > 0 else 1.0
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
"mode": mode,
|
|
350
|
+
"original_sections": len(original),
|
|
351
|
+
"result_sections": len(result),
|
|
352
|
+
"original_tokens": orig_tokens,
|
|
353
|
+
"result_tokens": result_tokens,
|
|
354
|
+
"compression_ratio": round(ratio, 2),
|
|
355
|
+
"saved_tokens": orig_tokens - result_tokens,
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
# ---------------------------------------------------------------------------
|
|
360
|
+
# Tier 3: LLM summarization (aggressive mode)
|
|
361
|
+
# ---------------------------------------------------------------------------
|
|
362
|
+
|
|
363
|
+
_SUMMARIZE_SYSTEM_PROMPT = (
|
|
364
|
+
"You compress long context sections for an AI assistant that has a limited "
|
|
365
|
+
"token budget. Rewrite the user's section to preserve every fact, name, "
|
|
366
|
+
"number, and decision — but drop filler, redundancy, and stylistic prose. "
|
|
367
|
+
"Return ONLY the compressed section body; no preamble, no headings, no "
|
|
368
|
+
"commentary about what you changed. Stay under the requested token budget."
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
async def _llm_summarize_body(
|
|
373
|
+
llm_router: Any,
|
|
374
|
+
*,
|
|
375
|
+
title: str,
|
|
376
|
+
body: str,
|
|
377
|
+
target_tokens: int,
|
|
378
|
+
) -> Optional[str]:
|
|
379
|
+
"""Ask the LLM to rewrite ``body`` under ``target_tokens``.
|
|
380
|
+
|
|
381
|
+
Returns the summary on success or ``None`` on any failure — callers fall
|
|
382
|
+
back to the tight-truncated body.
|
|
383
|
+
"""
|
|
384
|
+
if not body or target_tokens <= 0:
|
|
385
|
+
return None
|
|
386
|
+
user_prompt = (
|
|
387
|
+
f"Section title: {title or '(untitled)'}\n"
|
|
388
|
+
f"Target: ~{target_tokens} tokens\n\n"
|
|
389
|
+
f"Section body:\n---\n{body}\n---\n\n"
|
|
390
|
+
f"Compressed body (plain text, no headings):"
|
|
391
|
+
)
|
|
392
|
+
try:
|
|
393
|
+
resp = await llm_router.complete(
|
|
394
|
+
messages=[
|
|
395
|
+
{"role": "system", "content": _SUMMARIZE_SYSTEM_PROMPT},
|
|
396
|
+
{"role": "user", "content": user_prompt},
|
|
397
|
+
],
|
|
398
|
+
context={"task": "context_compression", "max_tokens": max(64, target_tokens * 2)},
|
|
399
|
+
)
|
|
400
|
+
except Exception as exc:
|
|
401
|
+
logger.debug("LLM summarize call failed for '%s': %s", title, exc)
|
|
402
|
+
return None
|
|
403
|
+
|
|
404
|
+
summary = (getattr(resp, "content", "") or "").strip()
|
|
405
|
+
if not summary:
|
|
406
|
+
return None
|
|
407
|
+
# Trim accidental code fences / wrapping.
|
|
408
|
+
summary = re.sub(r"^```[a-zA-Z]*\s*|\s*```$", "", summary).strip()
|
|
409
|
+
return summary or None
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
async def compress_sections_with_llm(
|
|
413
|
+
sections: List[Dict[str, Any]],
|
|
414
|
+
*,
|
|
415
|
+
llm_router: Any,
|
|
416
|
+
query: str = "",
|
|
417
|
+
config: Optional[CompressionConfig] = None,
|
|
418
|
+
override_mode: Optional[CompressionMode] = None,
|
|
419
|
+
) -> Dict[str, Any]:
|
|
420
|
+
"""Async variant of ``compress_sections`` that applies LLM summarization.
|
|
421
|
+
|
|
422
|
+
Runs the full sync pipeline first (tier 1 drop + tier 2 truncate +
|
|
423
|
+
tier 3 tight truncate) and then — if mode is AGGRESSIVE and the LLM
|
|
424
|
+
is reachable — replaces truncated section bodies with LLM-generated
|
|
425
|
+
summaries. Falls back to the sync result on any LLM error so the
|
|
426
|
+
caller always gets a useful response.
|
|
427
|
+
"""
|
|
428
|
+
result = compress_sections(
|
|
429
|
+
sections,
|
|
430
|
+
query=query,
|
|
431
|
+
config=config,
|
|
432
|
+
override_mode=override_mode,
|
|
433
|
+
)
|
|
434
|
+
mode_str = result["metadata"]["mode"]
|
|
435
|
+
if mode_str != CompressionMode.AGGRESSIVE.value or llm_router is None:
|
|
436
|
+
return result
|
|
437
|
+
|
|
438
|
+
cfg = config or _default_config()
|
|
439
|
+
truncated_ids = set(result["metadata"].get("truncated") or [])
|
|
440
|
+
if not truncated_ids:
|
|
441
|
+
return result
|
|
442
|
+
|
|
443
|
+
summarized_ids: List[str] = []
|
|
444
|
+
for section in result["sections"]:
|
|
445
|
+
sid = section.get("id", "")
|
|
446
|
+
if sid not in truncated_ids:
|
|
447
|
+
continue
|
|
448
|
+
body = section.get("body", "") or ""
|
|
449
|
+
target = max(cfg.min_section_tokens, int(estimate_tokens(body)))
|
|
450
|
+
summary = await _llm_summarize_body(
|
|
451
|
+
llm_router,
|
|
452
|
+
title=section.get("title", "") or "",
|
|
453
|
+
body=body,
|
|
454
|
+
target_tokens=target,
|
|
455
|
+
)
|
|
456
|
+
if summary is None:
|
|
457
|
+
continue
|
|
458
|
+
section["body"] = summary
|
|
459
|
+
summarized_ids.append(sid)
|
|
460
|
+
|
|
461
|
+
if summarized_ids:
|
|
462
|
+
result["metadata"]["summarized"] = summarized_ids
|
|
463
|
+
# Recompute stats to reflect the LLM pass.
|
|
464
|
+
originals = list(sections)
|
|
465
|
+
result["stats"] = _stats(originals, result["sections"], mode_str)
|
|
466
|
+
|
|
467
|
+
return result
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Read-only connector framework (cognition item 2, Phase C).
|
|
2
|
+
|
|
3
|
+
Pull-style senses: each connector polls an external source on its own cadence
|
|
4
|
+
and normalizes what it sees into Observations that feed the observation store,
|
|
5
|
+
the world-model populator, and (via the populator's audit hook) belief
|
|
6
|
+
maintenance. Config is env-only; default off; shadow-first per connector.
|
|
7
|
+
Push-style ingress is handled by the host framework's webhook adapter and is
|
|
8
|
+
intentionally not built here.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from apsimo.connectors.base import (
|
|
12
|
+
Connector, ConnectorConfig, EntityHint, Observation,
|
|
13
|
+
)
|
|
14
|
+
from apsimo.connectors.manager import (
|
|
15
|
+
ConnectorManager, connectors_enabled, connectors_mode,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"Connector", "ConnectorConfig", "EntityHint", "Observation",
|
|
20
|
+
"ConnectorManager", "connectors_enabled", "connectors_mode",
|
|
21
|
+
]
|