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/__init__.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Apsimo cognition server for agent runtimes.
|
|
2
|
+
|
|
3
|
+
A standalone FastAPI server that agent hosts (Hermes plugin, MCP harnesses,
|
|
4
|
+
REST integrations) mount via the ``/v1/host`` API surface.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import os
|
|
9
|
+
from .environment import apply_environment_aliases
|
|
10
|
+
|
|
11
|
+
apply_environment_aliases()
|
|
12
|
+
|
|
13
|
+
try: # single source of truth: the installed package metadata (pyproject)
|
|
14
|
+
from importlib.metadata import version as _pkg_version
|
|
15
|
+
__version__ = _pkg_version("apsimo")
|
|
16
|
+
except Exception: # editable/unbuilt checkouts without installed metadata
|
|
17
|
+
__version__ = "0.0.0+unknown"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_state_dir() -> Path:
|
|
21
|
+
"""Return the selected state directory without moving existing state.
|
|
22
|
+
|
|
23
|
+
Priority:
|
|
24
|
+
1. APSIMO_STATE_DIR or legacy COLONY_STATE_DIR (explicit override)
|
|
25
|
+
2. ~/.colony/data (retained unconfigured-library compatibility default)
|
|
26
|
+
|
|
27
|
+
Guided setup selects its explicit private instance directory. This fallback
|
|
28
|
+
stays aligned with older independently configured storage readers.
|
|
29
|
+
|
|
30
|
+
Creates the directory if it doesn't exist.
|
|
31
|
+
"""
|
|
32
|
+
explicit = os.environ.get("APSIMO_STATE_DIR") or os.environ.get("COLONY_STATE_DIR")
|
|
33
|
+
if explicit:
|
|
34
|
+
path = Path(explicit)
|
|
35
|
+
else:
|
|
36
|
+
path = Path.home() / ".colony" / "data"
|
|
37
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
38
|
+
return path
|
apsimo/__main__.py
ADDED
apsimo/agent/__init__.py
ADDED
apsimo/agent/client.py
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"""Colony Agent Client — WebSocket client for remote agents.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
from colony.agent import AgentClient
|
|
5
|
+
|
|
6
|
+
client = AgentClient(config_path="~/.colony/agent.json")
|
|
7
|
+
|
|
8
|
+
@client.on_initiative
|
|
9
|
+
async def handle_initiative(initiative):
|
|
10
|
+
print(f"Received: {initiative['description']}")
|
|
11
|
+
await client.acknowledge(initiative["id"])
|
|
12
|
+
result = await process_initiative(initiative)
|
|
13
|
+
await client.complete(initiative["id"], result=result)
|
|
14
|
+
|
|
15
|
+
await client.start()
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import asyncio
|
|
19
|
+
import json
|
|
20
|
+
import time
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any, Callable, Dict, Optional
|
|
23
|
+
|
|
24
|
+
import websockets
|
|
25
|
+
|
|
26
|
+
from .models import AgentConfig
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AgentClient:
|
|
30
|
+
"""WebSocket client for remote Colony agents.
|
|
31
|
+
|
|
32
|
+
Handles:
|
|
33
|
+
- WebSocket connection with auth
|
|
34
|
+
- Heartbeat loop
|
|
35
|
+
- Initiative delivery
|
|
36
|
+
- Reconnection with exponential backoff
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
config_path: str = "~/.colony/agent.json",
|
|
42
|
+
config: Optional[AgentConfig] = None,
|
|
43
|
+
) -> None:
|
|
44
|
+
"""Initialize agent client.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
config_path: Path to agent config file
|
|
48
|
+
config: Optional pre-loaded config (skips file read)
|
|
49
|
+
"""
|
|
50
|
+
if config:
|
|
51
|
+
self.config = config
|
|
52
|
+
else:
|
|
53
|
+
config_file = Path(config_path).expanduser()
|
|
54
|
+
if not config_file.exists():
|
|
55
|
+
raise FileNotFoundError(f"Agent config not found: {config_file}")
|
|
56
|
+
self.config = AgentConfig.parse_file(config_file)
|
|
57
|
+
|
|
58
|
+
self._ws: Optional[websockets.WebSocketClientProtocol] = None
|
|
59
|
+
self._running = False
|
|
60
|
+
self._seq = 0
|
|
61
|
+
self._pending_acks: Dict[int, asyncio.Future] = {}
|
|
62
|
+
self._handlers: Dict[str, Callable] = {
|
|
63
|
+
"initiative": None,
|
|
64
|
+
"config": None,
|
|
65
|
+
"disconnect": None,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# Reconnection state
|
|
69
|
+
self._reconnect_delay = 1.0
|
|
70
|
+
self._max_reconnect_delay = 60.0
|
|
71
|
+
|
|
72
|
+
def on_initiative(self, handler: Callable) -> Callable:
|
|
73
|
+
"""Register initiative handler."""
|
|
74
|
+
self._handlers["initiative"] = handler
|
|
75
|
+
return handler
|
|
76
|
+
|
|
77
|
+
def on_config(self, handler: Callable) -> Callable:
|
|
78
|
+
"""Register config update handler."""
|
|
79
|
+
self._handlers["config"] = handler
|
|
80
|
+
return handler
|
|
81
|
+
|
|
82
|
+
def on_disconnect(self, handler: Callable) -> Callable:
|
|
83
|
+
"""Register disconnect handler."""
|
|
84
|
+
self._handlers["disconnect"] = handler
|
|
85
|
+
return handler
|
|
86
|
+
|
|
87
|
+
async def start(self) -> None:
|
|
88
|
+
"""Connect to Colony and start message loop."""
|
|
89
|
+
if not self.config.websocket_url:
|
|
90
|
+
raise ValueError("No websocket_url in config (local mode?)")
|
|
91
|
+
|
|
92
|
+
self._running = True
|
|
93
|
+
|
|
94
|
+
while self._running:
|
|
95
|
+
try:
|
|
96
|
+
await self._connect()
|
|
97
|
+
await self._message_loop()
|
|
98
|
+
except Exception as e:
|
|
99
|
+
if not self._running:
|
|
100
|
+
break
|
|
101
|
+
print(f"Connection error: {e}, reconnecting in {self._reconnect_delay}s...")
|
|
102
|
+
await asyncio.sleep(self._reconnect_delay)
|
|
103
|
+
self._reconnect_delay = min(
|
|
104
|
+
self._reconnect_delay * 2,
|
|
105
|
+
self._max_reconnect_delay,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
async def stop(self) -> None:
|
|
109
|
+
"""Disconnect from Colony."""
|
|
110
|
+
self._running = False
|
|
111
|
+
if self._ws:
|
|
112
|
+
await self._ws.close()
|
|
113
|
+
|
|
114
|
+
async def _connect(self) -> None:
|
|
115
|
+
"""Establish WebSocket connection with auth."""
|
|
116
|
+
headers = {}
|
|
117
|
+
if self.config.node_cert:
|
|
118
|
+
# Sign challenge with node cert
|
|
119
|
+
headers["Authorization"] = f"Bearer {self.config.node_cert.signature}"
|
|
120
|
+
headers["X-Agent-Id"] = self.config.agent_id
|
|
121
|
+
|
|
122
|
+
self._ws = await websockets.connect(
|
|
123
|
+
self.config.websocket_url,
|
|
124
|
+
extra_headers=headers,
|
|
125
|
+
ping_interval=30,
|
|
126
|
+
ping_timeout=10,
|
|
127
|
+
)
|
|
128
|
+
self._reconnect_delay = 1.0 # Reset on successful connection
|
|
129
|
+
print(f"Connected to Colony: {self.config.websocket_url}")
|
|
130
|
+
|
|
131
|
+
# Start heartbeat task
|
|
132
|
+
asyncio.create_task(self._heartbeat_loop())
|
|
133
|
+
|
|
134
|
+
async def _message_loop(self) -> None:
|
|
135
|
+
"""Process incoming messages."""
|
|
136
|
+
if not self._ws:
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
async for message in self._ws:
|
|
140
|
+
try:
|
|
141
|
+
data = json.loads(message)
|
|
142
|
+
await self._handle_message(data)
|
|
143
|
+
except json.JSONDecodeError:
|
|
144
|
+
print(f"Invalid JSON: {message}")
|
|
145
|
+
except Exception as e:
|
|
146
|
+
print(f"Error handling message: {e}")
|
|
147
|
+
|
|
148
|
+
async def _handle_message(self, data: Dict[str, Any]) -> None:
|
|
149
|
+
"""Handle incoming message."""
|
|
150
|
+
msg_type = data.get("type")
|
|
151
|
+
seq = data.get("seq")
|
|
152
|
+
|
|
153
|
+
# Handle ACKs for our messages
|
|
154
|
+
if msg_type == "ack" and seq in self._pending_acks:
|
|
155
|
+
self._pending_acks[seq].set_result(data)
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
# Handle initiative delivery
|
|
159
|
+
if msg_type == "initiative":
|
|
160
|
+
if self._handlers["initiative"]:
|
|
161
|
+
await self._handlers["initiative"](data.get("initiative", {}))
|
|
162
|
+
# Auto-ack receipt
|
|
163
|
+
await self._send_ack(seq)
|
|
164
|
+
|
|
165
|
+
# Handle config update
|
|
166
|
+
elif msg_type == "config":
|
|
167
|
+
if self._handlers["config"]:
|
|
168
|
+
await self._handlers["config"](data.get("config", {}))
|
|
169
|
+
|
|
170
|
+
# Handle disconnect notice
|
|
171
|
+
elif msg_type == "disconnect":
|
|
172
|
+
if self._handlers["disconnect"]:
|
|
173
|
+
await self._handlers["disconnect"](data.get("reason", "unknown"))
|
|
174
|
+
await self.stop()
|
|
175
|
+
|
|
176
|
+
# Handle ping
|
|
177
|
+
elif msg_type == "ping":
|
|
178
|
+
await self._send({"type": "pong", "seq": seq})
|
|
179
|
+
|
|
180
|
+
async def _heartbeat_loop(self) -> None:
|
|
181
|
+
"""Send periodic heartbeats."""
|
|
182
|
+
# The colony side tracks current_assignments via the agents table
|
|
183
|
+
# (incremented/decremented when initiatives are assigned/completed),
|
|
184
|
+
# so the heartbeat does not need to carry it — the server only uses
|
|
185
|
+
# this message to update last_seen_at.
|
|
186
|
+
while self._running and self._ws:
|
|
187
|
+
try:
|
|
188
|
+
await self._send({
|
|
189
|
+
"type": "heartbeat",
|
|
190
|
+
"status": "online",
|
|
191
|
+
})
|
|
192
|
+
await asyncio.sleep(30)
|
|
193
|
+
except Exception:
|
|
194
|
+
break
|
|
195
|
+
|
|
196
|
+
async def _send(self, message: Dict[str, Any]) -> bool:
|
|
197
|
+
"""Send message with sequencing."""
|
|
198
|
+
if not self._ws:
|
|
199
|
+
return False
|
|
200
|
+
|
|
201
|
+
self._seq += 1
|
|
202
|
+
message["seq"] = self._seq
|
|
203
|
+
|
|
204
|
+
try:
|
|
205
|
+
await self._ws.send(json.dumps(message))
|
|
206
|
+
return True
|
|
207
|
+
except Exception as e:
|
|
208
|
+
print(f"Send error: {e}")
|
|
209
|
+
return False
|
|
210
|
+
|
|
211
|
+
async def _send_ack(self, seq: Optional[int]) -> None:
|
|
212
|
+
"""Send acknowledgment."""
|
|
213
|
+
await self._send({"type": "ack", "ack_seq": seq})
|
|
214
|
+
|
|
215
|
+
# --- Public API ---
|
|
216
|
+
|
|
217
|
+
async def acknowledge(self, initiative_id: str) -> bool:
|
|
218
|
+
"""Acknowledge initiative receipt."""
|
|
219
|
+
return await self._send({
|
|
220
|
+
"type": "initiative_ack",
|
|
221
|
+
"initiative_id": initiative_id,
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
async def complete(
|
|
225
|
+
self,
|
|
226
|
+
initiative_id: str,
|
|
227
|
+
result: Optional[str] = None,
|
|
228
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
229
|
+
) -> bool:
|
|
230
|
+
"""Mark initiative as completed."""
|
|
231
|
+
return await self._send({
|
|
232
|
+
"type": "initiative_complete",
|
|
233
|
+
"initiative_id": initiative_id,
|
|
234
|
+
"result": result,
|
|
235
|
+
"result_metadata": metadata or {},
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
async def fail(
|
|
239
|
+
self,
|
|
240
|
+
initiative_id: str,
|
|
241
|
+
reason: str,
|
|
242
|
+
retry: bool = True,
|
|
243
|
+
) -> bool:
|
|
244
|
+
"""Mark initiative as failed."""
|
|
245
|
+
return await self._send({
|
|
246
|
+
"type": "initiative_fail",
|
|
247
|
+
"initiative_id": initiative_id,
|
|
248
|
+
"reason": reason,
|
|
249
|
+
"retry": retry,
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
async def delegate(
|
|
253
|
+
self,
|
|
254
|
+
initiative_id: str,
|
|
255
|
+
reason: str,
|
|
256
|
+
target_agent_id: Optional[str] = None,
|
|
257
|
+
) -> bool:
|
|
258
|
+
"""Delegate initiative to another agent."""
|
|
259
|
+
return await self._send({
|
|
260
|
+
"type": "initiative_delegate",
|
|
261
|
+
"initiative_id": initiative_id,
|
|
262
|
+
"reason": reason,
|
|
263
|
+
"target_agent_id": target_agent_id,
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
async def update_status(
|
|
267
|
+
self,
|
|
268
|
+
status: str = "online",
|
|
269
|
+
current_assignments: int = 0,
|
|
270
|
+
) -> bool:
|
|
271
|
+
"""Update agent status."""
|
|
272
|
+
return await self._send({
|
|
273
|
+
"type": "status_update",
|
|
274
|
+
"status": status,
|
|
275
|
+
"current_assignments": current_assignments,
|
|
276
|
+
})
|
apsimo/agent/models.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Agent SDK models."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class NodeCertificate(BaseModel):
|
|
10
|
+
"""Node certificate structure."""
|
|
11
|
+
|
|
12
|
+
colony_id: str
|
|
13
|
+
node_id: str
|
|
14
|
+
public_key: Optional[str] = None # node_public_key_ed25519 in spec
|
|
15
|
+
issued_at: str # ISO timestamp
|
|
16
|
+
expires_at: Optional[str] = None # ISO timestamp
|
|
17
|
+
signature: str
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AgentConfig(BaseModel):
|
|
21
|
+
"""Agent configuration file schema.
|
|
22
|
+
|
|
23
|
+
This is the structure saved to ~/.colony/agent.json after
|
|
24
|
+
running `colony agent connect`.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
agent_id: str
|
|
28
|
+
node_id: str
|
|
29
|
+
colony_id: str
|
|
30
|
+
websocket_url: Optional[str] = None # For remote mode
|
|
31
|
+
name: str
|
|
32
|
+
capabilities: List[str] = Field(default_factory=list)
|
|
33
|
+
is_primary: bool = False
|
|
34
|
+
max_concurrent: int = 5
|
|
35
|
+
node_cert: Optional[NodeCertificate] = None
|
|
36
|
+
connection_mode: str = "remote"
|
|
37
|
+
registered_at: Optional[str] = None # ISO timestamp
|
|
38
|
+
|
|
39
|
+
# Optional fields
|
|
40
|
+
priority: int = 1
|
|
41
|
+
excluded_types: List[str] = Field(default_factory=list)
|
|
42
|
+
included_types: List[str] = Field(default_factory=list)
|
|
43
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
44
|
+
|
|
45
|
+
class Config:
|
|
46
|
+
extra = "allow" # Forward compatibility
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Multi-agent support for Colony.
|
|
2
|
+
|
|
3
|
+
Provides:
|
|
4
|
+
- AgentStore: Registry of connected agents
|
|
5
|
+
- InviteStore: Setup code management
|
|
6
|
+
- WebSocketManager: Remote agent connections
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .models import Agent, AgentStatus, AgentMetadata
|
|
10
|
+
from .store import AgentStore, InviteStore
|
|
11
|
+
from .websocket import WebSocketManager
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"Agent",
|
|
15
|
+
"AgentStatus",
|
|
16
|
+
"AgentMetadata",
|
|
17
|
+
"AgentStore",
|
|
18
|
+
"InviteStore",
|
|
19
|
+
"WebSocketManager",
|
|
20
|
+
]
|
apsimo/agents/models.py
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"""Agent data models for multi-agent Colony.
|
|
2
|
+
|
|
3
|
+
Defines:
|
|
4
|
+
- AgentStatus enum
|
|
5
|
+
- AgentMetadata schema
|
|
6
|
+
- Agent dataclass
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Any, Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AgentStatus(str, Enum):
|
|
16
|
+
"""Agent status values."""
|
|
17
|
+
|
|
18
|
+
ONLINE = "online"
|
|
19
|
+
OFFLINE = "offline"
|
|
20
|
+
BUSY = "busy"
|
|
21
|
+
SUSPENDED = "suspended"
|
|
22
|
+
REVOKED = "revoked"
|
|
23
|
+
|
|
24
|
+
def is_active(self) -> bool:
|
|
25
|
+
"""Can this agent receive assignments?"""
|
|
26
|
+
return self in (AgentStatus.ONLINE, AgentStatus.BUSY)
|
|
27
|
+
|
|
28
|
+
def can_reconnect(self) -> bool:
|
|
29
|
+
"""Can this agent reconnect to Colony?"""
|
|
30
|
+
return self != AgentStatus.REVOKED
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class AgentMetadata:
|
|
35
|
+
"""Structured metadata about an agent's environment.
|
|
36
|
+
|
|
37
|
+
Standard fields:
|
|
38
|
+
- hostname: Machine hostname
|
|
39
|
+
- platform: OS platform (darwin, linux, windows)
|
|
40
|
+
- version: Colony version
|
|
41
|
+
- harness: Which harness (openclaw, claude-code, codex, crush)
|
|
42
|
+
- {harness}_version: Harness version
|
|
43
|
+
- python_version: Python version (for Python-based harnesses)
|
|
44
|
+
- started_at: ISO timestamp when agent started
|
|
45
|
+
- tz: IANA timezone
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
hostname: Optional[str] = None
|
|
49
|
+
platform: Optional[str] = None
|
|
50
|
+
version: Optional[str] = None
|
|
51
|
+
harness: Optional[str] = None
|
|
52
|
+
started_at: Optional[str] = None
|
|
53
|
+
tz: Optional[str] = None
|
|
54
|
+
last_connection_ip: Optional[str] = None
|
|
55
|
+
last_connection_ip_ts: Optional[str] = None
|
|
56
|
+
extra: Dict[str, Any] = field(default_factory=dict)
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def from_dict(cls, data: Dict[str, Any]) -> "AgentMetadata":
|
|
60
|
+
"""Create from dict, handling unknown fields."""
|
|
61
|
+
known = {
|
|
62
|
+
"hostname",
|
|
63
|
+
"platform",
|
|
64
|
+
"version",
|
|
65
|
+
"harness",
|
|
66
|
+
"started_at",
|
|
67
|
+
"tz",
|
|
68
|
+
"last_connection_ip",
|
|
69
|
+
"last_connection_ip_ts",
|
|
70
|
+
}
|
|
71
|
+
return cls(
|
|
72
|
+
hostname=data.get("hostname"),
|
|
73
|
+
platform=data.get("platform"),
|
|
74
|
+
version=data.get("version"),
|
|
75
|
+
harness=data.get("harness"),
|
|
76
|
+
started_at=data.get("started_at"),
|
|
77
|
+
tz=data.get("tz"),
|
|
78
|
+
last_connection_ip=data.get("last_connection_ip"),
|
|
79
|
+
last_connection_ip_ts=data.get("last_connection_ip_ts"),
|
|
80
|
+
extra={k: v for k, v in data.items() if k not in known},
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
84
|
+
"""Convert to dict for JSON serialization."""
|
|
85
|
+
result = {}
|
|
86
|
+
if self.hostname:
|
|
87
|
+
result["hostname"] = self.hostname
|
|
88
|
+
if self.platform:
|
|
89
|
+
result["platform"] = self.platform
|
|
90
|
+
if self.version:
|
|
91
|
+
result["version"] = self.version
|
|
92
|
+
if self.harness:
|
|
93
|
+
result["harness"] = self.harness
|
|
94
|
+
if self.started_at:
|
|
95
|
+
result["started_at"] = self.started_at
|
|
96
|
+
if self.tz:
|
|
97
|
+
result["tz"] = self.tz
|
|
98
|
+
if self.last_connection_ip:
|
|
99
|
+
result["last_connection_ip"] = self.last_connection_ip
|
|
100
|
+
if self.last_connection_ip_ts:
|
|
101
|
+
result["last_connection_ip_ts"] = self.last_connection_ip_ts
|
|
102
|
+
result.update(self.extra)
|
|
103
|
+
return result
|
|
104
|
+
|
|
105
|
+
def get_harness_version(self) -> Optional[str]:
|
|
106
|
+
"""Get harness-specific version field."""
|
|
107
|
+
if not self.harness:
|
|
108
|
+
return None
|
|
109
|
+
return self.extra.get(f"{self.harness}_version")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass
|
|
113
|
+
class Agent:
|
|
114
|
+
"""A connected agent (Hermes host, Claude Code, worker daemon, etc.).
|
|
115
|
+
|
|
116
|
+
Attributes:
|
|
117
|
+
agent_id: Unique identifier (UUID)
|
|
118
|
+
node_id: Device node ID
|
|
119
|
+
colony_id: Parent Colony ID
|
|
120
|
+
name: Human-readable name (e.g., "node-a", "workstation")
|
|
121
|
+
connection_mode: "local" (HTTP) or "remote" (WebSocket)
|
|
122
|
+
gateway_url: For local mode, URL to push initiatives
|
|
123
|
+
websocket_connected: For remote mode, is WebSocket active?
|
|
124
|
+
capabilities: What can this agent do? (messaging, calendar, coding)
|
|
125
|
+
is_primary: Preferred for user-facing initiatives
|
|
126
|
+
priority: 0=backup, 1=normal, 2=high
|
|
127
|
+
max_concurrent: Max simultaneous initiative assignments
|
|
128
|
+
max_initiatives_per_hour: Rate limit
|
|
129
|
+
excluded_types: Initiative types to skip
|
|
130
|
+
included_types: Only these types (if set)
|
|
131
|
+
status: Current status
|
|
132
|
+
current_assignments: Count of active assignments
|
|
133
|
+
last_seen_at: Last heartbeat/disconnect time
|
|
134
|
+
metadata: Environment info
|
|
135
|
+
registered_at: When agent was registered
|
|
136
|
+
node_cert: Signed certificate (JSON)
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
agent_id: str
|
|
140
|
+
node_id: str
|
|
141
|
+
colony_id: str
|
|
142
|
+
name: str
|
|
143
|
+
connection_mode: str = "local"
|
|
144
|
+
gateway_url: Optional[str] = None
|
|
145
|
+
websocket_connected: bool = False
|
|
146
|
+
capabilities: List[str] = field(default_factory=list)
|
|
147
|
+
is_primary: bool = False
|
|
148
|
+
priority: int = 1
|
|
149
|
+
max_concurrent: int = 5
|
|
150
|
+
max_initiatives_per_hour: int = 10
|
|
151
|
+
excluded_types: List[str] = field(default_factory=list)
|
|
152
|
+
included_types: List[str] = field(default_factory=list)
|
|
153
|
+
status: str = "offline"
|
|
154
|
+
current_assignments: int = 0
|
|
155
|
+
last_seen_at: Optional[datetime] = None
|
|
156
|
+
metadata: AgentMetadata = field(default_factory=AgentMetadata)
|
|
157
|
+
registered_at: Optional[datetime] = None
|
|
158
|
+
node_cert: Optional[Dict[str, Any]] = None
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def load(self) -> float:
|
|
162
|
+
"""Current load as ratio (0.0-1.0)."""
|
|
163
|
+
if self.max_concurrent <= 0:
|
|
164
|
+
return 1.0
|
|
165
|
+
return self.current_assignments / self.max_concurrent
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def has_capacity(self) -> bool:
|
|
169
|
+
"""Can accept more assignments?"""
|
|
170
|
+
return (
|
|
171
|
+
self.status in ("online", "busy")
|
|
172
|
+
and self.current_assignments < self.max_concurrent
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
def can_handle_type(self, initiative_type: str) -> bool:
|
|
176
|
+
"""Check if agent can handle this initiative type."""
|
|
177
|
+
# Excluded types take priority
|
|
178
|
+
if initiative_type in self.excluded_types:
|
|
179
|
+
return False
|
|
180
|
+
|
|
181
|
+
# If included_types is set, must be in list
|
|
182
|
+
if self.included_types and initiative_type not in self.included_types:
|
|
183
|
+
return False
|
|
184
|
+
|
|
185
|
+
return True
|
|
186
|
+
|
|
187
|
+
def has_capability(self, capability: str) -> bool:
|
|
188
|
+
"""Check if agent has a specific capability."""
|
|
189
|
+
return capability in self.capabilities
|
|
190
|
+
|
|
191
|
+
def has_capabilities(self, capabilities: List[str]) -> bool:
|
|
192
|
+
"""Check if agent has all specified capabilities."""
|
|
193
|
+
return all(cap in self.capabilities for cap in capabilities)
|
|
194
|
+
|
|
195
|
+
@classmethod
|
|
196
|
+
def from_row(cls, row: dict) -> "Agent":
|
|
197
|
+
"""Create from SQLite row dict."""
|
|
198
|
+
import json
|
|
199
|
+
|
|
200
|
+
metadata_raw = row.get("metadata", "{}")
|
|
201
|
+
if isinstance(metadata_raw, str):
|
|
202
|
+
metadata_dict = json.loads(metadata_raw)
|
|
203
|
+
else:
|
|
204
|
+
metadata_dict = metadata_raw
|
|
205
|
+
|
|
206
|
+
capabilities_raw = row.get("capabilities", "[]")
|
|
207
|
+
if isinstance(capabilities_raw, str):
|
|
208
|
+
capabilities = json.loads(capabilities_raw)
|
|
209
|
+
else:
|
|
210
|
+
capabilities = capabilities_raw
|
|
211
|
+
|
|
212
|
+
excluded_raw = row.get("excluded_types", "[]")
|
|
213
|
+
if isinstance(excluded_raw, str):
|
|
214
|
+
excluded_types = json.loads(excluded_raw)
|
|
215
|
+
else:
|
|
216
|
+
excluded_types = excluded_raw
|
|
217
|
+
|
|
218
|
+
included_raw = row.get("included_types", "[]")
|
|
219
|
+
if isinstance(included_raw, str):
|
|
220
|
+
included_types = json.loads(included_raw)
|
|
221
|
+
else:
|
|
222
|
+
included_types = included_raw
|
|
223
|
+
|
|
224
|
+
node_cert_raw = row.get("node_cert")
|
|
225
|
+
if node_cert_raw and isinstance(node_cert_raw, str):
|
|
226
|
+
node_cert = json.loads(node_cert_raw)
|
|
227
|
+
else:
|
|
228
|
+
node_cert = node_cert_raw
|
|
229
|
+
|
|
230
|
+
return cls(
|
|
231
|
+
agent_id=row["agent_id"],
|
|
232
|
+
node_id=row["node_id"],
|
|
233
|
+
colony_id=row["colony_id"],
|
|
234
|
+
name=row["name"],
|
|
235
|
+
connection_mode=row.get("connection_mode", "local"),
|
|
236
|
+
gateway_url=row.get("gateway_url"),
|
|
237
|
+
websocket_connected=bool(row.get("websocket_connected", 0)),
|
|
238
|
+
capabilities=capabilities,
|
|
239
|
+
is_primary=bool(row.get("is_primary", 0)),
|
|
240
|
+
priority=row.get("priority", 1),
|
|
241
|
+
max_concurrent=row.get("max_concurrent", 5),
|
|
242
|
+
max_initiatives_per_hour=row.get("max_initiatives_per_hour", 10),
|
|
243
|
+
excluded_types=excluded_types,
|
|
244
|
+
included_types=included_types,
|
|
245
|
+
status=row.get("status", "offline"),
|
|
246
|
+
current_assignments=row.get("current_assignments", 0),
|
|
247
|
+
last_seen_at=_parse_datetime(row.get("last_seen_at")),
|
|
248
|
+
metadata=AgentMetadata.from_dict(metadata_dict),
|
|
249
|
+
registered_at=_parse_datetime(row.get("registered_at")),
|
|
250
|
+
node_cert=node_cert,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _parse_datetime(value: Optional[str]) -> Optional[datetime]:
|
|
255
|
+
"""Parse ISO datetime string."""
|
|
256
|
+
if not value:
|
|
257
|
+
return None
|
|
258
|
+
try:
|
|
259
|
+
# Handle both with and without timezone
|
|
260
|
+
if value.endswith("Z"):
|
|
261
|
+
value = value[:-1] + "+00:00"
|
|
262
|
+
return datetime.fromisoformat(value)
|
|
263
|
+
except (ValueError, TypeError):
|
|
264
|
+
return None
|