apsimo 1.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- apsimo/__init__.py +38 -0
- apsimo/__main__.py +6 -0
- apsimo/agent/__init__.py +6 -0
- apsimo/agent/client.py +276 -0
- apsimo/agent/models.py +46 -0
- apsimo/agents/__init__.py +20 -0
- apsimo/agents/models.py +264 -0
- apsimo/agents/store.py +861 -0
- apsimo/agents/websocket.py +522 -0
- apsimo/api/__init__.py +1 -0
- apsimo/api/auth_telemetry.py +287 -0
- apsimo/api/authority.py +1203 -0
- apsimo/api/contact_grants.py +347 -0
- apsimo/api/middleware.py +483 -0
- apsimo/api/routers/__init__.py +1 -0
- apsimo/api/routers/commitment_work.py +265 -0
- apsimo/api/routers/context_gate.py +123 -0
- apsimo/api/routers/executions.py +140 -0
- apsimo/api/routers/followup_plans.py +147 -0
- apsimo/api/routers/governed_actions.py +162 -0
- apsimo/api/routers/host.py +14473 -0
- apsimo/api/routers/initiative_work.py +115 -0
- apsimo/api/routers/mining.py +104 -0
- apsimo/api/routers/observations.py +110 -0
- apsimo/api/routers/social_state.py +225 -0
- apsimo/api/routers/task_queue.py +2715 -0
- apsimo/api/routers/temporal_followups.py +251 -0
- apsimo/api/routers/transport.py +110 -0
- apsimo/api/routers/transport_ingress_api.py +240 -0
- apsimo/api/schemas/__init__.py +1 -0
- apsimo/api/schemas/host.py +1949 -0
- apsimo/autonomy/cli.py +110 -0
- apsimo/autonomy/condition_worker.py +437 -0
- apsimo/autonomy/config.py +424 -0
- apsimo/autonomy/loop.py +4316 -0
- apsimo/autonomy/registry.py +339 -0
- apsimo/autonomy/scheduler.py +1822 -0
- apsimo/autonomy/synthesis.py +449 -0
- apsimo/backup.py +962 -0
- apsimo/beliefs/__init__.py +23 -0
- apsimo/beliefs/contradictions.py +109 -0
- apsimo/beliefs/decay.py +61 -0
- apsimo/beliefs/engine.py +479 -0
- apsimo/beliefs/models.py +67 -0
- apsimo/beliefs/promotion.py +41 -0
- apsimo/beliefs/resolve.py +58 -0
- apsimo/beliefs/source_claims.py +690 -0
- apsimo/beliefs/source_projection.py +883 -0
- apsimo/beliefs/source_time.py +208 -0
- apsimo/beliefs/store.py +133 -0
- apsimo/briefings/aggregators.py +824 -0
- apsimo/briefings/composer.py +420 -0
- apsimo/briefings/config.py +55 -0
- apsimo/briefings/delivery.py +439 -0
- apsimo/briefings/engagement.py +97 -0
- apsimo/briefings/engine.py +274 -0
- apsimo/briefings/enhancer.py +99 -0
- apsimo/briefings/models.py +183 -0
- apsimo/briefings/scheduler.py +382 -0
- apsimo/briefings/store.py +435 -0
- apsimo/chain/__init__.py +48 -0
- apsimo/chain/block.py +100 -0
- apsimo/chain/cli.py +704 -0
- apsimo/chain/genesis.py +443 -0
- apsimo/chain/identity.py +416 -0
- apsimo/chain/keys.py +1025 -0
- apsimo/chain/local_keys.py +187 -0
- apsimo/chain/manager.py +290 -0
- apsimo/chain/node.py +163 -0
- apsimo/chain/plugin_transactions.py +371 -0
- apsimo/chain/protocol.py +220 -0
- apsimo/chain/state_machine.py +676 -0
- apsimo/chain/storage.py +503 -0
- apsimo/chain/transactions.py +250 -0
- apsimo/chain/validation.py +397 -0
- apsimo/channels/__init__.py +1 -0
- apsimo/channels/manifest.py +31 -0
- apsimo/channels/migrations/001_channels_schema.sql +12 -0
- apsimo/channels/phone_gateways.py +42 -0
- apsimo/channels/presence.py +188 -0
- apsimo/channels/router.py +235 -0
- apsimo/channels/store.py +231 -0
- apsimo/cli.py +2688 -0
- apsimo/cognition/__init__.py +11 -0
- apsimo/cognition/charter.py +398 -0
- apsimo/cognition/drive_governance.py +3530 -0
- apsimo/cognition/evidence_pipeline.py +1627 -0
- apsimo/cognition/external_events.py +932 -0
- apsimo/cognition/goal_spine.py +3488 -0
- apsimo/cognition/introspection.py +214 -0
- apsimo/cognition/prompt.py +150 -0
- apsimo/cognition/runtime.py +108 -0
- apsimo/cognition/trigger.py +154 -0
- apsimo/commitments/__init__.py +18 -0
- apsimo/commitments/local_work.py +355 -0
- apsimo/commitments/store.py +1052 -0
- apsimo/commitments/work.py +91 -0
- apsimo/compat.py +53 -0
- apsimo/compression/__init__.py +467 -0
- apsimo/connectors/__init__.py +21 -0
- apsimo/connectors/base.py +152 -0
- apsimo/connectors/caldav_calendar.py +125 -0
- apsimo/connectors/fs_documents.py +85 -0
- apsimo/connectors/imap_email.py +138 -0
- apsimo/connectors/manager.py +218 -0
- apsimo/connectors/webhook_pull.py +88 -0
- apsimo/contacts/__init__.py +33 -0
- apsimo/contacts/comms.py +357 -0
- apsimo/contacts/config.py +79 -0
- apsimo/contacts/exporters/__init__.py +1 -0
- apsimo/contacts/exporters/vcard.py +71 -0
- apsimo/contacts/identity_links.py +251 -0
- apsimo/contacts/importer.py +280 -0
- apsimo/contacts/importers/__init__.py +1 -0
- apsimo/contacts/importers/batch.py +43 -0
- apsimo/contacts/importers/macos_contacts.py +101 -0
- apsimo/contacts/migrations/001_contacts_schema.sql +141 -0
- apsimo/contacts/migrations/002_trust_scopes.sql +36 -0
- apsimo/contacts/migrations/003_open_gateway_enum.sql +32 -0
- apsimo/contacts/migrations/004_contact_provision_operations.sql +18 -0
- apsimo/contacts/migrations/005_identity_links.sql +27 -0
- apsimo/contacts/models.py +308 -0
- apsimo/contacts/scoring.py +16 -0
- apsimo/contacts/store.py +1623 -0
- apsimo/contacts/transport_ingress.py +252 -0
- apsimo/contacts/world_bridge.py +314 -0
- apsimo/contextgate/__init__.py +69 -0
- apsimo/contextgate/chunker.py +169 -0
- apsimo/contextgate/estimate.py +54 -0
- apsimo/contextgate/gate.py +313 -0
- apsimo/contextgate/retrieve.py +115 -0
- apsimo/delivery/__init__.py +16 -0
- apsimo/delivery/bridge.py +1260 -0
- apsimo/delivery/channels.py +526 -0
- apsimo/delivery/classification.py +50 -0
- apsimo/delivery/rate_limiter.py +268 -0
- apsimo/delivery/reachout_policy.py +206 -0
- apsimo/directed/__init__.py +22 -0
- apsimo/directed/audit.py +167 -0
- apsimo/directed/intake.py +95 -0
- apsimo/directed/models.py +191 -0
- apsimo/directed/service.py +509 -0
- apsimo/directives/__init__.py +25 -0
- apsimo/directives/evidence.py +87 -0
- apsimo/directives/extractor.py +188 -0
- apsimo/directives/guard.py +364 -0
- apsimo/directives/models.py +206 -0
- apsimo/directives/service.py +372 -0
- apsimo/directives/store.py +167 -0
- apsimo/doctor.py +2173 -0
- apsimo/environment.py +43 -0
- apsimo/events/__init__.py +33 -0
- apsimo/events/broadcaster.py +98 -0
- apsimo/events/bus.py +217 -0
- apsimo/events/journal.py +863 -0
- apsimo/events/stream.py +131 -0
- apsimo/events/types.py +150 -0
- apsimo/execution_results.py +357 -0
- apsimo/feedback/__init__.py +5 -0
- apsimo/feedback/store.py +76 -0
- apsimo/feeds/__init__.py +19 -0
- apsimo/feeds/cli.py +84 -0
- apsimo/feeds/engine.py +437 -0
- apsimo/feeds/example-feed.yaml +77 -0
- apsimo/feeds/hermes_cron.py +126 -0
- apsimo/feeds/manager.py +235 -0
- apsimo/feeds/spec.py +250 -0
- apsimo/feeds/template.py +202 -0
- apsimo/gate/__init__.py +18 -0
- apsimo/gate/audit.py +61 -0
- apsimo/gate/communication_policy.py +166 -0
- apsimo/gate/config.py +72 -0
- apsimo/gate/context_provenance.py +170 -0
- apsimo/gate/env_risk.py +226 -0
- apsimo/gate/guard_audit.py +353 -0
- apsimo/gate/layers/__init__.py +1 -0
- apsimo/gate/layers/base.py +15 -0
- apsimo/gate/layers/l1_recipient.py +66 -0
- apsimo/gate/layers/l2_pii.py +134 -0
- apsimo/gate/layers/l3_cross_context.py +50 -0
- apsimo/gate/layers/l4_trust_tier.py +78 -0
- apsimo/gate/layers/l5_injection.py +199 -0
- apsimo/gate/layers/l6_review.py +86 -0
- apsimo/gate/layers/l7_delay.py +100 -0
- apsimo/gate/layers/tom2_epistemic.py +185 -0
- apsimo/gate/models.py +64 -0
- apsimo/gate/pending_dispatch.py +5 -0
- apsimo/gate/pipeline.py +206 -0
- apsimo/gate/rejection.py +259 -0
- apsimo/gate/response_guard.py +700 -0
- apsimo/gate/rulesets/injection_v1.yaml +51 -0
- apsimo/gate/surface_policy.py +189 -0
- apsimo/gate/taint.py +226 -0
- apsimo/genesis.json +9 -0
- apsimo/goals/__init__.py +100 -0
- apsimo/goals/config.py +38 -0
- apsimo/goals/decomposer.py +421 -0
- apsimo/goals/engine.py +617 -0
- apsimo/goals/inference.py +354 -0
- apsimo/goals/models.py +302 -0
- apsimo/goals/priority.py +270 -0
- apsimo/goals/queue_bridge.py +149 -0
- apsimo/goals/replan.py +450 -0
- apsimo/goals/schema.sql +89 -0
- apsimo/goals/store.py +692 -0
- apsimo/governed_actions.py +1708 -0
- apsimo/harness_integration/__init__.py +45 -0
- apsimo/harness_integration/context.py +41 -0
- apsimo/harness_integration/skills.py +231 -0
- apsimo/identity/__init__.py +26 -0
- apsimo/identity/participants.py +181 -0
- apsimo/identity/resolver.py +329 -0
- apsimo/identity_bootstrap/__init__.py +5 -0
- apsimo/identity_bootstrap/builder.py +208 -0
- apsimo/identity_bootstrap/corpus.py +443 -0
- apsimo/identity_bootstrap/models.py +54 -0
- apsimo/identity_bootstrap/runner.py +353 -0
- apsimo/identity_bootstrap/seeders/__init__.py +25 -0
- apsimo/identity_bootstrap/seeders/briefings.py +109 -0
- apsimo/identity_bootstrap/seeders/chain.py +57 -0
- apsimo/identity_bootstrap/seeders/goals.py +128 -0
- apsimo/identity_bootstrap/seeders/memory.py +191 -0
- apsimo/identity_bootstrap/seeders/neo4j_cognition.py +79 -0
- apsimo/identity_bootstrap/seeders/relationship.py +152 -0
- apsimo/identity_bootstrap/seeders/sessions.py +67 -0
- apsimo/identity_bootstrap/seeders/skills.py +92 -0
- apsimo/identity_bootstrap/seeders/task_queue.py +72 -0
- apsimo/identity_bootstrap/seeders/world_model.py +143 -0
- apsimo/identity_bootstrap/self_query.py +92 -0
- apsimo/identity_bootstrap/self_reflection.py +155 -0
- apsimo/identity_bootstrap/skill.py +37 -0
- apsimo/identity_bootstrap/verifier.py +436 -0
- apsimo/initiatives/__init__.py +20 -0
- apsimo/initiatives/action_registry.py +454 -0
- apsimo/initiatives/approval_authority.py +2105 -0
- apsimo/initiatives/approval_policy.py +123 -0
- apsimo/initiatives/assignment.py +263 -0
- apsimo/initiatives/backup_evidence.py +100 -0
- apsimo/initiatives/context_freshness.py +103 -0
- apsimo/initiatives/models.py +318 -0
- apsimo/initiatives/native_work.py +270 -0
- apsimo/initiatives/standing_approvals.py +232 -0
- apsimo/initiatives/store.py +1081 -0
- apsimo/initiatives/temporal_followup.py +410 -0
- apsimo/intelligence/__init__.py +1 -0
- apsimo/intelligence/cognition/__init__.py +24 -0
- apsimo/intelligence/cognition/gap_detector.py +148 -0
- apsimo/intelligence/cognition/metalearner.py +547 -0
- apsimo/intelligence/cognition/metrics_collector.py +217 -0
- apsimo/intelligence/cognition/performance_index.py +299 -0
- apsimo/intelligence/cognition/registry.py +192 -0
- apsimo/intelligence/cognition/strategy_adjuster.py +222 -0
- apsimo/intelligence/cognition/types.py +16 -0
- apsimo/intelligence/components/__init__.py +66 -0
- apsimo/intelligence/components/anomaly_detector.py +413 -0
- apsimo/intelligence/components/initiative_engine.py +2643 -0
- apsimo/intelligence/components/preference_learner.py +521 -0
- apsimo/intelligence/components/research_orchestrator.py +358 -0
- apsimo/intelligence/components/self_directed_thinker.py +221 -0
- apsimo/intelligence/components/self_reflector.py +252 -0
- apsimo/intelligence/components/session_continuity.py +154 -0
- apsimo/intelligence/components/task_planner.py +320 -0
- apsimo/intelligence/components/tool_learner.py +217 -0
- apsimo/intelligence/graph/__init__.py +79 -0
- apsimo/intelligence/graph/client.py +2483 -0
- apsimo/intelligence/graph/consolidator.py +405 -0
- apsimo/intelligence/graph/distiller.py +312 -0
- apsimo/intelligence/graph/migrations.py +129 -0
- apsimo/intelligence/graph/queries.py +248 -0
- apsimo/intelligence/graph/recall.py +281 -0
- apsimo/intelligence/graph/reconciler.py +144 -0
- apsimo/intelligence/graph/schema.py +337 -0
- apsimo/intelligence/graph/selection.py +252 -0
- apsimo/intelligence/learning/__init__.py +17 -0
- apsimo/intelligence/learning/continuous_learner.py +245 -0
- apsimo/intelligence/learning/feedback_store.py +321 -0
- apsimo/intelligence/mind_model/__init__.py +1 -0
- apsimo/intelligence/mind_model/graph_baseline.py +136 -0
- apsimo/intelligence/mind_model/signal_collector.py +361 -0
- apsimo/intelligence/relationships/__init__.py +11 -0
- apsimo/intelligence/relationships/profiler.py +389 -0
- apsimo/intelligence/relationships/scorer.py +560 -0
- apsimo/intelligence/relationships/signal_floor.py +66 -0
- apsimo/intelligence/relationships/trust_tiers.py +300 -0
- apsimo/intelligence/synthesis/__init__.py +40 -0
- apsimo/intelligence/synthesis/connection_discoverer.py +379 -0
- apsimo/intelligence/synthesis/cross_domain_analyzer.py +287 -0
- apsimo/intelligence/synthesis/insight_deliverer.py +171 -0
- apsimo/intelligence/synthesis/insight_store.py +79 -0
- apsimo/intelligence/synthesis/insight_validator.py +183 -0
- apsimo/intelligence/synthesis/novelty_scorer.py +267 -0
- apsimo/intelligence/turn_middleware/__init__.py +15 -0
- apsimo/intelligence/turn_middleware/memory_sync.py +119 -0
- apsimo/mcp/__init__.py +41 -0
- apsimo/mcp/__main__.py +6 -0
- apsimo/mcp/config.py +287 -0
- apsimo/mcp/server.py +501 -0
- apsimo/migrations.py +187 -0
- apsimo/mining/__init__.py +27 -0
- apsimo/mining/corpus.py +239 -0
- apsimo/mining/escalations.py +289 -0
- apsimo/mining/models.py +169 -0
- apsimo/mining/store.py +210 -0
- apsimo/models/__init__.py +30 -0
- apsimo/models/memory.py +80 -0
- apsimo/models/mesh.py +72 -0
- apsimo/models/person.py +104 -0
- apsimo/models/signal.py +108 -0
- apsimo/observations/__init__.py +15 -0
- apsimo/observations/store.py +277 -0
- apsimo/patterns/__init__.py +6 -0
- apsimo/patterns/extract.py +187 -0
- apsimo/patterns/store.py +227 -0
- apsimo/persona/__init__.py +1 -0
- apsimo/persona/engine.py +611 -0
- apsimo/persona/manifest.py +140 -0
- apsimo/projects/__init__.py +28 -0
- apsimo/projects/engine.py +1681 -0
- apsimo/projects/event_outbox.py +188 -0
- apsimo/projects/models.py +216 -0
- apsimo/projects/planner.py +181 -0
- apsimo/projects/store.py +1446 -0
- apsimo/proposals/__init__.py +12 -0
- apsimo/proposals/engine.py +114 -0
- apsimo/proposals/models.py +207 -0
- apsimo/qualification/__init__.py +1 -0
- apsimo/qualification/cases.py +75 -0
- apsimo/qualification/cli.py +51 -0
- apsimo/qualification/memory_cases.py +209 -0
- apsimo/qualification/records.py +92 -0
- apsimo/qualification/report.py +87 -0
- apsimo/qualification/runner.py +311 -0
- apsimo/qualification/structured_cases.py +131 -0
- apsimo/reasoning/__init__.py +13 -0
- apsimo/reasoning/executor.py +506 -0
- apsimo/reasoning/loop.py +373 -0
- apsimo/reasoning/native_tools/__init__.py +16 -0
- apsimo/reasoning/native_tools/calculate.py +141 -0
- apsimo/reasoning/native_tools/file_ops.py +150 -0
- apsimo/reasoning/native_tools/web_search.py +49 -0
- apsimo/reasoning/tool_policy.py +182 -0
- apsimo/redact/__init__.py +176 -0
- apsimo/repos/__init__.py +5 -0
- apsimo/repos/mirrors.py +204 -0
- apsimo/research/__init__.py +41 -0
- apsimo/research/artifact.py +482 -0
- apsimo/research/gatherer.py +387 -0
- apsimo/research/pipeline.py +513 -0
- apsimo/research/search/__init__.py +7 -0
- apsimo/research/search/base.py +41 -0
- apsimo/research/search/brave.py +59 -0
- apsimo/research/search/cache.py +51 -0
- apsimo/research/search/duckduckgo.py +103 -0
- apsimo/research/search/orchestrator.py +119 -0
- apsimo/research/search/serpapi.py +59 -0
- apsimo/research/search/tavily.py +59 -0
- apsimo/research/synthesizer.py +309 -0
- apsimo/router/__init__.py +30 -0
- apsimo/router/complexity_scorer.py +148 -0
- apsimo/router/endpoints.py +153 -0
- apsimo/router/fallback.py +58 -0
- apsimo/router/functions.py +243 -0
- apsimo/router/native_policy.py +52 -0
- apsimo/router/router.py +762 -0
- apsimo/router/self_learning.py +174 -0
- apsimo/router/tiers.py +677 -0
- apsimo/sandbox/__init__.py +21 -0
- apsimo/sandbox/backend.py +195 -0
- apsimo/sandbox/manager.py +173 -0
- apsimo/scope_bounds.py +7 -0
- apsimo/secrets/__init__.py +6 -0
- apsimo/secrets/backends/__init__.py +8 -0
- apsimo/secrets/backends/base.py +42 -0
- apsimo/secrets/backends/env.py +110 -0
- apsimo/secrets/backends/keyring.py +72 -0
- apsimo/secrets/backends/onepassword.py +232 -0
- apsimo/secrets/cli.py +191 -0
- apsimo/secrets/manager.py +160 -0
- apsimo/secrets/migration.py +101 -0
- apsimo/secrets/types.py +98 -0
- apsimo/seed.py +41 -0
- apsimo/self_model/__init__.py +37 -0
- apsimo/self_model/appraisals.py +673 -0
- apsimo/self_model/benchmark.py +1314 -0
- apsimo/self_model/brief.py +40 -0
- apsimo/self_model/event_concerns.py +1128 -0
- apsimo/self_model/execution_forecasts.py +353 -0
- apsimo/self_model/expectations.py +1595 -0
- apsimo/self_model/experiments.py +1150 -0
- apsimo/self_model/journal.py +148 -0
- apsimo/self_model/judgments.py +705 -0
- apsimo/self_model/native_outcomes.py +55 -0
- apsimo/self_model/params.py +220 -0
- apsimo/self_model/perspective.py +246 -0
- apsimo/self_model/reconcile.py +183 -0
- apsimo/self_model/reply_forecasts.py +381 -0
- apsimo/self_model/runtime_forecasts.py +296 -0
- apsimo/self_model/runtime_models.py +67 -0
- apsimo/self_model/settlement.py +207 -0
- apsimo/self_model/situation.py +1731 -0
- apsimo/self_model/store.py +883 -0
- apsimo/self_model/supervised.py +137 -0
- apsimo/self_model/thinker.py +99 -0
- apsimo/self_model/trust.py +388 -0
- apsimo/self_model/workspace.py +2388 -0
- apsimo/server.py +4197 -0
- apsimo/services/__init__.py +1 -0
- apsimo/services/agent_bridge.py +474 -0
- apsimo/services/initiative_executor.py +914 -0
- apsimo/services/instance.py +297 -0
- apsimo/sessions/__init__.py +22 -0
- apsimo/sessions/config.py +13 -0
- apsimo/sessions/context_loader.py +88 -0
- apsimo/sessions/federation_session.py +75 -0
- apsimo/sessions/isolated_session.py +98 -0
- apsimo/sessions/reports.py +84 -0
- apsimo/sessions/store.py +148 -0
- apsimo/setup.py +2818 -0
- apsimo/setup_hermes.py +879 -0
- apsimo/setup_local_work.py +218 -0
- apsimo/setup_native_goals.py +134 -0
- apsimo/setup_native_reviews.py +115 -0
- apsimo/skills/__init__.py +10 -0
- apsimo/skills/base.py +108 -0
- apsimo/skills/budget.py +28 -0
- apsimo/skills/executor.py +493 -0
- apsimo/skills/executors/__init__.py +1 -0
- apsimo/skills/executors/behavioral_correction.py +75 -0
- apsimo/skills/executors/capability_gap.py +38 -0
- apsimo/skills/executors/data_quality.py +163 -0
- apsimo/skills/executors/knowledge_acquisition.py +41 -0
- apsimo/skills/executors/operational_hygiene.py +185 -0
- apsimo/skills/executors/subsystem_health.py +169 -0
- apsimo/skills/hermes_export.py +431 -0
- apsimo/skills/index.py +123 -0
- apsimo/skills/learning/__init__.py +21 -0
- apsimo/skills/learning/novelty_detector.py +206 -0
- apsimo/skills/learning/pattern_extractor.py +199 -0
- apsimo/skills/learning/triggers.py +159 -0
- apsimo/skills/loader.py +246 -0
- apsimo/skills/migrations/002_progressive_loading.sql +6 -0
- apsimo/skills/migrations/backfill_triggers.py +20 -0
- apsimo/skills/models.py +202 -0
- apsimo/skills/packager.py +128 -0
- apsimo/skills/protocols.py +70 -0
- apsimo/skills/registry.py +191 -0
- apsimo/skills/runtime.py +58 -0
- apsimo/skills/sandbox_runner.py +229 -0
- apsimo/skills/scheduler.py +129 -0
- apsimo/skills/schema.py +79 -0
- apsimo/skills/security/__init__.py +12 -0
- apsimo/skills/security/guards.py +53 -0
- apsimo/skills/security/scanner.py +223 -0
- apsimo/skills_memory/__init__.py +26 -0
- apsimo/skills_memory/distill.py +159 -0
- apsimo/skills_memory/models.py +85 -0
- apsimo/skills_memory/retrieve.py +62 -0
- apsimo/skills_memory/store.py +172 -0
- apsimo/surprise/__init__.py +6 -0
- apsimo/surprise/accumulation.py +57 -0
- apsimo/surprise/scorer.py +102 -0
- apsimo/surprise/store.py +203 -0
- apsimo/task_queue/__init__.py +69 -0
- apsimo/task_queue/action_receipts.py +148 -0
- apsimo/task_queue/approval_relay_canary.py +108 -0
- apsimo/task_queue/config.py +85 -0
- apsimo/task_queue/contract.py +361 -0
- apsimo/task_queue/events.py +130 -0
- apsimo/task_queue/governor.py +1031 -0
- apsimo/task_queue/handlers/__init__.py +16 -0
- apsimo/task_queue/handlers/base.py +37 -0
- apsimo/task_queue/handlers/inference.py +640 -0
- apsimo/task_queue/handlers/monitoring.py +116 -0
- apsimo/task_queue/handlers/registry.py +75 -0
- apsimo/task_queue/handlers/subtask_handler.py +173 -0
- apsimo/task_queue/handlers/system_maintenance.py +147 -0
- apsimo/task_queue/mesh_integration.py +111 -0
- apsimo/task_queue/models.py +317 -0
- apsimo/task_queue/queue_manager.py +8286 -0
- apsimo/task_queue/routing.py +287 -0
- apsimo/task_queue/scheduler.py +252 -0
- apsimo/task_queue/schema.sql +197 -0
- apsimo/task_queue/work_control.py +342 -0
- apsimo/task_queue/worker.py +993 -0
- apsimo/telemetry.py +145 -0
- apsimo/tom/__init__.py +6 -0
- apsimo/tom/affect.py +387 -0
- apsimo/tom/approvals.py +171 -0
- apsimo/tom/arcs.py +896 -0
- apsimo/tom/asymmetry.py +131 -0
- apsimo/tom/eligibility.py +248 -0
- apsimo/tom/engagement.py +214 -0
- apsimo/tom/exposure.py +214 -0
- apsimo/tom/extractor.py +306 -0
- apsimo/tom/fact_adapters.py +144 -0
- apsimo/tom/facts.py +326 -0
- apsimo/tom/integration.py +592 -0
- apsimo/tom/leveled.py +118 -0
- apsimo/tom/levels.py +247 -0
- apsimo/tom/recipient_audit.py +995 -0
- apsimo/tom/recipient_simulator.py +593 -0
- apsimo/tom/source_lineage.py +93 -0
- apsimo/tom/tom2.py +277 -0
- apsimo/tom/visibility.py +559 -0
- apsimo/tom/visibility_store.py +414 -0
- apsimo/tools/__init__.py +0 -0
- apsimo/tools/definitions.py +740 -0
- apsimo/tools/handlers.py +943 -0
- apsimo/toolsmith/__init__.py +26 -0
- apsimo/toolsmith/authority.py +166 -0
- apsimo/toolsmith/engine.py +559 -0
- apsimo/toolsmith/integrity.py +100 -0
- apsimo/toolsmith/miner.py +145 -0
- apsimo/toolsmith/policy.py +110 -0
- apsimo/toolsmith/registry.py +635 -0
- apsimo/turns/__init__.py +17 -0
- apsimo/turns/audio.py +134 -0
- apsimo/turns/documents.py +235 -0
- apsimo/turns/executions.py +486 -0
- apsimo/turns/hermes_history.py +245 -0
- apsimo/turns/hermes_kanban.py +268 -0
- apsimo/turns/hermes_work.py +96 -0
- apsimo/turns/idempotency.py +752 -0
- apsimo/turns/local_work.py +115 -0
- apsimo/turns/media.py +581 -0
- apsimo/turns/reported_workers.py +196 -0
- apsimo/turns/source_annotations.py +283 -0
- apsimo/turns/source_attribution.py +154 -0
- apsimo/turns/source_read.py +351 -0
- apsimo/turns/source_vectors.py +263 -0
- apsimo/turns/video.py +210 -0
- apsimo/util/autonomy_preset.py +220 -0
- apsimo/util/instance.py +92 -0
- apsimo/util/model_output.py +25 -0
- apsimo/util/quiet_hours.py +27 -0
- apsimo/util/session_safety.py +37 -0
- apsimo/util/temporal.py +343 -0
- apsimo/vector/__init__.py +75 -0
- apsimo/vector/backfill.py +171 -0
- apsimo/vector/caption.py +114 -0
- apsimo/vector/collections.py +51 -0
- apsimo/vector/config.py +102 -0
- apsimo/vector/embedder.py +670 -0
- apsimo/vector/image_preprocess.py +406 -0
- apsimo/vector/image_store.py +296 -0
- apsimo/vector/indexes.py +162 -0
- apsimo/vector/migrate.py +334 -0
- apsimo/vector/multimodal_provider.py +417 -0
- apsimo/vector/multimodal_types.py +87 -0
- apsimo/vector/openai_provider.py +119 -0
- apsimo/vector/query.py +49 -0
- apsimo/vector/reranker.py +565 -0
- apsimo/vector/safety_image.py +159 -0
- apsimo/vector/scanner.py +197 -0
- apsimo/vector/setup.py +289 -0
- apsimo/vector/store.py +533 -0
- apsimo/vector/tiers.py +263 -0
- apsimo/work_orders.py +925 -0
- apsimo/workers/__init__.py +21 -0
- apsimo/workers/agent_bridge.py +640 -0
- apsimo/workers/colony_worker.py +382 -0
- apsimo/workers/queue_worker.py +441 -0
- apsimo/workers/skills_sync.py +152 -0
- apsimo/world_model/__init__.py +71 -0
- apsimo/world_model/causal_maintenance.py +131 -0
- apsimo/world_model/causal_policy.py +43 -0
- apsimo/world_model/causal_query.py +125 -0
- apsimo/world_model/confidence.py +54 -0
- apsimo/world_model/config.py +64 -0
- apsimo/world_model/constants.py +97 -0
- apsimo/world_model/entities.py +145 -0
- apsimo/world_model/expectation_resolvers.py +177 -0
- apsimo/world_model/extraction/__init__.py +7 -0
- apsimo/world_model/extraction/base.py +62 -0
- apsimo/world_model/extraction/conversation_extractor.py +262 -0
- apsimo/world_model/extraction/detector.py +74 -0
- apsimo/world_model/extraction/document_extractor.py +78 -0
- apsimo/world_model/extraction/formats/__init__.py +24 -0
- apsimo/world_model/extraction/formats/csv_fmt.py +68 -0
- apsimo/world_model/extraction/formats/html_fmt.py +72 -0
- apsimo/world_model/extraction/formats/json_fmt.py +68 -0
- apsimo/world_model/extraction/formats/pdf.py +43 -0
- apsimo/world_model/extraction/formats/text.py +27 -0
- apsimo/world_model/extraction/llm_extractor.py +164 -0
- apsimo/world_model/extraction/pipeline.py +73 -0
- apsimo/world_model/integrations/__init__.py +5 -0
- apsimo/world_model/integrations/mind_model_bridge.py +115 -0
- apsimo/world_model/integrations/social_intel_bridge.py +120 -0
- apsimo/world_model/jobs/__init__.py +4 -0
- apsimo/world_model/jobs/extraction_job.py +168 -0
- apsimo/world_model/llm_extract.py +572 -0
- apsimo/world_model/neo4j/__init__.py +5 -0
- apsimo/world_model/neo4j/backend.py +654 -0
- apsimo/world_model/observations.py +155 -0
- apsimo/world_model/populator.py +307 -0
- apsimo/world_model/postgres/__init__.py +1 -0
- apsimo/world_model/postgres/backend.py +683 -0
- apsimo/world_model/relationships.py +25 -0
- apsimo/world_model/resolution/__init__.py +13 -0
- apsimo/world_model/resolution/entity_resolver.py +232 -0
- apsimo/world_model/resolution/merge_audit.py +16 -0
- apsimo/world_model/resolution/merge_workflow.py +117 -0
- apsimo/world_model/source_reports.py +121 -0
- apsimo/world_model/sqlite/__init__.py +4 -0
- apsimo/world_model/sqlite/backend.py +855 -0
- apsimo/world_model/sqlite/schema.sql +132 -0
- apsimo/world_model/store.py +545 -0
- apsimo-1.3.0.dist-info/METADATA +78 -0
- apsimo-1.3.0.dist-info/RECORD +614 -0
- apsimo-1.3.0.dist-info/WHEEL +5 -0
- apsimo-1.3.0.dist-info/entry_points.txt +11 -0
- apsimo-1.3.0.dist-info/licenses/LICENSE +21 -0
- apsimo-1.3.0.dist-info/top_level.txt +2 -0
- colony_sidecar/__init__.py +4 -0
apsimo/mcp/server.py
ADDED
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
"""Apsimo MCP Server implementation.
|
|
2
|
+
|
|
3
|
+
Thin adapter that translates MCP tool calls into Apsimo sidecar HTTP requests.
|
|
4
|
+
All cognitive state lives in the sidecar — this server has no direct DB access.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
from mcp.server.fastmcp import FastMCP
|
|
12
|
+
|
|
13
|
+
# ---------------------------------------------------------------------------
|
|
14
|
+
# Helpers
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
def _base_url() -> str:
|
|
18
|
+
return os.environ.get("APSIMO_URL") or os.environ.get("COLONY_URL", "http://127.0.0.1:7777")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _api_key() -> str:
|
|
22
|
+
return os.environ.get("APSIMO_API_KEY") or os.environ.get("COLONY_API_KEY", "")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _headers() -> dict[str, str]:
|
|
26
|
+
key = _api_key()
|
|
27
|
+
return {"Authorization": f"Bearer {key}"} if key else {}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _source() -> str | None:
|
|
31
|
+
return os.environ.get("APSIMO_MCP_SOURCE") or os.environ.get("COLONY_MCP_SOURCE")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _contact_id(override: str | None = None) -> str | None:
|
|
35
|
+
return override or os.environ.get("APSIMO_MCP_CONTACT_ID") or os.environ.get("COLONY_MCP_CONTACT_ID")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _require_contact(override: str | None = None) -> tuple[str, dict[str, str]]:
|
|
39
|
+
"""Return (contact_id, error_dict) — if contact_id is None, error_dict has the error."""
|
|
40
|
+
cid = _contact_id(override)
|
|
41
|
+
if cid:
|
|
42
|
+
return cid, {}
|
|
43
|
+
return "", {
|
|
44
|
+
"error": "contact_id_required",
|
|
45
|
+
"message": "No contact_id provided and APSIMO_MCP_CONTACT_ID is not set",
|
|
46
|
+
"suggestion": "Set APSIMO_MCP_CONTACT_ID in your MCP config or pass contact_id explicitly",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _sidecar_error(exc: Exception) -> dict[str, str]:
|
|
51
|
+
"""Format a sidecar connection error."""
|
|
52
|
+
return {
|
|
53
|
+
"error": "sidecar_unreachable",
|
|
54
|
+
"message": f"Apsimo sidecar not reachable at {_base_url()}",
|
|
55
|
+
"suggestion": "Start with: apsimo start",
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
async def _get(path: str, params: dict | None = None) -> dict | None:
|
|
60
|
+
try:
|
|
61
|
+
async with httpx.AsyncClient(timeout=10) as client:
|
|
62
|
+
r = await client.get(f"{_base_url()}{path}", headers=_headers(), params=params)
|
|
63
|
+
if r.status_code == 200:
|
|
64
|
+
return r.json()
|
|
65
|
+
return {"error": f"http_{r.status_code}", "message": r.text[:1000]}
|
|
66
|
+
except httpx.ConnectError as exc:
|
|
67
|
+
return _sidecar_error(exc)
|
|
68
|
+
except Exception as exc:
|
|
69
|
+
return {"error": "request_failed", "message": str(exc)[:1000]}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
async def _post(path: str, data: dict) -> dict | None:
|
|
73
|
+
try:
|
|
74
|
+
# Inject provenance from COLONY_MCP_SOURCE into metadata so it
|
|
75
|
+
# survives Pydantic validation (top-level 'provenance' is not in any schema).
|
|
76
|
+
src = _source()
|
|
77
|
+
if src:
|
|
78
|
+
data.setdefault("metadata", {})["provenance"] = src
|
|
79
|
+
async with httpx.AsyncClient(timeout=10) as client:
|
|
80
|
+
r = await client.post(f"{_base_url()}{path}", headers={**_headers(), "Content-Type": "application/json"}, json=data)
|
|
81
|
+
if r.status_code in (200, 201):
|
|
82
|
+
return r.json()
|
|
83
|
+
return {"error": f"http_{r.status_code}", "message": r.text[:1000]}
|
|
84
|
+
except httpx.ConnectError as exc:
|
|
85
|
+
return _sidecar_error(exc)
|
|
86
|
+
except Exception as exc:
|
|
87
|
+
return {"error": "request_failed", "message": str(exc)[:1000]}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
async def _patch(path: str, data: dict) -> dict | None:
|
|
91
|
+
try:
|
|
92
|
+
async with httpx.AsyncClient(timeout=10) as client:
|
|
93
|
+
r = await client.patch(f"{_base_url()}{path}", headers={**_headers(), "Content-Type": "application/json"}, json=data)
|
|
94
|
+
if r.status_code == 200:
|
|
95
|
+
return r.json()
|
|
96
|
+
return {"error": f"http_{r.status_code}", "message": r.text[:1000]}
|
|
97
|
+
except httpx.ConnectError as exc:
|
|
98
|
+
return _sidecar_error(exc)
|
|
99
|
+
except Exception as exc:
|
|
100
|
+
return {"error": "request_failed", "message": str(exc)[:1000]}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
async def _delete(path: str) -> tuple[int, str]:
|
|
104
|
+
"""Delete a resource. Returns (status_code, error_message_or_empty)."""
|
|
105
|
+
try:
|
|
106
|
+
async with httpx.AsyncClient(timeout=10) as client:
|
|
107
|
+
r = await client.delete(f"{_base_url()}{path}", headers=_headers())
|
|
108
|
+
return r.status_code, ""
|
|
109
|
+
except httpx.ConnectError as exc:
|
|
110
|
+
return -1, str(exc)[:200]
|
|
111
|
+
except Exception as exc:
|
|
112
|
+
return -1, str(exc)[:200]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# ---------------------------------------------------------------------------
|
|
116
|
+
# Server
|
|
117
|
+
# ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
class ApsimoMCP(FastMCP):
|
|
120
|
+
"""Advertise one tool catalog while accepting pre-rename clients."""
|
|
121
|
+
|
|
122
|
+
async def call_tool(self, name: str, arguments: dict[str, Any]):
|
|
123
|
+
if name.startswith("colony_"):
|
|
124
|
+
name = "apsimo_" + name[len("colony_"):]
|
|
125
|
+
return await super().call_tool(name, arguments)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def create_server() -> FastMCP:
|
|
129
|
+
"""Create the Apsimo MCP server with all tools registered."""
|
|
130
|
+
|
|
131
|
+
mcp = ApsimoMCP(
|
|
132
|
+
"apsimo",
|
|
133
|
+
instructions=(
|
|
134
|
+
"Apsimo provides a cognitive substrate for AI agents: commitments, facts, "
|
|
135
|
+
"affect tracking, world model, patterns, and surprises. Use these tools to "
|
|
136
|
+
"give your agent memory, awareness, and continuity across sessions."
|
|
137
|
+
),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# --- Read-only tools (safe for auto-call) ---
|
|
141
|
+
|
|
142
|
+
@mcp.tool(annotations={"readOnlyHint": True, "idempotentHint": True})
|
|
143
|
+
async def apsimo_health() -> dict:
|
|
144
|
+
"""Check if Apsimo sidecar is running and healthy. Call at session start or when other tools fail."""
|
|
145
|
+
return await _get("/v1/host/health")
|
|
146
|
+
|
|
147
|
+
@mcp.tool(annotations={"readOnlyHint": True, "idempotentHint": True})
|
|
148
|
+
async def apsimo_get_context(
|
|
149
|
+
contact_id: str | None = None,
|
|
150
|
+
message: str | None = None,
|
|
151
|
+
) -> dict:
|
|
152
|
+
"""Get context scoped to the authenticated viewer and contact: commitments, relevant evidence and source-backed working perspective. Call at the start of a task or when the user asks what to work on."""
|
|
153
|
+
cid, err = _require_contact(contact_id)
|
|
154
|
+
if err:
|
|
155
|
+
return err
|
|
156
|
+
|
|
157
|
+
payload: dict[str, Any] = {
|
|
158
|
+
"identity": {"host_id": "mcp"},
|
|
159
|
+
"context": {"session_id": "mcp", "contact_id": cid},
|
|
160
|
+
"incoming_message": {"role": "user", "content": message or ""},
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return await _post("/v1/host/context/assemble", payload)
|
|
164
|
+
|
|
165
|
+
@mcp.tool(annotations={"readOnlyHint": True, "idempotentHint": True})
|
|
166
|
+
async def apsimo_check_commitments(
|
|
167
|
+
status: str | None = None,
|
|
168
|
+
person_id: str | None = None,
|
|
169
|
+
limit: int = 10,
|
|
170
|
+
) -> dict:
|
|
171
|
+
"""List or search commitments. Call before starting work, when a deadline is mentioned, or when planning a sprint. If person_id is not provided, returns all commitments."""
|
|
172
|
+
params: dict[str, Any] = {"limit": limit}
|
|
173
|
+
if status:
|
|
174
|
+
params["status"] = status
|
|
175
|
+
cid = _contact_id(person_id)
|
|
176
|
+
if cid:
|
|
177
|
+
params["person_id"] = cid
|
|
178
|
+
return await _get("/v1/host/commitments", params=params)
|
|
179
|
+
|
|
180
|
+
@mcp.tool(annotations={"readOnlyHint": True, "idempotentHint": True})
|
|
181
|
+
async def apsimo_lookup_facts(
|
|
182
|
+
contact_id: str | None = None,
|
|
183
|
+
source: str | None = None,
|
|
184
|
+
min_confidence: float | None = None,
|
|
185
|
+
limit: int = 10,
|
|
186
|
+
) -> dict:
|
|
187
|
+
"""Retrieve facts about a contact. Call when starting a conversation, making design decisions, or personalizing output. Filter by source (told_by_contact, told_to_contact, shared_context, inferred) or minimum confidence."""
|
|
188
|
+
cid, err = _require_contact(contact_id)
|
|
189
|
+
if err:
|
|
190
|
+
return err
|
|
191
|
+
params: dict[str, Any] = {"contact_id": cid, "limit": limit}
|
|
192
|
+
if source:
|
|
193
|
+
params["source"] = source
|
|
194
|
+
if min_confidence is not None:
|
|
195
|
+
params["min_confidence"] = min_confidence
|
|
196
|
+
return await _get("/v1/host/mind/facts", params=params)
|
|
197
|
+
|
|
198
|
+
@mcp.tool(annotations={"readOnlyHint": True, "idempotentHint": True})
|
|
199
|
+
async def apsimo_check_affect(
|
|
200
|
+
contact_id: str | None = None,
|
|
201
|
+
) -> dict:
|
|
202
|
+
"""Read legacy explicit affect history for compatibility. This is not measured emotion and must not determine trust or disclosure authority."""
|
|
203
|
+
cid, err = _require_contact(contact_id)
|
|
204
|
+
if err:
|
|
205
|
+
return err
|
|
206
|
+
return await _get(f"/v1/host/affect/state/{cid}")
|
|
207
|
+
|
|
208
|
+
@mcp.tool(annotations={"readOnlyHint": True, "idempotentHint": True})
|
|
209
|
+
async def apsimo_search_world(
|
|
210
|
+
query: str,
|
|
211
|
+
entity_type: str | None = None,
|
|
212
|
+
limit: int = 5,
|
|
213
|
+
) -> dict:
|
|
214
|
+
"""Search the world model for entities or relationships. Call when exploring a codebase, understanding dependencies, or planning changes."""
|
|
215
|
+
data: dict[str, Any] = {
|
|
216
|
+
"identity": {"host_id": "mcp"},
|
|
217
|
+
"query": query,
|
|
218
|
+
"limit": limit,
|
|
219
|
+
}
|
|
220
|
+
if entity_type:
|
|
221
|
+
data["entity_type"] = entity_type
|
|
222
|
+
return await _post("/v1/host/world/entities/query", data)
|
|
223
|
+
|
|
224
|
+
@mcp.tool(annotations={"readOnlyHint": True, "idempotentHint": True})
|
|
225
|
+
async def apsimo_get_patterns(
|
|
226
|
+
pattern_type: str | None = None,
|
|
227
|
+
min_frequency: int | None = None,
|
|
228
|
+
active_only: bool = False,
|
|
229
|
+
limit: int = 5,
|
|
230
|
+
) -> dict:
|
|
231
|
+
"""Retrieve learned patterns. Call when suggesting workflows, onboarding to a project, or planning work. Filter by pattern_type, minimum frequency, or active status."""
|
|
232
|
+
params: dict[str, Any] = {"limit": limit}
|
|
233
|
+
if pattern_type:
|
|
234
|
+
params["pattern_type"] = pattern_type
|
|
235
|
+
if min_frequency is not None:
|
|
236
|
+
params["min_frequency"] = min_frequency
|
|
237
|
+
if active_only:
|
|
238
|
+
params["active_only"] = True
|
|
239
|
+
return await _get("/v1/host/patterns", params=params)
|
|
240
|
+
|
|
241
|
+
# --- Mutating tools ---
|
|
242
|
+
|
|
243
|
+
@mcp.tool(annotations={"readOnlyHint": False, "idempotentHint": False})
|
|
244
|
+
async def apsimo_create_commitment(
|
|
245
|
+
description: str,
|
|
246
|
+
person_id: str | None = None,
|
|
247
|
+
due_at: str | None = None,
|
|
248
|
+
priority: int = 50,
|
|
249
|
+
) -> dict:
|
|
250
|
+
"""Create a new commitment. Call when the user promises something, agrees to a deadline, or a task has a clear due date. Priority is 0-100, default 50."""
|
|
251
|
+
cid, err = _require_contact(person_id)
|
|
252
|
+
if err:
|
|
253
|
+
return err
|
|
254
|
+
data: dict[str, Any] = {
|
|
255
|
+
"person_id": cid,
|
|
256
|
+
"description": description,
|
|
257
|
+
"priority": priority,
|
|
258
|
+
}
|
|
259
|
+
if due_at:
|
|
260
|
+
data["due_at"] = due_at
|
|
261
|
+
return await _post("/v1/host/commitments", data)
|
|
262
|
+
|
|
263
|
+
@mcp.tool(annotations={"readOnlyHint": False, "idempotentHint": True})
|
|
264
|
+
async def apsimo_fulfill_commitment(
|
|
265
|
+
commitment_id: str,
|
|
266
|
+
note: str | None = None,
|
|
267
|
+
) -> dict:
|
|
268
|
+
"""Mark a commitment as fulfilled. Call when a task is completed or a promise is kept."""
|
|
269
|
+
data: dict[str, Any] = {"outcome": "done", "resolved_by": "agent"}
|
|
270
|
+
if note:
|
|
271
|
+
data["reason"] = note
|
|
272
|
+
return await _patch(f"/v1/host/commitments/{commitment_id}", data)
|
|
273
|
+
|
|
274
|
+
@mcp.tool(annotations={"readOnlyHint": False, "idempotentHint": True})
|
|
275
|
+
async def apsimo_cancel_commitment(
|
|
276
|
+
commitment_id: str,
|
|
277
|
+
reason: str | None = None,
|
|
278
|
+
outcome: str = "wont_do",
|
|
279
|
+
) -> dict:
|
|
280
|
+
"""Cancel a commitment that's no longer relevant, with WHY: outcome is one of invalid (should never have been recorded), duplicate (already tracked), wont_do (deliberately dropped), obsolete (overtaken by events). The reason feeds the learning loop that calibrates what gets recorded."""
|
|
281
|
+
if outcome not in ("invalid", "duplicate", "wont_do", "obsolete"):
|
|
282
|
+
outcome = "wont_do"
|
|
283
|
+
data: dict[str, Any] = {"outcome": outcome, "resolved_by": "agent"}
|
|
284
|
+
if reason:
|
|
285
|
+
data["reason"] = reason
|
|
286
|
+
return await _patch(f"/v1/host/commitments/{commitment_id}", data)
|
|
287
|
+
|
|
288
|
+
@mcp.tool(annotations={"readOnlyHint": False, "idempotentHint": False})
|
|
289
|
+
async def apsimo_remember_fact(
|
|
290
|
+
fact: str,
|
|
291
|
+
contact_id: str | None = None,
|
|
292
|
+
source: str | None = None,
|
|
293
|
+
confidence: float = 0.8,
|
|
294
|
+
) -> dict:
|
|
295
|
+
"""Store a fact about a person, project, or concept. Call when the user states a preference, makes a decision, or reveals context worth remembering. Source can be: told_by_contact, told_to_contact, shared_context, inferred."""
|
|
296
|
+
cid, err = _require_contact(contact_id)
|
|
297
|
+
if err:
|
|
298
|
+
return err
|
|
299
|
+
data: dict[str, Any] = {
|
|
300
|
+
"contact_id": cid,
|
|
301
|
+
"fact": fact,
|
|
302
|
+
"confidence": confidence,
|
|
303
|
+
}
|
|
304
|
+
if source:
|
|
305
|
+
data["source"] = source
|
|
306
|
+
return await _post("/v1/host/mind/facts", data)
|
|
307
|
+
|
|
308
|
+
@mcp.tool(annotations={"readOnlyHint": False, "idempotentHint": True})
|
|
309
|
+
async def apsimo_forget_sources(source_ids: list[str], contact_id: str | None = None) -> dict:
|
|
310
|
+
"""Forget selected canonical turn sources by their recalled source IDs.
|
|
311
|
+
|
|
312
|
+
Removes exact checkpoint copies and linked graph summaries. Does not
|
|
313
|
+
claim to erase native transcripts, backups or legacy unlinked facts.
|
|
314
|
+
Use only for the user's explicit forget request; never guess IDs.
|
|
315
|
+
"""
|
|
316
|
+
cid, err = _require_contact(contact_id)
|
|
317
|
+
if err:
|
|
318
|
+
return err
|
|
319
|
+
return await _post("/v1/host/memory/sources/forget", {"contact_id": cid, "source_ids": source_ids})
|
|
320
|
+
|
|
321
|
+
@mcp.tool(annotations={"readOnlyHint": False, "idempotentHint": True})
|
|
322
|
+
async def apsimo_forget_fact(
|
|
323
|
+
fact_id: str,
|
|
324
|
+
) -> dict:
|
|
325
|
+
"""Remove an outdated or incorrect fact. Call when you learn a fact was wrong, a preference changes, or context is stale."""
|
|
326
|
+
status, err_msg = await _delete(f"/v1/host/mind/facts/{fact_id}")
|
|
327
|
+
if status == 204 or status == 200:
|
|
328
|
+
return {"deleted": True}
|
|
329
|
+
return {"error": "delete_failed", "message": f"Status {status}: {err_msg}"}
|
|
330
|
+
|
|
331
|
+
@mcp.tool(annotations={"readOnlyHint": False, "idempotentHint": False})
|
|
332
|
+
async def apsimo_record_affect(
|
|
333
|
+
valence: float,
|
|
334
|
+
trigger: str,
|
|
335
|
+
contact_id: str | None = None,
|
|
336
|
+
arousal: float = 0.5,
|
|
337
|
+
) -> dict:
|
|
338
|
+
"""Legacy explicit affect record. Retained for compatibility; do not infer numeric mood from ordinary conversation or use it to grant authority."""
|
|
339
|
+
cid, err = _require_contact(contact_id)
|
|
340
|
+
if err:
|
|
341
|
+
return err
|
|
342
|
+
data = {
|
|
343
|
+
"contact_id": cid,
|
|
344
|
+
"valence": valence,
|
|
345
|
+
"arousal": arousal,
|
|
346
|
+
"trigger": trigger,
|
|
347
|
+
}
|
|
348
|
+
return await _post("/v1/host/affect/events", data)
|
|
349
|
+
|
|
350
|
+
@mcp.tool(annotations={"readOnlyHint": False, "idempotentHint": False})
|
|
351
|
+
async def apsimo_record_surprise(
|
|
352
|
+
observation: str,
|
|
353
|
+
expected: str | None = None,
|
|
354
|
+
surprise_score: float | None = None,
|
|
355
|
+
pattern_id: str | None = None,
|
|
356
|
+
context: dict[str, Any] | None = None,
|
|
357
|
+
) -> dict:
|
|
358
|
+
"""Record something unexpected. Call when something doesn't behave as expected, a bug is weirder than anticipated, or assumptions are violated. Observation should include what actually happened vs what was expected."""
|
|
359
|
+
data: dict[str, Any] = {
|
|
360
|
+
"observation": observation,
|
|
361
|
+
}
|
|
362
|
+
if expected is not None:
|
|
363
|
+
data["expected"] = expected
|
|
364
|
+
if surprise_score is not None:
|
|
365
|
+
data["surprise_score"] = surprise_score
|
|
366
|
+
if pattern_id:
|
|
367
|
+
data["pattern_id"] = pattern_id
|
|
368
|
+
if context is not None:
|
|
369
|
+
data["context"] = context
|
|
370
|
+
return await _post("/v1/host/surprises", data)
|
|
371
|
+
|
|
372
|
+
@mcp.tool(annotations={"readOnlyHint": False, "idempotentHint": True})
|
|
373
|
+
async def apsimo_task_complete(
|
|
374
|
+
task_id: str,
|
|
375
|
+
) -> dict:
|
|
376
|
+
"""Mark a task as completed. Call when an initiative mentions a task that's done."""
|
|
377
|
+
return await _post(f"/v1/host/tasks/{task_id}/complete", {})
|
|
378
|
+
|
|
379
|
+
@mcp.tool(annotations={"readOnlyHint": False, "idempotentHint": True})
|
|
380
|
+
async def apsimo_task_snooze(
|
|
381
|
+
task_id: str,
|
|
382
|
+
hours: int = 24,
|
|
383
|
+
reason: str = "",
|
|
384
|
+
) -> dict:
|
|
385
|
+
"""Snooze a task — don't generate initiatives for it for N hours (1-168). Call when a task is valid but not actionable right now."""
|
|
386
|
+
return await _post(f"/v1/host/tasks/{task_id}/snooze", {"hours": hours, "reason": reason})
|
|
387
|
+
|
|
388
|
+
@mcp.tool(annotations={"readOnlyHint": False, "idempotentHint": True})
|
|
389
|
+
async def apsimo_task_dismiss(
|
|
390
|
+
task_id: str,
|
|
391
|
+
reason: str = "stale",
|
|
392
|
+
) -> dict:
|
|
393
|
+
"""Dismiss a task as no longer relevant. Reason can be: stale, completed, abandoned, not_applicable. Call when a task is clearly outdated or irrelevant."""
|
|
394
|
+
return await _post(f"/v1/host/tasks/{task_id}/dismiss", {"reason": reason})
|
|
395
|
+
|
|
396
|
+
@mcp.tool(annotations={"readOnlyHint": False, "idempotentHint": True})
|
|
397
|
+
async def apsimo_initiative_feedback(
|
|
398
|
+
initiative_id: str,
|
|
399
|
+
action: str,
|
|
400
|
+
details: dict[str, Any] | None = None,
|
|
401
|
+
) -> dict:
|
|
402
|
+
"""Provide feedback on how an initiative was handled. Action can be: acknowledged, actioned, dismissed, snoozed. Call after handling a apsimo_initiative."""
|
|
403
|
+
data: dict[str, Any] = {"action": action}
|
|
404
|
+
if details:
|
|
405
|
+
data["details"] = details
|
|
406
|
+
return await _post(f"/v1/host/initiatives/{initiative_id}/respond", data)
|
|
407
|
+
|
|
408
|
+
# --- Resources ---
|
|
409
|
+
|
|
410
|
+
@mcp.resource("colony://status")
|
|
411
|
+
async def status_resource() -> dict:
|
|
412
|
+
"""Current Apsimo system status."""
|
|
413
|
+
return await _get("/v1/host/health")
|
|
414
|
+
|
|
415
|
+
@mcp.resource("colony://commitments")
|
|
416
|
+
async def commitments_resource() -> dict:
|
|
417
|
+
"""All active commitments (pending + overdue)."""
|
|
418
|
+
return await _get("/v1/host/commitments", params={"status": "pending"})
|
|
419
|
+
|
|
420
|
+
@mcp.resource("colony://affect/{contact_id}")
|
|
421
|
+
async def affect_resource(contact_id: str) -> dict:
|
|
422
|
+
"""Current affect state for a contact."""
|
|
423
|
+
return await _get(f"/v1/host/affect/state/{contact_id}")
|
|
424
|
+
|
|
425
|
+
@mcp.resource("colony://facts/{contact_id}")
|
|
426
|
+
async def facts_resource(contact_id: str) -> dict:
|
|
427
|
+
"""Known facts for a contact."""
|
|
428
|
+
return await _get("/v1/host/mind/facts", params={"contact_id": contact_id})
|
|
429
|
+
|
|
430
|
+
@mcp.resource("colony://world/entities")
|
|
431
|
+
async def world_resource() -> dict:
|
|
432
|
+
"""Top entities in the world model."""
|
|
433
|
+
return await _post("/v1/host/world/entities/query", {"identity": {"host_id": "mcp"}, "query": "", "limit": 10})
|
|
434
|
+
|
|
435
|
+
@mcp.resource("colony://surprises/unresolved")
|
|
436
|
+
async def surprises_resource() -> dict:
|
|
437
|
+
"""Current unresolved surprises."""
|
|
438
|
+
return await _get("/v1/host/surprises", params={"resolved": False})
|
|
439
|
+
|
|
440
|
+
# --- Prompts ---
|
|
441
|
+
|
|
442
|
+
@mcp.prompt()
|
|
443
|
+
async def daily_briefing() -> str:
|
|
444
|
+
"""Review commitments, source-backed context, and surprises."""
|
|
445
|
+
cid = _contact_id()
|
|
446
|
+
if not cid:
|
|
447
|
+
return "Set APSIMO_MCP_CONTACT_ID to get your daily briefing."
|
|
448
|
+
return (
|
|
449
|
+
f"Review the following for {cid}:\n"
|
|
450
|
+
"1. Check apsimo_check_commitments for pending and overdue items\n"
|
|
451
|
+
"2. Check apsimo_get_context for relevant source-backed context\n"
|
|
452
|
+
"3. Check apsimo_get_patterns for workflow patterns\n"
|
|
453
|
+
"4. Check colony://surprises/unresolved for anything unexpected\n"
|
|
454
|
+
"Then prioritize by deadlines, explicit commitments, and relevant evidence."
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
@mcp.prompt()
|
|
458
|
+
async def pre_task() -> str:
|
|
459
|
+
"""Before starting a task, check commitments and facts about relevant people and components."""
|
|
460
|
+
cid = _contact_id()
|
|
461
|
+
if not cid:
|
|
462
|
+
return "Set APSIMO_MCP_CONTACT_ID to use pre-task context."
|
|
463
|
+
return (
|
|
464
|
+
f"Before starting this task:\n"
|
|
465
|
+
f"1. Call apsimo_check_commitments to see what {cid} has pending\n"
|
|
466
|
+
f"2. Call apsimo_lookup_facts to recall relevant context\n"
|
|
467
|
+
f"3. Call apsimo_get_context for source-backed appraisals and working perspective\n"
|
|
468
|
+
"Use this information to prioritize and tailor your approach."
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
@mcp.prompt()
|
|
472
|
+
async def post_task() -> str:
|
|
473
|
+
"""After completing a task, record what happened and check off commitments."""
|
|
474
|
+
cid = _contact_id()
|
|
475
|
+
if not cid:
|
|
476
|
+
return "Set APSIMO_MCP_CONTACT_ID to use post-task recording."
|
|
477
|
+
return (
|
|
478
|
+
f"I just completed a task. For {cid}:\n"
|
|
479
|
+
"1. If there was a commitment for this task, call apsimo_fulfill_commitment\n"
|
|
480
|
+
"2. If anything unexpected happened, call apsimo_record_surprise\n"
|
|
481
|
+
"3. If I learned something worth remembering, call apsimo_remember_fact with its source.\n"
|
|
482
|
+
"Keep observations separate from inferred opinions; do not manufacture a mood score."
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
return mcp
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def run_stdio() -> None:
|
|
489
|
+
"""Run the MCP server using stdio transport."""
|
|
490
|
+
server = create_server()
|
|
491
|
+
server.run(transport="stdio")
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def run_http(host: str = "127.0.0.1", port: int = 7778) -> None:
|
|
495
|
+
"""Run the MCP server using streamable HTTP transport."""
|
|
496
|
+
server = create_server()
|
|
497
|
+
server.run(transport="streamable-http", host=host, port=port)
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
if __name__ == "__main__":
|
|
501
|
+
run_stdio()
|
apsimo/migrations.py
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""Generic schema migration runner for Colony SQLite stores.
|
|
2
|
+
|
|
3
|
+
Discovers numbered .sql files in a migrations directory, tracks which
|
|
4
|
+
have been applied via a ``schema_version`` table in the target database,
|
|
5
|
+
and applies unapplied ones in order. Works with both sync (sqlite3)
|
|
6
|
+
and async (aiosqlite) connections.
|
|
7
|
+
|
|
8
|
+
Usage (sync)::
|
|
9
|
+
|
|
10
|
+
import sqlite3
|
|
11
|
+
conn = sqlite3.connect("colony-channels.db")
|
|
12
|
+
applied = run_migrations_sync(conn, Path("channels/migrations"))
|
|
13
|
+
|
|
14
|
+
Usage (async)::
|
|
15
|
+
|
|
16
|
+
import aiosqlite
|
|
17
|
+
db = await aiosqlite.connect("colony-contacts.db")
|
|
18
|
+
applied = await run_migrations(db, Path("contacts/migrations"))
|
|
19
|
+
|
|
20
|
+
Migration filenames must match ``NNN_description.sql`` (e.g.
|
|
21
|
+
``001_initial_schema.sql``). The numeric prefix determines order.
|
|
22
|
+
Each file is executed as a single ``executescript`` call inside its own
|
|
23
|
+
transaction -- if it fails, the version is NOT recorded and startup
|
|
24
|
+
aborts with a clear error.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import logging
|
|
30
|
+
import re
|
|
31
|
+
import sqlite3
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
from typing import TYPE_CHECKING
|
|
34
|
+
|
|
35
|
+
if TYPE_CHECKING:
|
|
36
|
+
import aiosqlite
|
|
37
|
+
|
|
38
|
+
logger = logging.getLogger(__name__)
|
|
39
|
+
|
|
40
|
+
_VERSION_DDL = """\
|
|
41
|
+
CREATE TABLE IF NOT EXISTS schema_version (
|
|
42
|
+
version TEXT PRIMARY KEY,
|
|
43
|
+
filename TEXT NOT NULL,
|
|
44
|
+
applied_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
|
|
45
|
+
);
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
_MIGRATION_RE = re.compile(r"^(\d+)_.+\.sql$")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _discover(migrations_dir: Path) -> list[tuple[str, Path]]:
|
|
52
|
+
"""Return ``(version, path)`` pairs sorted by numeric prefix."""
|
|
53
|
+
found: list[tuple[int, str, Path]] = []
|
|
54
|
+
if not migrations_dir.is_dir():
|
|
55
|
+
return []
|
|
56
|
+
for p in migrations_dir.iterdir():
|
|
57
|
+
m = _MIGRATION_RE.match(p.name)
|
|
58
|
+
if m:
|
|
59
|
+
found.append((int(m.group(1)), m.group(1).zfill(3), p))
|
|
60
|
+
found.sort(key=lambda t: t[0])
|
|
61
|
+
return [(v, p) for _, v, p in found]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# ── Sync API (sqlite3) ──────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def run_migrations_sync(
|
|
68
|
+
conn: sqlite3.Connection,
|
|
69
|
+
migrations_dir: Path,
|
|
70
|
+
*,
|
|
71
|
+
table: str = "schema_version",
|
|
72
|
+
) -> list[str]:
|
|
73
|
+
"""Apply unapplied migrations using a sync sqlite3 connection.
|
|
74
|
+
|
|
75
|
+
Returns the list of newly applied version strings.
|
|
76
|
+
"""
|
|
77
|
+
ddl = _VERSION_DDL.replace("schema_version", table)
|
|
78
|
+
conn.executescript(ddl)
|
|
79
|
+
conn.commit()
|
|
80
|
+
|
|
81
|
+
cur = conn.execute(f"SELECT version FROM {table}") # noqa: S608
|
|
82
|
+
applied = {row[0] for row in cur.fetchall()}
|
|
83
|
+
|
|
84
|
+
available = _discover(migrations_dir)
|
|
85
|
+
newly_applied: list[str] = []
|
|
86
|
+
|
|
87
|
+
for version, path in available:
|
|
88
|
+
if version in applied:
|
|
89
|
+
continue
|
|
90
|
+
sql = path.read_text()
|
|
91
|
+
logger.info("Applying migration %s (%s)", version, path.name)
|
|
92
|
+
try:
|
|
93
|
+
conn.executescript(sql)
|
|
94
|
+
conn.execute(
|
|
95
|
+
f"INSERT INTO {table}(version, filename) VALUES (?, ?)", # noqa: S608
|
|
96
|
+
(version, path.name),
|
|
97
|
+
)
|
|
98
|
+
conn.commit()
|
|
99
|
+
except Exception:
|
|
100
|
+
logger.error("Migration %s failed (%s)", version, path.name)
|
|
101
|
+
raise
|
|
102
|
+
newly_applied.append(version)
|
|
103
|
+
|
|
104
|
+
if newly_applied:
|
|
105
|
+
logger.info(
|
|
106
|
+
"Applied %d migration(s): %s",
|
|
107
|
+
len(newly_applied),
|
|
108
|
+
", ".join(newly_applied),
|
|
109
|
+
)
|
|
110
|
+
return newly_applied
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# ── Async API (aiosqlite) ───────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
async def run_migrations(
|
|
117
|
+
db: "aiosqlite.Connection",
|
|
118
|
+
migrations_dir: Path,
|
|
119
|
+
*,
|
|
120
|
+
table: str = "schema_version",
|
|
121
|
+
) -> list[str]:
|
|
122
|
+
"""Apply unapplied migrations using an async aiosqlite connection.
|
|
123
|
+
|
|
124
|
+
Returns the list of newly applied version strings.
|
|
125
|
+
"""
|
|
126
|
+
ddl = _VERSION_DDL.replace("schema_version", table)
|
|
127
|
+
await db.executescript(ddl)
|
|
128
|
+
await db.commit()
|
|
129
|
+
|
|
130
|
+
async with db.execute(f"SELECT version FROM {table}") as cur: # noqa: S608
|
|
131
|
+
rows = await cur.fetchall()
|
|
132
|
+
applied = {row[0] for row in rows}
|
|
133
|
+
|
|
134
|
+
available = _discover(migrations_dir)
|
|
135
|
+
newly_applied: list[str] = []
|
|
136
|
+
|
|
137
|
+
for version, path in available:
|
|
138
|
+
if version in applied:
|
|
139
|
+
continue
|
|
140
|
+
sql = path.read_text()
|
|
141
|
+
logger.info("Applying migration %s (%s)", version, path.name)
|
|
142
|
+
try:
|
|
143
|
+
await db.executescript(sql)
|
|
144
|
+
await db.execute(
|
|
145
|
+
f"INSERT INTO {table}(version, filename) VALUES (?, ?)", # noqa: S608
|
|
146
|
+
(version, path.name),
|
|
147
|
+
)
|
|
148
|
+
await db.commit()
|
|
149
|
+
except Exception:
|
|
150
|
+
logger.error("Migration %s failed (%s)", version, path.name)
|
|
151
|
+
raise
|
|
152
|
+
newly_applied.append(version)
|
|
153
|
+
|
|
154
|
+
if newly_applied:
|
|
155
|
+
logger.info(
|
|
156
|
+
"Applied %d migration(s): %s",
|
|
157
|
+
len(newly_applied),
|
|
158
|
+
", ".join(newly_applied),
|
|
159
|
+
)
|
|
160
|
+
return newly_applied
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def applied_versions_sync(
|
|
164
|
+
conn: sqlite3.Connection,
|
|
165
|
+
*,
|
|
166
|
+
table: str = "schema_version",
|
|
167
|
+
) -> set[str]:
|
|
168
|
+
"""Return the set of already-applied version strings (sync)."""
|
|
169
|
+
try:
|
|
170
|
+
cur = conn.execute(f"SELECT version FROM {table}") # noqa: S608
|
|
171
|
+
return {row[0] for row in cur.fetchall()}
|
|
172
|
+
except sqlite3.OperationalError:
|
|
173
|
+
return set()
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
async def applied_versions(
|
|
177
|
+
db: "aiosqlite.Connection",
|
|
178
|
+
*,
|
|
179
|
+
table: str = "schema_version",
|
|
180
|
+
) -> set[str]:
|
|
181
|
+
"""Return the set of already-applied version strings (async)."""
|
|
182
|
+
try:
|
|
183
|
+
async with db.execute(f"SELECT version FROM {table}") as cur: # noqa: S608
|
|
184
|
+
rows = await cur.fetchall()
|
|
185
|
+
return {row[0] for row in rows}
|
|
186
|
+
except Exception:
|
|
187
|
+
return set()
|