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/tools/handlers.py
ADDED
|
@@ -0,0 +1,943 @@
|
|
|
1
|
+
"""Tool handlers for Colony-native server-side execution.
|
|
2
|
+
|
|
3
|
+
Each handler is an async function that receives the tool arguments
|
|
4
|
+
and returns a string result. Handlers have access to the SubsystemRegistry
|
|
5
|
+
for calling Colony's intelligence systems.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
from typing import TYPE_CHECKING, Any
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from apsimo.autonomy.registry import SubsystemRegistry
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _as_int(value: Any, default: int) -> int:
|
|
21
|
+
"""Coerce a tool-supplied value to int.
|
|
22
|
+
|
|
23
|
+
LLM tool calls frequently deliver numbers as strings ("5") or floats
|
|
24
|
+
(10.0). Downstream stores (Neo4j LIMIT, list slicing) require a real int,
|
|
25
|
+
so normalise here rather than crashing deep in a query.
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
return int(value)
|
|
29
|
+
except (TypeError, ValueError):
|
|
30
|
+
return default
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
async def handle_memory_search(
|
|
34
|
+
args: dict[str, Any],
|
|
35
|
+
registry: SubsystemRegistry,
|
|
36
|
+
) -> str:
|
|
37
|
+
"""Search Colony's memory graph."""
|
|
38
|
+
query = args.get("query", "")
|
|
39
|
+
person_id = args.get("person_id")
|
|
40
|
+
limit = _as_int(args.get("limit", 5), 5)
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
graph = registry.graph
|
|
44
|
+
if graph is None:
|
|
45
|
+
return json.dumps({"error": "Memory graph not wired", "status": "unavailable"})
|
|
46
|
+
|
|
47
|
+
# ColonyGraph exposes semantic memory retrieval as recall(); there is
|
|
48
|
+
# no search() method (the old name was API drift). recall does its own
|
|
49
|
+
# ANN + strength decay and returns dicts annotated with relevance.
|
|
50
|
+
results = await graph.recall(
|
|
51
|
+
query=query,
|
|
52
|
+
limit=limit,
|
|
53
|
+
person_id=person_id,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def _ts(v: Any) -> Any:
|
|
57
|
+
# Neo4j hydration returns neo4j.time.DateTime, which json can't
|
|
58
|
+
# serialise. Normalise any date-like value to an ISO string.
|
|
59
|
+
return v.isoformat() if hasattr(v, "isoformat") else v
|
|
60
|
+
|
|
61
|
+
memories = [
|
|
62
|
+
{
|
|
63
|
+
"content": (m.get("content") or "")[:200],
|
|
64
|
+
"timestamp": _ts(m.get("created_at") or m.get("timestamp")),
|
|
65
|
+
"relevance": m.get("relevance", m.get("score", 0)),
|
|
66
|
+
}
|
|
67
|
+
for m in results[:limit]
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
# default=str is a belt-and-suspenders guard for any other non-JSON
|
|
71
|
+
# types (e.g. stray DateTime/Decimal) surfacing from the graph.
|
|
72
|
+
return json.dumps({
|
|
73
|
+
"query": query,
|
|
74
|
+
"count": len(memories),
|
|
75
|
+
"memories": memories,
|
|
76
|
+
}, default=str)
|
|
77
|
+
except Exception as e:
|
|
78
|
+
logger.error("colony_memory_search failed: %s", e)
|
|
79
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
async def handle_get_relationship(
|
|
83
|
+
args: dict[str, Any],
|
|
84
|
+
registry: SubsystemRegistry,
|
|
85
|
+
) -> str:
|
|
86
|
+
"""Get relationship info for a contact."""
|
|
87
|
+
contact_id = args.get("contact_id", "")
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
contacts = registry.contacts
|
|
91
|
+
if contacts is None:
|
|
92
|
+
return json.dumps({"error": "Contacts store not wired", "status": "unavailable"})
|
|
93
|
+
|
|
94
|
+
contact = await contacts.get(contact_id)
|
|
95
|
+
if contact is None:
|
|
96
|
+
return json.dumps({
|
|
97
|
+
"contact_id": contact_id,
|
|
98
|
+
"status": "not_found",
|
|
99
|
+
"tier": "stranger",
|
|
100
|
+
"score": 0,
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
return json.dumps({
|
|
104
|
+
"contact_id": contact_id,
|
|
105
|
+
"name": contact.get("name"),
|
|
106
|
+
"tier": contact.get("tier", "stranger"),
|
|
107
|
+
"score": contact.get("score", 0),
|
|
108
|
+
"interaction_count": contact.get("interaction_count", 0),
|
|
109
|
+
"last_interaction": contact.get("last_interaction"),
|
|
110
|
+
})
|
|
111
|
+
except Exception as e:
|
|
112
|
+
logger.error("colony_get_relationship failed: %s", e)
|
|
113
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
async def handle_list_goals(
|
|
117
|
+
args: dict[str, Any],
|
|
118
|
+
registry: SubsystemRegistry,
|
|
119
|
+
) -> str:
|
|
120
|
+
"""List the user's goals."""
|
|
121
|
+
person_id = args.get("person_id")
|
|
122
|
+
status = args.get("status", "active")
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
goals = registry.goals
|
|
126
|
+
if goals is None:
|
|
127
|
+
return json.dumps({"error": "Goals store not wired", "status": "unavailable"})
|
|
128
|
+
|
|
129
|
+
# GoalEngine is sync and exposes list_goals(status, limit, offset)
|
|
130
|
+
# returning Goal objects — there is no `list` method and no person_id
|
|
131
|
+
# filter (the old call raised AttributeError on every invocation).
|
|
132
|
+
goal_list = goals.list_goals(status=status, limit=50)
|
|
133
|
+
|
|
134
|
+
return json.dumps({
|
|
135
|
+
"count": len(goal_list or []),
|
|
136
|
+
"goals": [
|
|
137
|
+
{
|
|
138
|
+
"id": getattr(g, "goal_id", None),
|
|
139
|
+
"title": getattr(g, "title", ""),
|
|
140
|
+
"status": getattr(getattr(g, "status", None), "value",
|
|
141
|
+
str(getattr(g, "status", ""))),
|
|
142
|
+
"progress": float(getattr(g, "progress_pct", 0.0) or 0.0),
|
|
143
|
+
}
|
|
144
|
+
for g in (goal_list or [])
|
|
145
|
+
],
|
|
146
|
+
})
|
|
147
|
+
except Exception as e:
|
|
148
|
+
logger.error("colony_list_goals failed: %s", e)
|
|
149
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
async def handle_get_briefing(
|
|
153
|
+
args: dict[str, Any],
|
|
154
|
+
registry: SubsystemRegistry,
|
|
155
|
+
) -> str:
|
|
156
|
+
"""Get a briefing for a contact."""
|
|
157
|
+
contact_id = args.get("contact_id", "")
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
briefings = registry.briefings
|
|
161
|
+
if briefings is None:
|
|
162
|
+
return json.dumps({"error": "Briefings engine not wired", "status": "unavailable"})
|
|
163
|
+
|
|
164
|
+
# API drift fix: the engine has no generate(); expose recent + pending
|
|
165
|
+
# briefings (read-only, no LLM cost, no delivery side effects).
|
|
166
|
+
from dataclasses import asdict, is_dataclass
|
|
167
|
+
|
|
168
|
+
def _plain(b):
|
|
169
|
+
try:
|
|
170
|
+
if is_dataclass(b):
|
|
171
|
+
return asdict(b)
|
|
172
|
+
if hasattr(b, "model_dump"):
|
|
173
|
+
return b.model_dump()
|
|
174
|
+
return b.__dict__
|
|
175
|
+
except Exception:
|
|
176
|
+
return {"repr": str(b)}
|
|
177
|
+
|
|
178
|
+
recent = [_plain(b) for b in briefings.get_recent(limit=3)]
|
|
179
|
+
pending = [_plain(b) for b in briefings.get_pending()]
|
|
180
|
+
return json.dumps(
|
|
181
|
+
{"recent": recent, "pending": pending, "contact_id": contact_id},
|
|
182
|
+
default=str,
|
|
183
|
+
)
|
|
184
|
+
except Exception as e:
|
|
185
|
+
logger.error("colony_get_briefing failed: %s", e)
|
|
186
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
async def handle_record_insight(
|
|
190
|
+
args: dict[str, Any],
|
|
191
|
+
registry: SubsystemRegistry,
|
|
192
|
+
) -> str:
|
|
193
|
+
"""Record an insight to memory."""
|
|
194
|
+
insight_type = args.get("insight_type", "fact")
|
|
195
|
+
content = args.get("content", "")
|
|
196
|
+
confidence = args.get("confidence", 0.7)
|
|
197
|
+
person_id = args.get("person_id")
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
graph = registry.graph
|
|
201
|
+
if graph is None:
|
|
202
|
+
return json.dumps({"error": "Memory graph not wired", "status": "unavailable"})
|
|
203
|
+
|
|
204
|
+
insight_id = await graph.record_insight(
|
|
205
|
+
insight_type=insight_type,
|
|
206
|
+
content=content,
|
|
207
|
+
confidence=confidence,
|
|
208
|
+
person_id=person_id,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
return json.dumps({
|
|
212
|
+
"status": "recorded",
|
|
213
|
+
"insight_id": insight_id,
|
|
214
|
+
"type": insight_type,
|
|
215
|
+
})
|
|
216
|
+
except Exception as e:
|
|
217
|
+
logger.error("colony_record_insight failed: %s", e)
|
|
218
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
async def handle_query_entities(
|
|
222
|
+
args: dict[str, Any],
|
|
223
|
+
registry: SubsystemRegistry,
|
|
224
|
+
) -> str:
|
|
225
|
+
"""Query the world model for entities."""
|
|
226
|
+
query = args.get("query", "")
|
|
227
|
+
entity_type = args.get("entity_type", "all")
|
|
228
|
+
limit = _as_int(args.get("limit", 10), 10)
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
world = registry.world_model
|
|
232
|
+
if world is None:
|
|
233
|
+
return json.dumps({"error": "World model not wired", "status": "unavailable"})
|
|
234
|
+
|
|
235
|
+
# WorldModelStore searches entities via find_entities() (there is no
|
|
236
|
+
# query() method -- API drift). "all" (the tool default) means no type
|
|
237
|
+
# filter. Results are BaseEntity dataclasses, so read fields by
|
|
238
|
+
# attribute (with a dict fallback) rather than .get().
|
|
239
|
+
etype = None if entity_type in (None, "", "all") else entity_type
|
|
240
|
+
entities = await world.find_entities(
|
|
241
|
+
query=query,
|
|
242
|
+
entity_type=etype,
|
|
243
|
+
limit=limit,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
def _field(e: Any, name: str, default: Any = None) -> Any:
|
|
247
|
+
if isinstance(e, dict):
|
|
248
|
+
return e.get(name, default)
|
|
249
|
+
return getattr(e, name, default)
|
|
250
|
+
|
|
251
|
+
return json.dumps({
|
|
252
|
+
"count": len(entities),
|
|
253
|
+
"entities": [
|
|
254
|
+
{
|
|
255
|
+
"id": _field(e, "id"),
|
|
256
|
+
"name": _field(e, "name"),
|
|
257
|
+
"type": _field(e, "entity_type"),
|
|
258
|
+
}
|
|
259
|
+
for e in entities
|
|
260
|
+
],
|
|
261
|
+
}, default=str)
|
|
262
|
+
except Exception as e:
|
|
263
|
+
logger.error("colony_query_entities failed: %s", e)
|
|
264
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
async def handle_start_research(
|
|
268
|
+
args: dict[str, Any],
|
|
269
|
+
registry: SubsystemRegistry,
|
|
270
|
+
) -> str:
|
|
271
|
+
"""Start a background research task."""
|
|
272
|
+
topic = args.get("topic", "")
|
|
273
|
+
depth = args.get("depth", "standard")
|
|
274
|
+
|
|
275
|
+
try:
|
|
276
|
+
research = registry.research
|
|
277
|
+
if research is None:
|
|
278
|
+
return json.dumps({"error": "Research pipeline not wired", "status": "unavailable"})
|
|
279
|
+
|
|
280
|
+
# The ResearchPipeline runs its stages inline and returns a finished
|
|
281
|
+
# artifact (the previous `.start()` method never existed -> API drift).
|
|
282
|
+
run = await research.run(goal=topic)
|
|
283
|
+
artifact = getattr(run, "artifact", None)
|
|
284
|
+
content = getattr(artifact, "content", "") if artifact else ""
|
|
285
|
+
status = getattr(run, "status", "completed")
|
|
286
|
+
status = getattr(status, "value", None) or str(status)
|
|
287
|
+
return json.dumps({
|
|
288
|
+
"status": status,
|
|
289
|
+
"run_id": getattr(run, "id", None),
|
|
290
|
+
"topic": topic,
|
|
291
|
+
"finding": (content or "")[:1500],
|
|
292
|
+
"word_count": getattr(artifact, "word_count", 0) if artifact else 0,
|
|
293
|
+
})
|
|
294
|
+
except Exception as e:
|
|
295
|
+
logger.error("colony_start_research failed: %s", e)
|
|
296
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
async def handle_discover_connections(
|
|
300
|
+
args: dict[str, Any],
|
|
301
|
+
registry: SubsystemRegistry,
|
|
302
|
+
) -> str:
|
|
303
|
+
"""Discover non-obvious connections."""
|
|
304
|
+
entity_id = args.get("entity_id")
|
|
305
|
+
min_novelty = args.get("min_novelty", 0.3)
|
|
306
|
+
|
|
307
|
+
try:
|
|
308
|
+
# The real accessor is connection_discoverer (registry.synthesis never
|
|
309
|
+
# existed -> API drift), and the method is discover_connections().
|
|
310
|
+
discoverer = registry.connection_discoverer
|
|
311
|
+
if discoverer is None:
|
|
312
|
+
return json.dumps({"error": "Connection discoverer not wired", "status": "unavailable"})
|
|
313
|
+
|
|
314
|
+
connections = await discoverer.discover_connections(
|
|
315
|
+
person_id=entity_id,
|
|
316
|
+
min_confidence=float(min_novelty),
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
def _f(c, *names, default=None):
|
|
320
|
+
for n in names:
|
|
321
|
+
v = c.get(n) if isinstance(c, dict) else getattr(c, n, None)
|
|
322
|
+
if v is not None:
|
|
323
|
+
return v
|
|
324
|
+
return default
|
|
325
|
+
|
|
326
|
+
return json.dumps({
|
|
327
|
+
"count": len(connections),
|
|
328
|
+
"connections": [
|
|
329
|
+
{
|
|
330
|
+
"from": _f(c, "source", "from", "source_id"),
|
|
331
|
+
"to": _f(c, "target", "to", "target_id"),
|
|
332
|
+
"type": _f(c, "connection_type", "type", "relationship_type"),
|
|
333
|
+
"confidence": _f(c, "confidence", "novelty", default=0),
|
|
334
|
+
}
|
|
335
|
+
for c in connections[:20]
|
|
336
|
+
],
|
|
337
|
+
}, default=str)
|
|
338
|
+
except Exception as e:
|
|
339
|
+
logger.error("colony_discover_connections failed: %s", e)
|
|
340
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _iso_ts(epoch: Any) -> str:
|
|
344
|
+
try:
|
|
345
|
+
from datetime import datetime, timezone
|
|
346
|
+
return datetime.fromtimestamp(float(epoch), timezone.utc).isoformat()
|
|
347
|
+
except Exception:
|
|
348
|
+
return str(epoch)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
async def handle_list_boundaries(
|
|
352
|
+
args: dict[str, Any],
|
|
353
|
+
registry: SubsystemRegistry,
|
|
354
|
+
) -> str:
|
|
355
|
+
"""List the owner's active standing directives / boundaries."""
|
|
356
|
+
try:
|
|
357
|
+
dm = registry.directives
|
|
358
|
+
if dm is None:
|
|
359
|
+
return json.dumps({"error": "Directives not wired", "status": "unavailable"})
|
|
360
|
+
active = dm.active()
|
|
361
|
+
return json.dumps({
|
|
362
|
+
"count": len(active),
|
|
363
|
+
"boundaries": [
|
|
364
|
+
{
|
|
365
|
+
"id": d.id,
|
|
366
|
+
"polarity": d.polarity.value, # prohibit | require | prefer
|
|
367
|
+
"subject": d.subject,
|
|
368
|
+
"stated": d.raw_text or d.subject,
|
|
369
|
+
"since": _iso_ts(d.created_at),
|
|
370
|
+
}
|
|
371
|
+
for d in active
|
|
372
|
+
],
|
|
373
|
+
}, default=str)
|
|
374
|
+
except Exception as e:
|
|
375
|
+
logger.error("colony_list_boundaries failed: %s", e)
|
|
376
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
async def handle_recent_boundary_blocks(
|
|
380
|
+
args: dict[str, Any],
|
|
381
|
+
registry: SubsystemRegistry,
|
|
382
|
+
) -> str:
|
|
383
|
+
"""What autonomous actions were recently refused, and which boundary refused them."""
|
|
384
|
+
limit = _as_int(args.get("limit", 10), 10)
|
|
385
|
+
try:
|
|
386
|
+
dm = registry.directives
|
|
387
|
+
if dm is None:
|
|
388
|
+
return json.dumps({"error": "Directives not wired", "status": "unavailable"})
|
|
389
|
+
blocks = dm.guard.recent_blocks(limit=limit)
|
|
390
|
+
return json.dumps({
|
|
391
|
+
"count": len(blocks),
|
|
392
|
+
"blocks": [
|
|
393
|
+
{
|
|
394
|
+
"when": _iso_ts(b.get("ts")),
|
|
395
|
+
"action_kind": b.get("action_kind"),
|
|
396
|
+
"action": b.get("action_summary"),
|
|
397
|
+
"refused_because": b.get("directives"),
|
|
398
|
+
"directive_ids": b.get("directive_ids"),
|
|
399
|
+
}
|
|
400
|
+
for b in blocks
|
|
401
|
+
],
|
|
402
|
+
}, default=str)
|
|
403
|
+
except Exception as e:
|
|
404
|
+
logger.error("colony_recent_boundary_blocks failed: %s", e)
|
|
405
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
async def handle_flag_boundary_concern(
|
|
409
|
+
args: dict[str, Any],
|
|
410
|
+
registry: SubsystemRegistry,
|
|
411
|
+
) -> str:
|
|
412
|
+
"""Surface a CRITICAL finding about a boundaried subject (once, guarded).
|
|
413
|
+
|
|
414
|
+
Use ONLY when reflection over a subject the owner told you to leave alone
|
|
415
|
+
reveals something critical (security vulnerability, data loss, financial
|
|
416
|
+
risk). It is delivered at most once per boundary, clearly marked as
|
|
417
|
+
boundary-respecting.
|
|
418
|
+
"""
|
|
419
|
+
subject = args.get("subject", "")
|
|
420
|
+
finding = args.get("finding", "")
|
|
421
|
+
try:
|
|
422
|
+
severity = float(args.get("severity", 0.9))
|
|
423
|
+
except (TypeError, ValueError):
|
|
424
|
+
severity = 0.9
|
|
425
|
+
try:
|
|
426
|
+
dm = registry.directives
|
|
427
|
+
if dm is None:
|
|
428
|
+
return json.dumps({"error": "Directives not wired", "status": "unavailable"})
|
|
429
|
+
if not subject or not finding:
|
|
430
|
+
return json.dumps({"error": "subject and finding required", "status": "error"})
|
|
431
|
+
out = await dm.flag_critical(subject, finding, severity=severity)
|
|
432
|
+
return json.dumps(out, default=str)
|
|
433
|
+
except Exception as e:
|
|
434
|
+
logger.error("colony_flag_boundary_concern failed: %s", e)
|
|
435
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
async def handle_repo_list_files(
|
|
439
|
+
args: dict[str, Any],
|
|
440
|
+
registry: SubsystemRegistry,
|
|
441
|
+
) -> str:
|
|
442
|
+
"""List files in an owner-designated read-only repo mirror."""
|
|
443
|
+
try:
|
|
444
|
+
mirrors = registry.repo_mirrors
|
|
445
|
+
if mirrors is None:
|
|
446
|
+
return json.dumps({"error": "No repo mirrors configured", "status": "unavailable"})
|
|
447
|
+
return json.dumps(mirrors.list_files(
|
|
448
|
+
args.get("repo", ""), args.get("path", ""),
|
|
449
|
+
limit=_as_int(args.get("limit", 200), 200)), default=str)
|
|
450
|
+
except Exception as e:
|
|
451
|
+
logger.error("repo_list_files failed: %s", e)
|
|
452
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
async def handle_repo_read_file(
|
|
456
|
+
args: dict[str, Any],
|
|
457
|
+
registry: SubsystemRegistry,
|
|
458
|
+
) -> str:
|
|
459
|
+
"""Read a file from an owner-designated read-only repo mirror."""
|
|
460
|
+
try:
|
|
461
|
+
mirrors = registry.repo_mirrors
|
|
462
|
+
if mirrors is None:
|
|
463
|
+
return json.dumps({"error": "No repo mirrors configured", "status": "unavailable"})
|
|
464
|
+
return json.dumps(mirrors.read_file(
|
|
465
|
+
args.get("repo", ""), args.get("path", "")), default=str)
|
|
466
|
+
except Exception as e:
|
|
467
|
+
logger.error("repo_read_file failed: %s", e)
|
|
468
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
async def handle_repo_search(
|
|
472
|
+
args: dict[str, Any],
|
|
473
|
+
registry: SubsystemRegistry,
|
|
474
|
+
) -> str:
|
|
475
|
+
"""Search an owner-designated read-only repo mirror (git grep)."""
|
|
476
|
+
try:
|
|
477
|
+
mirrors = registry.repo_mirrors
|
|
478
|
+
if mirrors is None:
|
|
479
|
+
return json.dumps({"error": "No repo mirrors configured", "status": "unavailable"})
|
|
480
|
+
return json.dumps(mirrors.search(
|
|
481
|
+
args.get("repo", ""), args.get("query", ""),
|
|
482
|
+
glob=args.get("glob", "")), default=str)
|
|
483
|
+
except Exception as e:
|
|
484
|
+
logger.error("repo_search failed: %s", e)
|
|
485
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
async def handle_task_complete(
|
|
489
|
+
args: dict[str, Any],
|
|
490
|
+
registry: SubsystemRegistry,
|
|
491
|
+
) -> str:
|
|
492
|
+
"""Mark a task/goal as completed."""
|
|
493
|
+
task_id = args.get("task_id", "")
|
|
494
|
+
try:
|
|
495
|
+
goals = registry.goals
|
|
496
|
+
if goals is None:
|
|
497
|
+
return json.dumps({"error": "Goals store not wired", "status": "unavailable"})
|
|
498
|
+
await goals.complete(task_id)
|
|
499
|
+
return json.dumps({"status": "completed", "task_id": task_id})
|
|
500
|
+
except Exception as e:
|
|
501
|
+
logger.error("colony_task_complete failed: %s", e)
|
|
502
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
async def handle_task_snooze(
|
|
506
|
+
args: dict[str, Any],
|
|
507
|
+
registry: SubsystemRegistry,
|
|
508
|
+
) -> str:
|
|
509
|
+
"""Snooze a task for N hours."""
|
|
510
|
+
task_id = args.get("task_id", "")
|
|
511
|
+
hours = min(args.get("hours", 24), 168)
|
|
512
|
+
reason = args.get("reason", "")
|
|
513
|
+
try:
|
|
514
|
+
goals = registry.goals
|
|
515
|
+
if goals is None:
|
|
516
|
+
return json.dumps({"error": "Goals store not wired", "status": "unavailable"})
|
|
517
|
+
await goals.snooze(task_id, hours=hours, reason=reason)
|
|
518
|
+
return json.dumps({"status": "snoozed", "task_id": task_id, "hours": hours})
|
|
519
|
+
except Exception as e:
|
|
520
|
+
logger.error("colony_task_snooze failed: %s", e)
|
|
521
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
async def handle_task_dismiss(
|
|
525
|
+
args: dict[str, Any],
|
|
526
|
+
registry: SubsystemRegistry,
|
|
527
|
+
) -> str:
|
|
528
|
+
"""Dismiss a task as no longer relevant."""
|
|
529
|
+
task_id = args.get("task_id", "")
|
|
530
|
+
reason = args.get("reason", "stale")
|
|
531
|
+
try:
|
|
532
|
+
goals = registry.goals
|
|
533
|
+
if goals is None:
|
|
534
|
+
return json.dumps({"error": "Goals store not wired", "status": "unavailable"})
|
|
535
|
+
await goals.dismiss(task_id, reason=reason)
|
|
536
|
+
return json.dumps({"status": "dismissed", "task_id": task_id, "reason": reason})
|
|
537
|
+
except Exception as e:
|
|
538
|
+
logger.error("colony_task_dismiss failed: %s", e)
|
|
539
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
async def handle_initiative_feedback(
|
|
543
|
+
args: dict[str, Any],
|
|
544
|
+
registry: SubsystemRegistry,
|
|
545
|
+
) -> str:
|
|
546
|
+
"""Record feedback on an initiative (also drives per-type priority feedback)."""
|
|
547
|
+
initiative_id = args.get("initiative_id", "")
|
|
548
|
+
action = args.get("action", "acknowledged")
|
|
549
|
+
details = args.get("details", {})
|
|
550
|
+
try:
|
|
551
|
+
# Outcome-driven feedback (item 3b): nudge the initiative's TYPE
|
|
552
|
+
# multiplier so classes the owner acts on rise and dismissed ones decay.
|
|
553
|
+
try:
|
|
554
|
+
fb = getattr(registry, "feedback_store", None)
|
|
555
|
+
store = getattr(registry, "initiative_store", None)
|
|
556
|
+
itype = args.get("initiative_type") or details.get("type")
|
|
557
|
+
if not itype and store is not None:
|
|
558
|
+
init = store.get(initiative_id) if hasattr(store, "get") else None
|
|
559
|
+
itype = getattr(init, "type", None)
|
|
560
|
+
if fb is not None and itype:
|
|
561
|
+
fb.record(itype, action)
|
|
562
|
+
except Exception:
|
|
563
|
+
logger.debug("type feedback record failed", exc_info=True)
|
|
564
|
+
return json.dumps({
|
|
565
|
+
"status": "recorded",
|
|
566
|
+
"initiative_id": initiative_id,
|
|
567
|
+
"action": action,
|
|
568
|
+
})
|
|
569
|
+
except Exception as e:
|
|
570
|
+
logger.error("colony_initiative_feedback failed: %s", e)
|
|
571
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
# --- Cognition program handlers (items 1/3/4/7 + Amendment 1) ---
|
|
575
|
+
|
|
576
|
+
async def handle_list_projects(
|
|
577
|
+
args: dict[str, Any],
|
|
578
|
+
registry: SubsystemRegistry,
|
|
579
|
+
) -> str:
|
|
580
|
+
status = args.get("status", "all")
|
|
581
|
+
try:
|
|
582
|
+
engine = registry.project_engine
|
|
583
|
+
if engine is None:
|
|
584
|
+
return json.dumps({"error": "Projects not wired", "status": "unavailable"})
|
|
585
|
+
items = engine.store.list_projects(
|
|
586
|
+
status=None if status in ("", "all") else status, limit=30)
|
|
587
|
+
return json.dumps({
|
|
588
|
+
"count": len(items),
|
|
589
|
+
"projects": [
|
|
590
|
+
{"id": p.id, "title": p.title, "status": p.status,
|
|
591
|
+
"source": p.source, "reason": p.reason}
|
|
592
|
+
for p in items
|
|
593
|
+
],
|
|
594
|
+
}, default=str)
|
|
595
|
+
except Exception as e:
|
|
596
|
+
logger.error("list_projects failed: %s", e)
|
|
597
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
async def handle_project_status(
|
|
601
|
+
args: dict[str, Any],
|
|
602
|
+
registry: SubsystemRegistry,
|
|
603
|
+
) -> str:
|
|
604
|
+
try:
|
|
605
|
+
engine = registry.project_engine
|
|
606
|
+
if engine is None:
|
|
607
|
+
return json.dumps({"error": "Projects not wired", "status": "unavailable"})
|
|
608
|
+
out = engine.project_status(args.get("project_id", ""))
|
|
609
|
+
return json.dumps(out or {"error": "not_found"}, default=str)
|
|
610
|
+
except Exception as e:
|
|
611
|
+
logger.error("project_status failed: %s", e)
|
|
612
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
async def handle_create_project(
|
|
616
|
+
args: dict[str, Any],
|
|
617
|
+
registry: SubsystemRegistry,
|
|
618
|
+
) -> str:
|
|
619
|
+
try:
|
|
620
|
+
engine = registry.project_engine
|
|
621
|
+
if engine is None:
|
|
622
|
+
return json.dumps({"error": "Projects not wired", "status": "unavailable"})
|
|
623
|
+
project, reason = engine.create_project(
|
|
624
|
+
args.get("objective", ""), title=args.get("title", ""),
|
|
625
|
+
source="owner")
|
|
626
|
+
if project is None:
|
|
627
|
+
return json.dumps({"created": False, "reason": reason})
|
|
628
|
+
return json.dumps({"created": True, "project_id": project.id,
|
|
629
|
+
"title": project.title,
|
|
630
|
+
"note": "planned and pursued on the autonomy loop"})
|
|
631
|
+
except Exception as e:
|
|
632
|
+
logger.error("create_project failed: %s", e)
|
|
633
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
async def handle_abandon_project(
|
|
637
|
+
args: dict[str, Any],
|
|
638
|
+
registry: SubsystemRegistry,
|
|
639
|
+
) -> str:
|
|
640
|
+
try:
|
|
641
|
+
engine = registry.project_engine
|
|
642
|
+
if engine is None:
|
|
643
|
+
return json.dumps({"error": "Projects not wired", "status": "unavailable"})
|
|
644
|
+
project = engine.abandon(args.get("project_id", ""),
|
|
645
|
+
reason=args.get("reason", "owner_request"))
|
|
646
|
+
return json.dumps({"abandoned": project is not None,
|
|
647
|
+
"status": getattr(project, "status", None)})
|
|
648
|
+
except Exception as e:
|
|
649
|
+
logger.error("abandon_project failed: %s", e)
|
|
650
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
async def handle_recall_skills(
|
|
654
|
+
args: dict[str, Any],
|
|
655
|
+
registry: SubsystemRegistry,
|
|
656
|
+
) -> str:
|
|
657
|
+
try:
|
|
658
|
+
store = registry.skill_store
|
|
659
|
+
if store is None:
|
|
660
|
+
return json.dumps({"error": "Skills memory not wired", "status": "unavailable"})
|
|
661
|
+
from apsimo.skills_memory import relevant_skills
|
|
662
|
+
skills = relevant_skills(store, args.get("situation", ""), k=3)
|
|
663
|
+
return json.dumps({
|
|
664
|
+
"count": len(skills),
|
|
665
|
+
"skills": [
|
|
666
|
+
{"title": s.title, "situation": s.situation,
|
|
667
|
+
"steps": s.steps, "gotchas": s.gotchas, "domain": s.domain}
|
|
668
|
+
for s in skills
|
|
669
|
+
],
|
|
670
|
+
}, default=str)
|
|
671
|
+
except Exception as e:
|
|
672
|
+
logger.error("recall_skills failed: %s", e)
|
|
673
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
async def handle_self_status(
|
|
677
|
+
args: dict[str, Any],
|
|
678
|
+
registry: SubsystemRegistry,
|
|
679
|
+
) -> str:
|
|
680
|
+
try:
|
|
681
|
+
sm = registry.self_model
|
|
682
|
+
if sm is None:
|
|
683
|
+
return json.dumps({"error": "Self-model not wired", "status": "unavailable"})
|
|
684
|
+
out = sm.status()
|
|
685
|
+
out["brief"] = sm.brief()
|
|
686
|
+
return json.dumps(out, default=str)
|
|
687
|
+
except Exception as e:
|
|
688
|
+
logger.error("self_status failed: %s", e)
|
|
689
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
async def handle_action_journal(
|
|
693
|
+
args: dict[str, Any],
|
|
694
|
+
registry: SubsystemRegistry,
|
|
695
|
+
) -> str:
|
|
696
|
+
try:
|
|
697
|
+
sm = registry.self_model
|
|
698
|
+
journal = getattr(sm, "journal", None) if sm is not None else None
|
|
699
|
+
if journal is None:
|
|
700
|
+
return json.dumps({"error": "Action journal not wired", "status": "unavailable"})
|
|
701
|
+
entries = journal.recent(limit=_as_int(args.get("limit", 20), 20),
|
|
702
|
+
domain=args.get("domain") or None)
|
|
703
|
+
return json.dumps({
|
|
704
|
+
"count": len(entries),
|
|
705
|
+
"entries": [
|
|
706
|
+
{"when": _iso_ts(e.get("ts")), "domain": e.get("domain"),
|
|
707
|
+
"action": e.get("description"), "decision": e.get("decision"),
|
|
708
|
+
"reasoning": e.get("reasoning"),
|
|
709
|
+
"confidence": e.get("confidence"),
|
|
710
|
+
"outcome": e.get("outcome")}
|
|
711
|
+
for e in entries
|
|
712
|
+
],
|
|
713
|
+
}, default=str)
|
|
714
|
+
except Exception as e:
|
|
715
|
+
logger.error("action_journal failed: %s", e)
|
|
716
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
async def handle_belief_conflicts(
|
|
720
|
+
args: dict[str, Any],
|
|
721
|
+
registry: SubsystemRegistry,
|
|
722
|
+
) -> str:
|
|
723
|
+
try:
|
|
724
|
+
engine = registry.belief_engine
|
|
725
|
+
if engine is None:
|
|
726
|
+
return json.dumps({"error": "Belief engine not wired", "status": "unavailable"})
|
|
727
|
+
status = args.get("status", "all")
|
|
728
|
+
items = engine.conflicts(status=None if status in ("", "all") else status,
|
|
729
|
+
limit=30)
|
|
730
|
+
return json.dumps({"count": len(items), "conflicts": items}, default=str)
|
|
731
|
+
except Exception as e:
|
|
732
|
+
logger.error("belief_conflicts failed: %s", e)
|
|
733
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
async def _resolve_one_contact(store, who: str):
|
|
737
|
+
"""Resolve who -> a single contact_id, or return (None, error_json)."""
|
|
738
|
+
who = (who or "").strip()
|
|
739
|
+
if who.startswith("cid-"):
|
|
740
|
+
return who, None
|
|
741
|
+
matches = await store.find_by_name(who, threshold=0.6)
|
|
742
|
+
if not matches:
|
|
743
|
+
return None, json.dumps({"error": f"no contact matching {who!r}",
|
|
744
|
+
"status": "not_found"})
|
|
745
|
+
if len(matches) > 1:
|
|
746
|
+
return None, json.dumps({
|
|
747
|
+
"status": "ambiguous",
|
|
748
|
+
"candidates": [{"contact_id": m.contact_id,
|
|
749
|
+
"display_name": m.display_name}
|
|
750
|
+
for m in matches[:5]]})
|
|
751
|
+
return matches[0].contact_id, None
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
async def handle_link_contact(
|
|
755
|
+
args: dict[str, Any],
|
|
756
|
+
registry: SubsystemRegistry,
|
|
757
|
+
) -> str:
|
|
758
|
+
"""Attach a channel handle to a person (owner curation)."""
|
|
759
|
+
try:
|
|
760
|
+
from apsimo.api.routers.host import _contacts_store
|
|
761
|
+
if _contacts_store is None:
|
|
762
|
+
return json.dumps({"error": "contact store not wired",
|
|
763
|
+
"status": "unavailable"})
|
|
764
|
+
cid, err = await _resolve_one_contact(_contacts_store, args.get("who", ""))
|
|
765
|
+
if err:
|
|
766
|
+
return err
|
|
767
|
+
gw = str(args.get("gateway", "")).strip().lower()
|
|
768
|
+
addr = str(args.get("address", "")).strip()
|
|
769
|
+
if not gw or not addr:
|
|
770
|
+
return json.dumps({"error": "gateway and address required",
|
|
771
|
+
"status": "error"})
|
|
772
|
+
await _contacts_store.add_handle(cid, gw, addr, verified=True,
|
|
773
|
+
source="owner")
|
|
774
|
+
return json.dumps({"linked": True, "contact_id": cid,
|
|
775
|
+
"gateway": gw, "address": addr})
|
|
776
|
+
except ValueError as e:
|
|
777
|
+
return json.dumps({"error": str(e), "status": "conflict"})
|
|
778
|
+
except Exception as e:
|
|
779
|
+
logger.error("link_contact failed: %s", e)
|
|
780
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
async def handle_merge_contacts(
|
|
784
|
+
args: dict[str, Any],
|
|
785
|
+
registry: SubsystemRegistry,
|
|
786
|
+
) -> str:
|
|
787
|
+
"""Merge two contacts that are the same person (owner curation)."""
|
|
788
|
+
try:
|
|
789
|
+
from apsimo.api.routers.host import _contacts_store
|
|
790
|
+
if _contacts_store is None:
|
|
791
|
+
return json.dumps({"error": "contact store not wired",
|
|
792
|
+
"status": "unavailable"})
|
|
793
|
+
keep, err = await _resolve_one_contact(_contacts_store, args.get("keep", ""))
|
|
794
|
+
if err:
|
|
795
|
+
return err
|
|
796
|
+
merge, err = await _resolve_one_contact(_contacts_store, args.get("merge", ""))
|
|
797
|
+
if err:
|
|
798
|
+
return err
|
|
799
|
+
kept = await _contacts_store.merge_contacts(keep, merge, performed_by="owner")
|
|
800
|
+
return json.dumps({"merged": True, "kept_contact_id": keep,
|
|
801
|
+
"merged_contact_id": merge,
|
|
802
|
+
"interaction_count": getattr(kept, "interaction_count", None)},
|
|
803
|
+
default=str)
|
|
804
|
+
except ValueError as e:
|
|
805
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
806
|
+
except Exception as e:
|
|
807
|
+
logger.error("merge_contacts failed: %s", e)
|
|
808
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
809
|
+
|
|
810
|
+
|
|
811
|
+
async def handle_pending_contact_proposals(
|
|
812
|
+
args: dict[str, Any],
|
|
813
|
+
registry: SubsystemRegistry,
|
|
814
|
+
) -> str:
|
|
815
|
+
"""List handle-link proposals awaiting owner review."""
|
|
816
|
+
try:
|
|
817
|
+
from apsimo.api.routers.host import _contacts_store
|
|
818
|
+
if _contacts_store is None:
|
|
819
|
+
return json.dumps({"error": "contact store not wired",
|
|
820
|
+
"status": "unavailable"})
|
|
821
|
+
props = await _contacts_store.list_handle_proposals(limit=50)
|
|
822
|
+
return json.dumps({"count": len(props), "proposals": props}, default=str)
|
|
823
|
+
except Exception as e:
|
|
824
|
+
logger.error("pending_contact_proposals failed: %s", e)
|
|
825
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
async def handle_relationship_brief(
|
|
829
|
+
args: dict[str, Any],
|
|
830
|
+
registry: SubsystemRegistry,
|
|
831
|
+
) -> str:
|
|
832
|
+
"""Standing + psyche + approach brief for a person (docs/RELATIONSHIPS.md)."""
|
|
833
|
+
try:
|
|
834
|
+
from apsimo.api.routers.host import (
|
|
835
|
+
_contacts_store, _relationship_profiler,
|
|
836
|
+
)
|
|
837
|
+
if _relationship_profiler is None:
|
|
838
|
+
return json.dumps({"error": "relationship profiler not wired",
|
|
839
|
+
"status": "unavailable"})
|
|
840
|
+
who = str(args.get("who", "")).strip()
|
|
841
|
+
if not who:
|
|
842
|
+
return json.dumps({"error": "who is required", "status": "error"})
|
|
843
|
+
contact_id = who
|
|
844
|
+
if not who.startswith("cid-") and _contacts_store is not None:
|
|
845
|
+
matches = await _contacts_store.find_by_name(who, threshold=0.6)
|
|
846
|
+
if not matches:
|
|
847
|
+
return json.dumps({"error": f"no contact matching {who!r}",
|
|
848
|
+
"status": "not_found"})
|
|
849
|
+
if len(matches) > 1:
|
|
850
|
+
return json.dumps({
|
|
851
|
+
"status": "ambiguous",
|
|
852
|
+
"candidates": [
|
|
853
|
+
{"contact_id": m.contact_id,
|
|
854
|
+
"display_name": m.display_name}
|
|
855
|
+
for m in matches[:5]],
|
|
856
|
+
})
|
|
857
|
+
contact_id = matches[0].contact_id
|
|
858
|
+
brief = (None if args.get("refresh")
|
|
859
|
+
else _relationship_profiler.cached(contact_id))
|
|
860
|
+
if brief is None:
|
|
861
|
+
brief = await _relationship_profiler.profile(contact_id)
|
|
862
|
+
if brief is None:
|
|
863
|
+
return json.dumps({"error": f"no profile for {contact_id!r}",
|
|
864
|
+
"status": "not_found"})
|
|
865
|
+
return json.dumps({"brief": brief.to_dict(),
|
|
866
|
+
"rendered": brief.render()}, default=str)
|
|
867
|
+
except Exception as e:
|
|
868
|
+
logger.error("relationship_brief failed: %s", e)
|
|
869
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
870
|
+
|
|
871
|
+
|
|
872
|
+
async def handle_sandbox_run(
|
|
873
|
+
args: dict[str, Any],
|
|
874
|
+
registry: SubsystemRegistry,
|
|
875
|
+
) -> str:
|
|
876
|
+
try:
|
|
877
|
+
sandbox = registry.sandbox
|
|
878
|
+
if sandbox is None:
|
|
879
|
+
return json.dumps({"error": "Sandbox not wired", "status": "unavailable"})
|
|
880
|
+
# Agent-invoked runs are NOT owner-directed: they are flagged for owner
|
|
881
|
+
# approval (the owner auto-runs via the authenticated API). The agent
|
|
882
|
+
# cannot self-grant owner authority here.
|
|
883
|
+
out = sandbox.run(
|
|
884
|
+
args.get("script", ""),
|
|
885
|
+
lang=args.get("lang", "python"),
|
|
886
|
+
purpose=args.get("purpose", ""),
|
|
887
|
+
owner_directed=False,
|
|
888
|
+
)
|
|
889
|
+
return json.dumps(out, default=str)
|
|
890
|
+
except Exception as e:
|
|
891
|
+
logger.error("sandbox_run failed: %s", e)
|
|
892
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
893
|
+
|
|
894
|
+
|
|
895
|
+
async def handle_sandbox_status(
|
|
896
|
+
args: dict[str, Any],
|
|
897
|
+
registry: SubsystemRegistry,
|
|
898
|
+
) -> str:
|
|
899
|
+
try:
|
|
900
|
+
sandbox = registry.sandbox
|
|
901
|
+
if sandbox is None:
|
|
902
|
+
return json.dumps({"error": "Sandbox not wired", "status": "unavailable"})
|
|
903
|
+
return json.dumps(sandbox.status(), default=str)
|
|
904
|
+
except Exception as e:
|
|
905
|
+
logger.error("sandbox_status failed: %s", e)
|
|
906
|
+
return json.dumps({"error": str(e), "status": "error"})
|
|
907
|
+
|
|
908
|
+
|
|
909
|
+
# Handler registry -- maps tool name to handler function
|
|
910
|
+
TOOL_HANDLERS: dict[str, callable] = {
|
|
911
|
+
"colony_memory_search": handle_memory_search,
|
|
912
|
+
"colony_get_relationship": handle_get_relationship,
|
|
913
|
+
"colony_list_goals": handle_list_goals,
|
|
914
|
+
"colony_get_briefing": handle_get_briefing,
|
|
915
|
+
"colony_record_insight": handle_record_insight,
|
|
916
|
+
"colony_query_entities": handle_query_entities,
|
|
917
|
+
"colony_start_research": handle_start_research,
|
|
918
|
+
"colony_discover_connections": handle_discover_connections,
|
|
919
|
+
"colony_task_complete": handle_task_complete,
|
|
920
|
+
"colony_task_snooze": handle_task_snooze,
|
|
921
|
+
"colony_task_dismiss": handle_task_dismiss,
|
|
922
|
+
"colony_initiative_feedback": handle_initiative_feedback,
|
|
923
|
+
"colony_list_boundaries": handle_list_boundaries,
|
|
924
|
+
"colony_recent_boundary_blocks": handle_recent_boundary_blocks,
|
|
925
|
+
"repo_list_files": handle_repo_list_files,
|
|
926
|
+
"repo_read_file": handle_repo_read_file,
|
|
927
|
+
"repo_search": handle_repo_search,
|
|
928
|
+
"colony_flag_boundary_concern": handle_flag_boundary_concern,
|
|
929
|
+
"list_projects": handle_list_projects,
|
|
930
|
+
"project_status": handle_project_status,
|
|
931
|
+
"create_project": handle_create_project,
|
|
932
|
+
"abandon_project": handle_abandon_project,
|
|
933
|
+
"recall_skills": handle_recall_skills,
|
|
934
|
+
"self_status": handle_self_status,
|
|
935
|
+
"action_journal": handle_action_journal,
|
|
936
|
+
"belief_conflicts": handle_belief_conflicts,
|
|
937
|
+
"link_contact": handle_link_contact,
|
|
938
|
+
"merge_contacts": handle_merge_contacts,
|
|
939
|
+
"pending_contact_proposals": handle_pending_contact_proposals,
|
|
940
|
+
"relationship_brief": handle_relationship_brief,
|
|
941
|
+
"sandbox_run": handle_sandbox_run,
|
|
942
|
+
"sandbox_status": handle_sandbox_status,
|
|
943
|
+
}
|