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,354 @@
|
|
|
1
|
+
"""Goal Inference Pipeline — detect implicit goals from conversation context.
|
|
2
|
+
|
|
3
|
+
Architecture:
|
|
4
|
+
1. Rule-based pass (synchronous, < 5 ms) — keyword/pattern matching
|
|
5
|
+
2. GoalDeduplicator — semantic similarity check against active goals
|
|
6
|
+
|
|
7
|
+
NOTE: an LLM second-pass for low-confidence candidates was envisioned but is
|
|
8
|
+
NOT implemented — there is no hook or override seam in this module. Candidates
|
|
9
|
+
below MIN_CONFIDENCE are simply dropped. (Wire one through the project's Anthropic
|
|
10
|
+
client if low-confidence recall becomes a need; keep the rule-based path's <5 ms
|
|
11
|
+
synchronous guarantee — any LLM call must be out of band.)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import re
|
|
17
|
+
import time
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from enum import Enum
|
|
20
|
+
from typing import Dict, List, Optional, Tuple
|
|
21
|
+
|
|
22
|
+
from apsimo.goals.models import Goal, GoalPriority, GoalSource
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class IntentSignal(str, Enum):
|
|
26
|
+
"""Conversation signal types that may indicate a goal."""
|
|
27
|
+
DESIRE = "desire" # "I want to / I need to / I'd like to"
|
|
28
|
+
OBLIGATION = "obligation" # "I have to / I must / I should"
|
|
29
|
+
PROBLEM = "problem" # "I'm struggling with / This is broken"
|
|
30
|
+
DEADLINE = "deadline" # Explicit time reference with task context
|
|
31
|
+
FRUSTRATION = "frustration" # Repeated problem mentions, negative sentiment
|
|
32
|
+
RECURRING = "recurring" # "Every week / daily / each morning"
|
|
33
|
+
DELEGATION = "delegation" # "Can you / Could you / Please"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class ConversationMessage:
|
|
38
|
+
"""A single message in a conversation."""
|
|
39
|
+
message_id: str
|
|
40
|
+
role: str # "user" | "assistant" | "system"
|
|
41
|
+
content: str
|
|
42
|
+
timestamp: Optional[float] = None # Unix epoch
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class InferenceCandidate:
|
|
47
|
+
"""A candidate goal extracted from conversation context.
|
|
48
|
+
|
|
49
|
+
Attributes:
|
|
50
|
+
title: Inferred goal title.
|
|
51
|
+
description: Full description including supporting context.
|
|
52
|
+
signals: Which signal types contributed to this inference.
|
|
53
|
+
confidence: 0.0–1.0 confidence that this is a real goal.
|
|
54
|
+
source_messages: IDs of conversation messages that triggered inference.
|
|
55
|
+
suggested_deadline: Extracted deadline if any.
|
|
56
|
+
priority: Inferred priority based on signal type.
|
|
57
|
+
"""
|
|
58
|
+
title: str
|
|
59
|
+
description: str
|
|
60
|
+
signals: List[IntentSignal]
|
|
61
|
+
confidence: float
|
|
62
|
+
source_messages: List[str]
|
|
63
|
+
suggested_deadline: Optional[str] = None # ISO-8601 if detected
|
|
64
|
+
priority: GoalPriority = GoalPriority.NORMAL
|
|
65
|
+
|
|
66
|
+
def should_auto_accept(self, threshold: float = 0.85) -> bool:
|
|
67
|
+
"""Return True if confidence exceeds auto-accept threshold."""
|
|
68
|
+
return (
|
|
69
|
+
self.confidence >= threshold
|
|
70
|
+
and IntentSignal.DELEGATION in self.signals
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass
|
|
75
|
+
class GoalSimilarity:
|
|
76
|
+
"""Result of comparing two goals for deduplication."""
|
|
77
|
+
goal_id_a: str
|
|
78
|
+
goal_id_b: str
|
|
79
|
+
similarity_score: float # 0.0–1.0
|
|
80
|
+
shared_keywords: List[str]
|
|
81
|
+
recommendation: str # "duplicate" | "merge" | "related" | "distinct"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# ── Rule-based signal patterns ────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
_DESIRE_PATTERNS = re.compile(
|
|
87
|
+
r"\b(i want to|i need to|i'd like to|i would like to|i wish|i hope to|"
|
|
88
|
+
r"need to|want to|gotta|going to|planning to)\b",
|
|
89
|
+
re.IGNORECASE,
|
|
90
|
+
)
|
|
91
|
+
_OBLIGATION_PATTERNS = re.compile(
|
|
92
|
+
r"\b(i have to|i must|i should|i ought to|have to|must|should|"
|
|
93
|
+
r"need to finish|deadline|due by|due on)\b",
|
|
94
|
+
re.IGNORECASE,
|
|
95
|
+
)
|
|
96
|
+
_PROBLEM_PATTERNS = re.compile(
|
|
97
|
+
r"\b(struggling with|broken|not working|failing|error|bug|issue|problem|"
|
|
98
|
+
r"can't figure out|don't know how|stuck on|frustrated with|having trouble)\b",
|
|
99
|
+
re.IGNORECASE,
|
|
100
|
+
)
|
|
101
|
+
_DEADLINE_PATTERNS = re.compile(
|
|
102
|
+
r"\b(by (monday|tuesday|wednesday|thursday|friday|saturday|sunday)|"
|
|
103
|
+
r"by (tomorrow|tonight|end of (day|week|month))|"
|
|
104
|
+
r"before (the meeting|the call|the deadline)|"
|
|
105
|
+
r"(this|next) (monday|tuesday|wednesday|thursday|friday|week|month)|"
|
|
106
|
+
r"in (\d+) (hours?|days?|weeks?))\b",
|
|
107
|
+
re.IGNORECASE,
|
|
108
|
+
)
|
|
109
|
+
_RECURRING_PATTERNS = re.compile(
|
|
110
|
+
r"\b(every (day|week|month|morning|evening|monday|tuesday|wednesday|"
|
|
111
|
+
r"thursday|friday)|daily|weekly|monthly|each (morning|day|week))\b",
|
|
112
|
+
re.IGNORECASE,
|
|
113
|
+
)
|
|
114
|
+
_DELEGATION_PATTERNS = re.compile(
|
|
115
|
+
r"\b(can you|could you|please|would you|help me|remind me|schedule|"
|
|
116
|
+
r"set up|create|make|send|write|draft|prepare|organize)\b",
|
|
117
|
+
re.IGNORECASE,
|
|
118
|
+
)
|
|
119
|
+
_FRUSTRATION_PATTERNS = re.compile(
|
|
120
|
+
r"\b(frustrated|annoying|ridiculous|terrible|awful|keeps (breaking|failing)|"
|
|
121
|
+
r"still broken|why doesn'?t|why isn'?t|never works|always fails)\b",
|
|
122
|
+
re.IGNORECASE,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
_PRIORITY_SIGNALS = {
|
|
126
|
+
IntentSignal.OBLIGATION: GoalPriority.HIGH,
|
|
127
|
+
IntentSignal.DEADLINE: GoalPriority.HIGH,
|
|
128
|
+
IntentSignal.FRUSTRATION: GoalPriority.HIGH,
|
|
129
|
+
IntentSignal.DELEGATION: GoalPriority.NORMAL,
|
|
130
|
+
IntentSignal.DESIRE: GoalPriority.NORMAL,
|
|
131
|
+
IntentSignal.PROBLEM: GoalPriority.NORMAL,
|
|
132
|
+
IntentSignal.RECURRING: GoalPriority.LOW,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _extract_signals(text: str) -> List[Tuple[IntentSignal, float]]:
|
|
137
|
+
"""Extract signals from text, returning (signal, weight) pairs."""
|
|
138
|
+
results: List[Tuple[IntentSignal, float]] = []
|
|
139
|
+
if _DESIRE_PATTERNS.search(text):
|
|
140
|
+
results.append((IntentSignal.DESIRE, 0.6))
|
|
141
|
+
if _OBLIGATION_PATTERNS.search(text):
|
|
142
|
+
results.append((IntentSignal.OBLIGATION, 0.7))
|
|
143
|
+
if _PROBLEM_PATTERNS.search(text):
|
|
144
|
+
results.append((IntentSignal.PROBLEM, 0.55))
|
|
145
|
+
if _DEADLINE_PATTERNS.search(text):
|
|
146
|
+
results.append((IntentSignal.DEADLINE, 0.65))
|
|
147
|
+
if _RECURRING_PATTERNS.search(text):
|
|
148
|
+
results.append((IntentSignal.RECURRING, 0.5))
|
|
149
|
+
if _DELEGATION_PATTERNS.search(text):
|
|
150
|
+
results.append((IntentSignal.DELEGATION, 0.75))
|
|
151
|
+
if _FRUSTRATION_PATTERNS.search(text):
|
|
152
|
+
results.append((IntentSignal.FRUSTRATION, 0.6))
|
|
153
|
+
return results
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _compute_confidence(signals: List[Tuple[IntentSignal, float]]) -> float:
|
|
157
|
+
"""Combine multiple signal weights into a single confidence score."""
|
|
158
|
+
if not signals:
|
|
159
|
+
return 0.0
|
|
160
|
+
# Combine with diminishing returns: 1 - product(1 - w)
|
|
161
|
+
combined = 1.0
|
|
162
|
+
for _, w in signals:
|
|
163
|
+
combined *= (1.0 - w)
|
|
164
|
+
return round(1.0 - combined, 4)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _infer_priority(signals: List[IntentSignal]) -> GoalPriority:
|
|
168
|
+
"""Select highest priority implied by the signal set."""
|
|
169
|
+
best = GoalPriority.BACKGROUND
|
|
170
|
+
for sig in signals:
|
|
171
|
+
p = _PRIORITY_SIGNALS.get(sig, GoalPriority.NORMAL)
|
|
172
|
+
if p.value > best.value:
|
|
173
|
+
best = p
|
|
174
|
+
return best
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _extract_title(text: str) -> str:
|
|
178
|
+
"""Produce a short title from the message text (max 80 chars)."""
|
|
179
|
+
# Strip leading/trailing whitespace and trim to 80 chars
|
|
180
|
+
cleaned = re.sub(r"\s+", " ", text.strip())
|
|
181
|
+
# Remove common filler phrases
|
|
182
|
+
cleaned = re.sub(
|
|
183
|
+
r"^(can you|could you|please|i need to|i want to|i have to|"
|
|
184
|
+
r"i should|i must|help me)\s+",
|
|
185
|
+
"",
|
|
186
|
+
cleaned,
|
|
187
|
+
flags=re.IGNORECASE,
|
|
188
|
+
).strip()
|
|
189
|
+
# Capitalize
|
|
190
|
+
if cleaned:
|
|
191
|
+
cleaned = cleaned[0].upper() + cleaned[1:]
|
|
192
|
+
return cleaned[:80].rstrip(".,;:")
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class GoalInferencePipeline:
|
|
196
|
+
"""Detect implicit goals from conversation messages.
|
|
197
|
+
|
|
198
|
+
The rule-based pass runs synchronously on every user message. Candidates
|
|
199
|
+
below ``MIN_CONFIDENCE`` are dropped; there is no LLM second-pass wired in
|
|
200
|
+
(see module docstring).
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
MIN_CONFIDENCE = 0.35 # Below this, no candidate is produced
|
|
204
|
+
|
|
205
|
+
def __init__(self, min_confidence: float = MIN_CONFIDENCE) -> None:
|
|
206
|
+
self.min_confidence = min_confidence
|
|
207
|
+
|
|
208
|
+
def process_message(
|
|
209
|
+
self,
|
|
210
|
+
message: ConversationMessage,
|
|
211
|
+
history: Optional[List[ConversationMessage]] = None,
|
|
212
|
+
) -> Optional[InferenceCandidate]:
|
|
213
|
+
"""Run the rule-based inference pass on a single message.
|
|
214
|
+
|
|
215
|
+
Returns an InferenceCandidate if a goal-like signal is detected,
|
|
216
|
+
otherwise None.
|
|
217
|
+
|
|
218
|
+
Must complete in < 5 ms for typical inputs.
|
|
219
|
+
"""
|
|
220
|
+
if message.role != "user":
|
|
221
|
+
return None
|
|
222
|
+
|
|
223
|
+
text = message.content
|
|
224
|
+
signals_weighted = _extract_signals(text)
|
|
225
|
+
if not signals_weighted:
|
|
226
|
+
return None
|
|
227
|
+
|
|
228
|
+
confidence = _compute_confidence(signals_weighted)
|
|
229
|
+
if confidence < self.min_confidence:
|
|
230
|
+
return None
|
|
231
|
+
|
|
232
|
+
signals = [s for s, _ in signals_weighted]
|
|
233
|
+
priority = _infer_priority(signals)
|
|
234
|
+
title = _extract_title(text)
|
|
235
|
+
if not title:
|
|
236
|
+
return None
|
|
237
|
+
|
|
238
|
+
# Extract deadline hint
|
|
239
|
+
deadline_match = _DEADLINE_PATTERNS.search(text)
|
|
240
|
+
suggested_deadline = deadline_match.group(0) if deadline_match else None
|
|
241
|
+
|
|
242
|
+
return InferenceCandidate(
|
|
243
|
+
title=title,
|
|
244
|
+
description=text.strip(),
|
|
245
|
+
signals=signals,
|
|
246
|
+
confidence=confidence,
|
|
247
|
+
source_messages=[message.message_id],
|
|
248
|
+
suggested_deadline=suggested_deadline,
|
|
249
|
+
priority=priority,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
def process_history(
|
|
253
|
+
self,
|
|
254
|
+
history: List[ConversationMessage],
|
|
255
|
+
) -> List[InferenceCandidate]:
|
|
256
|
+
"""Process a conversation history, returning all detected candidates."""
|
|
257
|
+
candidates = []
|
|
258
|
+
for msg in history:
|
|
259
|
+
cand = self.process_message(msg, history=history)
|
|
260
|
+
if cand:
|
|
261
|
+
candidates.append(cand)
|
|
262
|
+
return candidates
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
# ── Goal Deduplication ────────────────────────────────────────────────────────
|
|
266
|
+
|
|
267
|
+
def _tokenize(text: str) -> set:
|
|
268
|
+
"""Simple word-level tokenizer for keyword overlap scoring."""
|
|
269
|
+
# Remove stop words and short tokens
|
|
270
|
+
stop_words = {
|
|
271
|
+
"i", "a", "an", "the", "to", "is", "are", "was", "were",
|
|
272
|
+
"be", "been", "being", "have", "has", "had", "do", "does",
|
|
273
|
+
"did", "will", "would", "could", "should", "may", "might",
|
|
274
|
+
"shall", "can", "need", "want", "going", "get", "got",
|
|
275
|
+
"my", "me", "we", "us", "it", "its", "this", "that",
|
|
276
|
+
"and", "or", "but", "if", "in", "on", "at", "by", "for",
|
|
277
|
+
"with", "about", "as", "of", "from",
|
|
278
|
+
}
|
|
279
|
+
words = re.findall(r"[a-z]+", text.lower())
|
|
280
|
+
return {w for w in words if w not in stop_words and len(w) > 2}
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _jaccard_similarity(a: set, b: set) -> float:
|
|
284
|
+
if not a or not b:
|
|
285
|
+
return 0.0
|
|
286
|
+
return len(a & b) / len(a | b)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class GoalDeduplicator:
|
|
290
|
+
"""Check and merge candidate goals against the active goal set."""
|
|
291
|
+
|
|
292
|
+
DUPLICATE_THRESHOLD = 0.90
|
|
293
|
+
MERGE_THRESHOLD = 0.70
|
|
294
|
+
|
|
295
|
+
def check(
|
|
296
|
+
self,
|
|
297
|
+
candidate: InferenceCandidate,
|
|
298
|
+
existing: List[Goal],
|
|
299
|
+
) -> Optional[GoalSimilarity]:
|
|
300
|
+
"""
|
|
301
|
+
Return a GoalSimilarity if the candidate overlaps with an existing goal.
|
|
302
|
+
Returns None if the candidate is distinct from all existing goals.
|
|
303
|
+
"""
|
|
304
|
+
if not existing:
|
|
305
|
+
return None
|
|
306
|
+
|
|
307
|
+
candidate_tokens = _tokenize(candidate.title + " " + candidate.description)
|
|
308
|
+
best: Optional[GoalSimilarity] = None
|
|
309
|
+
|
|
310
|
+
for goal in existing:
|
|
311
|
+
if goal.is_terminal():
|
|
312
|
+
continue
|
|
313
|
+
goal_tokens = _tokenize(goal.title + " " + goal.description)
|
|
314
|
+
score = _jaccard_similarity(candidate_tokens, goal_tokens)
|
|
315
|
+
shared = sorted(candidate_tokens & goal_tokens)
|
|
316
|
+
|
|
317
|
+
if score >= self.MERGE_THRESHOLD:
|
|
318
|
+
if score >= self.DUPLICATE_THRESHOLD:
|
|
319
|
+
rec = "duplicate"
|
|
320
|
+
else:
|
|
321
|
+
rec = "merge"
|
|
322
|
+
|
|
323
|
+
sim = GoalSimilarity(
|
|
324
|
+
goal_id_a="candidate",
|
|
325
|
+
goal_id_b=goal.goal_id,
|
|
326
|
+
similarity_score=score,
|
|
327
|
+
shared_keywords=shared[:10],
|
|
328
|
+
recommendation=rec,
|
|
329
|
+
)
|
|
330
|
+
if best is None or score > best.similarity_score:
|
|
331
|
+
best = sim
|
|
332
|
+
|
|
333
|
+
return best
|
|
334
|
+
|
|
335
|
+
def merge(self, base: Goal, candidate: InferenceCandidate) -> Goal:
|
|
336
|
+
"""Enrich an existing goal with additional context from a new candidate."""
|
|
337
|
+
# Extend description if the candidate has more detail
|
|
338
|
+
if len(candidate.description) > len(base.description):
|
|
339
|
+
base.description = candidate.description
|
|
340
|
+
|
|
341
|
+
# Adopt deadline if base has none
|
|
342
|
+
if candidate.suggested_deadline and base.deadline is None:
|
|
343
|
+
base.context["inferred_deadline_hint"] = candidate.suggested_deadline
|
|
344
|
+
|
|
345
|
+
# Upgrade priority if candidate is higher
|
|
346
|
+
if candidate.priority.value > base.priority.value:
|
|
347
|
+
base.priority = candidate.priority
|
|
348
|
+
|
|
349
|
+
# Merge source message IDs into context
|
|
350
|
+
existing_msgs = base.context.get("source_messages", [])
|
|
351
|
+
combined = list(set(existing_msgs + candidate.source_messages))
|
|
352
|
+
base.context["source_messages"] = combined
|
|
353
|
+
|
|
354
|
+
return base
|
apsimo/goals/models.py
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"""Colony Goal Engine — core data models.
|
|
2
|
+
|
|
3
|
+
Defines Goal, Subtask, GoalDAG and supporting types for the
|
|
4
|
+
Colony DAG-based goal decomposition and lifecycle management system.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import uuid
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
from enum import Enum
|
|
13
|
+
from typing import Any, Dict, List, Optional
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class GoalStatus(str, Enum):
|
|
17
|
+
"""Goal lifecycle states."""
|
|
18
|
+
PROPOSED = "proposed" # Inferred or suggested; awaiting user acceptance
|
|
19
|
+
ACCEPTED = "accepted" # User confirmed; ready for decomposition
|
|
20
|
+
ACTIVE = "active" # Decomposed and executing
|
|
21
|
+
BLOCKED = "blocked" # Cannot proceed; waiting on external factor
|
|
22
|
+
COMPLETED = "completed" # All subtasks done; outcome achieved
|
|
23
|
+
ABANDONED = "abandoned" # Given up; reason recorded
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class GoalSource(str, Enum):
|
|
27
|
+
"""How the goal was created."""
|
|
28
|
+
EXPLICIT = "explicit" # User stated the goal directly
|
|
29
|
+
INFERRED = "inferred" # Detected from conversation context
|
|
30
|
+
RECURRING = "recurring" # Generated from a recurring schedule
|
|
31
|
+
DELEGATED = "delegated" # Sent by another Colony agent via federation
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class GoalPriority(int, Enum):
|
|
35
|
+
"""Goal scheduling priority. Higher = more urgent."""
|
|
36
|
+
BACKGROUND = 0
|
|
37
|
+
LOW = 10
|
|
38
|
+
NORMAL = 50
|
|
39
|
+
HIGH = 80
|
|
40
|
+
CRITICAL = 100
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class SubtaskStatus(str, Enum):
|
|
44
|
+
"""Subtask execution states within a goal's DAG."""
|
|
45
|
+
PENDING = "pending" # Not yet dispatched
|
|
46
|
+
DISPATCHED = "dispatched" # Job posted to task queue
|
|
47
|
+
RUNNING = "running" # Job claimed by a worker
|
|
48
|
+
COMPLETED = "completed" # Job finished successfully
|
|
49
|
+
FAILED = "failed" # Job failed; may trigger replan
|
|
50
|
+
SKIPPED = "skipped" # Bypassed via replan strategy
|
|
51
|
+
BLOCKED = "blocked" # Waiting on dependency subtasks
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class GoalOutcome:
|
|
56
|
+
"""Describes the desired end state of a completed goal."""
|
|
57
|
+
description: str
|
|
58
|
+
success_criteria: List[str] = field(default_factory=list)
|
|
59
|
+
measurable: bool = False
|
|
60
|
+
target_value: Optional[float] = None
|
|
61
|
+
target_unit: Optional[str] = None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass
|
|
65
|
+
class Goal:
|
|
66
|
+
"""A user-level objective tracked by the Goal Engine.
|
|
67
|
+
|
|
68
|
+
Attributes:
|
|
69
|
+
goal_id: Globally unique identifier.
|
|
70
|
+
title: Short human-readable label (≤ 80 chars).
|
|
71
|
+
description: Full description of the objective.
|
|
72
|
+
source: How this goal was created.
|
|
73
|
+
status: Current lifecycle state.
|
|
74
|
+
priority: Scheduling priority score.
|
|
75
|
+
outcome: Desired end state description.
|
|
76
|
+
deadline: Optional absolute deadline.
|
|
77
|
+
parent_goal_id: If this is a sub-goal, the parent's ID.
|
|
78
|
+
tags: Arbitrary metadata for filtering and grouping.
|
|
79
|
+
context: Conversation or event context that generated this goal.
|
|
80
|
+
created_at: When the goal was created.
|
|
81
|
+
updated_at: When the goal was last modified.
|
|
82
|
+
accepted_at: When the user accepted the goal.
|
|
83
|
+
completed_at: When the goal reached COMPLETED.
|
|
84
|
+
abandoned_at: When the goal was abandoned.
|
|
85
|
+
abandon_reason: Why the goal was abandoned.
|
|
86
|
+
replan_count: Number of times the DAG has been replanned.
|
|
87
|
+
estimated_hours: Current completion time estimate in hours.
|
|
88
|
+
progress_pct: Fraction of subtasks completed (0.0–1.0).
|
|
89
|
+
"""
|
|
90
|
+
goal_id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
|
91
|
+
title: str = ""
|
|
92
|
+
description: str = ""
|
|
93
|
+
source: GoalSource = GoalSource.EXPLICIT
|
|
94
|
+
status: GoalStatus = GoalStatus.PROPOSED
|
|
95
|
+
priority: GoalPriority = GoalPriority.NORMAL
|
|
96
|
+
outcome: Optional[GoalOutcome] = None
|
|
97
|
+
deadline: Optional[datetime] = None
|
|
98
|
+
parent_goal_id: Optional[str] = None
|
|
99
|
+
tags: Dict[str, str] = field(default_factory=dict)
|
|
100
|
+
context: Dict[str, Any] = field(default_factory=dict)
|
|
101
|
+
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
102
|
+
updated_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
103
|
+
accepted_at: Optional[datetime] = None
|
|
104
|
+
completed_at: Optional[datetime] = None
|
|
105
|
+
abandoned_at: Optional[datetime] = None
|
|
106
|
+
abandon_reason: Optional[str] = None
|
|
107
|
+
replan_count: int = 0
|
|
108
|
+
estimated_hours: Optional[float] = None
|
|
109
|
+
progress_pct: float = 0.0
|
|
110
|
+
|
|
111
|
+
# Initiative management fields (v0.7.10)
|
|
112
|
+
last_initiative_at: Optional[datetime] = None
|
|
113
|
+
snoozed_until: Optional[datetime] = None
|
|
114
|
+
snooze_count: int = 0
|
|
115
|
+
dismissal_reason: Optional[str] = None
|
|
116
|
+
|
|
117
|
+
def is_terminal(self) -> bool:
|
|
118
|
+
# Compare by string value so the check is robust if GoalStatus is patched
|
|
119
|
+
# in a test environment (e.g. pytest-xdist workers that share sys.modules).
|
|
120
|
+
status_val = self.status.value if hasattr(self.status, "value") else str(self.status)
|
|
121
|
+
return status_val in ("completed", "abandoned")
|
|
122
|
+
|
|
123
|
+
def is_overdue(self) -> bool:
|
|
124
|
+
if self.deadline is None:
|
|
125
|
+
return False
|
|
126
|
+
return datetime.now(timezone.utc) > self.deadline
|
|
127
|
+
|
|
128
|
+
def age_hours(self) -> float:
|
|
129
|
+
return (datetime.now(timezone.utc) - self.created_at).total_seconds() / 3600.0
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@dataclass
|
|
133
|
+
class Subtask:
|
|
134
|
+
"""A concrete unit of work within a goal's DAG.
|
|
135
|
+
|
|
136
|
+
Attributes:
|
|
137
|
+
subtask_id: Unique identifier within the goal.
|
|
138
|
+
goal_id: Parent goal.
|
|
139
|
+
title: Human-readable description.
|
|
140
|
+
job_type: Task queue job type for dispatch.
|
|
141
|
+
payload: Job-specific parameters.
|
|
142
|
+
capabilities: Required worker capabilities.
|
|
143
|
+
depends_on: List of subtask_ids that must complete first.
|
|
144
|
+
status: Current execution state.
|
|
145
|
+
job_id: Task queue job_id if dispatched.
|
|
146
|
+
result: Job result if completed.
|
|
147
|
+
depth: DAG depth level (root = 0).
|
|
148
|
+
is_critical_path: True if this subtask is on the critical path.
|
|
149
|
+
retry_count: How many times this subtask has been retried.
|
|
150
|
+
max_retries: Maximum retries before escalating to replan.
|
|
151
|
+
estimated_hours: Expected execution duration.
|
|
152
|
+
started_at: When the job started executing.
|
|
153
|
+
completed_at: When the job completed.
|
|
154
|
+
error: Error message if failed.
|
|
155
|
+
"""
|
|
156
|
+
subtask_id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
|
157
|
+
goal_id: str = ""
|
|
158
|
+
title: str = ""
|
|
159
|
+
job_type: str = "custom"
|
|
160
|
+
payload: Dict[str, Any] = field(default_factory=dict)
|
|
161
|
+
capabilities: List[str] = field(default_factory=list)
|
|
162
|
+
depends_on: List[str] = field(default_factory=list)
|
|
163
|
+
status: SubtaskStatus = SubtaskStatus.PENDING
|
|
164
|
+
job_id: Optional[str] = None
|
|
165
|
+
result: Optional[Dict[str, Any]] = None
|
|
166
|
+
depth: int = 0
|
|
167
|
+
is_critical_path: bool = False
|
|
168
|
+
retry_count: int = 0
|
|
169
|
+
max_retries: int = 2
|
|
170
|
+
estimated_hours: Optional[float] = None
|
|
171
|
+
started_at: Optional[datetime] = None
|
|
172
|
+
completed_at: Optional[datetime] = None
|
|
173
|
+
error: Optional[str] = None
|
|
174
|
+
|
|
175
|
+
def is_ready(self, completed_ids: set) -> bool:
|
|
176
|
+
"""Return True when all dependencies have completed."""
|
|
177
|
+
return all(dep in completed_ids for dep in self.depends_on)
|
|
178
|
+
|
|
179
|
+
def duration_hours(self) -> Optional[float]:
|
|
180
|
+
if self.started_at and self.completed_at:
|
|
181
|
+
return (self.completed_at - self.started_at).total_seconds() / 3600.0
|
|
182
|
+
return None
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@dataclass
|
|
186
|
+
class GoalDAG:
|
|
187
|
+
"""The directed acyclic graph of subtasks for a single goal.
|
|
188
|
+
|
|
189
|
+
Attributes:
|
|
190
|
+
goal_id: Parent goal identifier.
|
|
191
|
+
subtasks: All subtask nodes keyed by subtask_id.
|
|
192
|
+
root_ids: Subtask IDs with no dependencies (entry points).
|
|
193
|
+
leaf_ids: Subtask IDs with no dependents (exit points).
|
|
194
|
+
critical_path: Ordered list of subtask_ids on the critical path.
|
|
195
|
+
max_depth: Deepest level in the DAG.
|
|
196
|
+
created_at: When this DAG version was created.
|
|
197
|
+
version: Incremented on each replan.
|
|
198
|
+
"""
|
|
199
|
+
goal_id: str
|
|
200
|
+
subtasks: Dict[str, Subtask] = field(default_factory=dict)
|
|
201
|
+
root_ids: List[str] = field(default_factory=list)
|
|
202
|
+
leaf_ids: List[str] = field(default_factory=list)
|
|
203
|
+
critical_path: List[str] = field(default_factory=list)
|
|
204
|
+
max_depth: int = 0
|
|
205
|
+
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
206
|
+
version: int = 1
|
|
207
|
+
|
|
208
|
+
def add_subtask(self, subtask: Subtask) -> None:
|
|
209
|
+
self.subtasks[subtask.subtask_id] = subtask
|
|
210
|
+
|
|
211
|
+
def ready_subtasks(self) -> List[Subtask]:
|
|
212
|
+
"""Return all PENDING subtasks whose dependencies are met."""
|
|
213
|
+
completed = {
|
|
214
|
+
sid for sid, s in self.subtasks.items()
|
|
215
|
+
if s.status == SubtaskStatus.COMPLETED
|
|
216
|
+
}
|
|
217
|
+
return [
|
|
218
|
+
s for s in self.subtasks.values()
|
|
219
|
+
if s.status == SubtaskStatus.PENDING and s.is_ready(completed)
|
|
220
|
+
]
|
|
221
|
+
|
|
222
|
+
def is_complete(self) -> bool:
|
|
223
|
+
return all(
|
|
224
|
+
s.status in {SubtaskStatus.COMPLETED, SubtaskStatus.SKIPPED}
|
|
225
|
+
for s in self.subtasks.values()
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
def completion_fraction(self) -> float:
|
|
229
|
+
if not self.subtasks:
|
|
230
|
+
return 0.0
|
|
231
|
+
done = sum(
|
|
232
|
+
1 for s in self.subtasks.values()
|
|
233
|
+
if s.status in {SubtaskStatus.COMPLETED, SubtaskStatus.SKIPPED}
|
|
234
|
+
)
|
|
235
|
+
return done / len(self.subtasks)
|
|
236
|
+
|
|
237
|
+
def validate(self) -> List[str]:
|
|
238
|
+
"""Validate DAG structure. Returns list of error messages."""
|
|
239
|
+
errors: List[str] = []
|
|
240
|
+
ids = set(self.subtasks.keys())
|
|
241
|
+
|
|
242
|
+
# All dependencies must reference known subtasks
|
|
243
|
+
for s in self.subtasks.values():
|
|
244
|
+
for dep in s.depends_on:
|
|
245
|
+
if dep not in ids:
|
|
246
|
+
errors.append(f"Subtask {s.subtask_id} has unknown dep {dep}")
|
|
247
|
+
# No self-dependency
|
|
248
|
+
if s.subtask_id in s.depends_on:
|
|
249
|
+
errors.append(f"Subtask {s.subtask_id} depends on itself")
|
|
250
|
+
|
|
251
|
+
# No cycles (DFS-based check)
|
|
252
|
+
visited: set = set()
|
|
253
|
+
rec_stack: set = set()
|
|
254
|
+
|
|
255
|
+
def has_cycle(node_id: str) -> bool:
|
|
256
|
+
visited.add(node_id)
|
|
257
|
+
rec_stack.add(node_id)
|
|
258
|
+
node = self.subtasks.get(node_id)
|
|
259
|
+
deps = node.depends_on if node else []
|
|
260
|
+
for dep in deps:
|
|
261
|
+
if dep not in visited:
|
|
262
|
+
if has_cycle(dep):
|
|
263
|
+
return True
|
|
264
|
+
elif dep in rec_stack:
|
|
265
|
+
return True
|
|
266
|
+
rec_stack.discard(node_id)
|
|
267
|
+
return False
|
|
268
|
+
|
|
269
|
+
for sid in ids:
|
|
270
|
+
if sid not in visited:
|
|
271
|
+
if has_cycle(sid):
|
|
272
|
+
errors.append(f"Cycle detected involving subtask {sid}")
|
|
273
|
+
break
|
|
274
|
+
|
|
275
|
+
return errors
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
@dataclass
|
|
279
|
+
class GoalTransitionRecord:
|
|
280
|
+
"""An audit record for a goal state transition."""
|
|
281
|
+
goal_id: str
|
|
282
|
+
from_status: str
|
|
283
|
+
to_status: str
|
|
284
|
+
trigger: str
|
|
285
|
+
created_at: datetime
|
|
286
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@dataclass
|
|
290
|
+
class GoalSummary:
|
|
291
|
+
"""Human-readable summary for the briefing system."""
|
|
292
|
+
goal_id: str
|
|
293
|
+
title: str
|
|
294
|
+
status: str
|
|
295
|
+
priority: int
|
|
296
|
+
progress_pct: float
|
|
297
|
+
estimated_hours: Optional[float]
|
|
298
|
+
deadline: Optional[datetime]
|
|
299
|
+
subtask_count: int
|
|
300
|
+
completed_subtasks: int
|
|
301
|
+
is_overdue: bool
|
|
302
|
+
replan_count: int
|