apsimo 1.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- apsimo/__init__.py +38 -0
- apsimo/__main__.py +6 -0
- apsimo/agent/__init__.py +6 -0
- apsimo/agent/client.py +276 -0
- apsimo/agent/models.py +46 -0
- apsimo/agents/__init__.py +20 -0
- apsimo/agents/models.py +264 -0
- apsimo/agents/store.py +861 -0
- apsimo/agents/websocket.py +522 -0
- apsimo/api/__init__.py +1 -0
- apsimo/api/auth_telemetry.py +287 -0
- apsimo/api/authority.py +1203 -0
- apsimo/api/contact_grants.py +347 -0
- apsimo/api/middleware.py +483 -0
- apsimo/api/routers/__init__.py +1 -0
- apsimo/api/routers/commitment_work.py +265 -0
- apsimo/api/routers/context_gate.py +123 -0
- apsimo/api/routers/executions.py +140 -0
- apsimo/api/routers/followup_plans.py +147 -0
- apsimo/api/routers/governed_actions.py +162 -0
- apsimo/api/routers/host.py +14473 -0
- apsimo/api/routers/initiative_work.py +115 -0
- apsimo/api/routers/mining.py +104 -0
- apsimo/api/routers/observations.py +110 -0
- apsimo/api/routers/social_state.py +225 -0
- apsimo/api/routers/task_queue.py +2715 -0
- apsimo/api/routers/temporal_followups.py +251 -0
- apsimo/api/routers/transport.py +110 -0
- apsimo/api/routers/transport_ingress_api.py +240 -0
- apsimo/api/schemas/__init__.py +1 -0
- apsimo/api/schemas/host.py +1949 -0
- apsimo/autonomy/cli.py +110 -0
- apsimo/autonomy/condition_worker.py +437 -0
- apsimo/autonomy/config.py +424 -0
- apsimo/autonomy/loop.py +4316 -0
- apsimo/autonomy/registry.py +339 -0
- apsimo/autonomy/scheduler.py +1822 -0
- apsimo/autonomy/synthesis.py +449 -0
- apsimo/backup.py +962 -0
- apsimo/beliefs/__init__.py +23 -0
- apsimo/beliefs/contradictions.py +109 -0
- apsimo/beliefs/decay.py +61 -0
- apsimo/beliefs/engine.py +479 -0
- apsimo/beliefs/models.py +67 -0
- apsimo/beliefs/promotion.py +41 -0
- apsimo/beliefs/resolve.py +58 -0
- apsimo/beliefs/source_claims.py +690 -0
- apsimo/beliefs/source_projection.py +883 -0
- apsimo/beliefs/source_time.py +208 -0
- apsimo/beliefs/store.py +133 -0
- apsimo/briefings/aggregators.py +824 -0
- apsimo/briefings/composer.py +420 -0
- apsimo/briefings/config.py +55 -0
- apsimo/briefings/delivery.py +439 -0
- apsimo/briefings/engagement.py +97 -0
- apsimo/briefings/engine.py +274 -0
- apsimo/briefings/enhancer.py +99 -0
- apsimo/briefings/models.py +183 -0
- apsimo/briefings/scheduler.py +382 -0
- apsimo/briefings/store.py +435 -0
- apsimo/chain/__init__.py +48 -0
- apsimo/chain/block.py +100 -0
- apsimo/chain/cli.py +704 -0
- apsimo/chain/genesis.py +443 -0
- apsimo/chain/identity.py +416 -0
- apsimo/chain/keys.py +1025 -0
- apsimo/chain/local_keys.py +187 -0
- apsimo/chain/manager.py +290 -0
- apsimo/chain/node.py +163 -0
- apsimo/chain/plugin_transactions.py +371 -0
- apsimo/chain/protocol.py +220 -0
- apsimo/chain/state_machine.py +676 -0
- apsimo/chain/storage.py +503 -0
- apsimo/chain/transactions.py +250 -0
- apsimo/chain/validation.py +397 -0
- apsimo/channels/__init__.py +1 -0
- apsimo/channels/manifest.py +31 -0
- apsimo/channels/migrations/001_channels_schema.sql +12 -0
- apsimo/channels/phone_gateways.py +42 -0
- apsimo/channels/presence.py +188 -0
- apsimo/channels/router.py +235 -0
- apsimo/channels/store.py +231 -0
- apsimo/cli.py +2688 -0
- apsimo/cognition/__init__.py +11 -0
- apsimo/cognition/charter.py +398 -0
- apsimo/cognition/drive_governance.py +3530 -0
- apsimo/cognition/evidence_pipeline.py +1627 -0
- apsimo/cognition/external_events.py +932 -0
- apsimo/cognition/goal_spine.py +3488 -0
- apsimo/cognition/introspection.py +214 -0
- apsimo/cognition/prompt.py +150 -0
- apsimo/cognition/runtime.py +108 -0
- apsimo/cognition/trigger.py +154 -0
- apsimo/commitments/__init__.py +18 -0
- apsimo/commitments/local_work.py +355 -0
- apsimo/commitments/store.py +1052 -0
- apsimo/commitments/work.py +91 -0
- apsimo/compat.py +53 -0
- apsimo/compression/__init__.py +467 -0
- apsimo/connectors/__init__.py +21 -0
- apsimo/connectors/base.py +152 -0
- apsimo/connectors/caldav_calendar.py +125 -0
- apsimo/connectors/fs_documents.py +85 -0
- apsimo/connectors/imap_email.py +138 -0
- apsimo/connectors/manager.py +218 -0
- apsimo/connectors/webhook_pull.py +88 -0
- apsimo/contacts/__init__.py +33 -0
- apsimo/contacts/comms.py +357 -0
- apsimo/contacts/config.py +79 -0
- apsimo/contacts/exporters/__init__.py +1 -0
- apsimo/contacts/exporters/vcard.py +71 -0
- apsimo/contacts/identity_links.py +251 -0
- apsimo/contacts/importer.py +280 -0
- apsimo/contacts/importers/__init__.py +1 -0
- apsimo/contacts/importers/batch.py +43 -0
- apsimo/contacts/importers/macos_contacts.py +101 -0
- apsimo/contacts/migrations/001_contacts_schema.sql +141 -0
- apsimo/contacts/migrations/002_trust_scopes.sql +36 -0
- apsimo/contacts/migrations/003_open_gateway_enum.sql +32 -0
- apsimo/contacts/migrations/004_contact_provision_operations.sql +18 -0
- apsimo/contacts/migrations/005_identity_links.sql +27 -0
- apsimo/contacts/models.py +308 -0
- apsimo/contacts/scoring.py +16 -0
- apsimo/contacts/store.py +1623 -0
- apsimo/contacts/transport_ingress.py +252 -0
- apsimo/contacts/world_bridge.py +314 -0
- apsimo/contextgate/__init__.py +69 -0
- apsimo/contextgate/chunker.py +169 -0
- apsimo/contextgate/estimate.py +54 -0
- apsimo/contextgate/gate.py +313 -0
- apsimo/contextgate/retrieve.py +115 -0
- apsimo/delivery/__init__.py +16 -0
- apsimo/delivery/bridge.py +1260 -0
- apsimo/delivery/channels.py +526 -0
- apsimo/delivery/classification.py +50 -0
- apsimo/delivery/rate_limiter.py +268 -0
- apsimo/delivery/reachout_policy.py +206 -0
- apsimo/directed/__init__.py +22 -0
- apsimo/directed/audit.py +167 -0
- apsimo/directed/intake.py +95 -0
- apsimo/directed/models.py +191 -0
- apsimo/directed/service.py +509 -0
- apsimo/directives/__init__.py +25 -0
- apsimo/directives/evidence.py +87 -0
- apsimo/directives/extractor.py +188 -0
- apsimo/directives/guard.py +364 -0
- apsimo/directives/models.py +206 -0
- apsimo/directives/service.py +372 -0
- apsimo/directives/store.py +167 -0
- apsimo/doctor.py +2173 -0
- apsimo/environment.py +43 -0
- apsimo/events/__init__.py +33 -0
- apsimo/events/broadcaster.py +98 -0
- apsimo/events/bus.py +217 -0
- apsimo/events/journal.py +863 -0
- apsimo/events/stream.py +131 -0
- apsimo/events/types.py +150 -0
- apsimo/execution_results.py +357 -0
- apsimo/feedback/__init__.py +5 -0
- apsimo/feedback/store.py +76 -0
- apsimo/feeds/__init__.py +19 -0
- apsimo/feeds/cli.py +84 -0
- apsimo/feeds/engine.py +437 -0
- apsimo/feeds/example-feed.yaml +77 -0
- apsimo/feeds/hermes_cron.py +126 -0
- apsimo/feeds/manager.py +235 -0
- apsimo/feeds/spec.py +250 -0
- apsimo/feeds/template.py +202 -0
- apsimo/gate/__init__.py +18 -0
- apsimo/gate/audit.py +61 -0
- apsimo/gate/communication_policy.py +166 -0
- apsimo/gate/config.py +72 -0
- apsimo/gate/context_provenance.py +170 -0
- apsimo/gate/env_risk.py +226 -0
- apsimo/gate/guard_audit.py +353 -0
- apsimo/gate/layers/__init__.py +1 -0
- apsimo/gate/layers/base.py +15 -0
- apsimo/gate/layers/l1_recipient.py +66 -0
- apsimo/gate/layers/l2_pii.py +134 -0
- apsimo/gate/layers/l3_cross_context.py +50 -0
- apsimo/gate/layers/l4_trust_tier.py +78 -0
- apsimo/gate/layers/l5_injection.py +199 -0
- apsimo/gate/layers/l6_review.py +86 -0
- apsimo/gate/layers/l7_delay.py +100 -0
- apsimo/gate/layers/tom2_epistemic.py +185 -0
- apsimo/gate/models.py +64 -0
- apsimo/gate/pending_dispatch.py +5 -0
- apsimo/gate/pipeline.py +206 -0
- apsimo/gate/rejection.py +259 -0
- apsimo/gate/response_guard.py +700 -0
- apsimo/gate/rulesets/injection_v1.yaml +51 -0
- apsimo/gate/surface_policy.py +189 -0
- apsimo/gate/taint.py +226 -0
- apsimo/genesis.json +9 -0
- apsimo/goals/__init__.py +100 -0
- apsimo/goals/config.py +38 -0
- apsimo/goals/decomposer.py +421 -0
- apsimo/goals/engine.py +617 -0
- apsimo/goals/inference.py +354 -0
- apsimo/goals/models.py +302 -0
- apsimo/goals/priority.py +270 -0
- apsimo/goals/queue_bridge.py +149 -0
- apsimo/goals/replan.py +450 -0
- apsimo/goals/schema.sql +89 -0
- apsimo/goals/store.py +692 -0
- apsimo/governed_actions.py +1708 -0
- apsimo/harness_integration/__init__.py +45 -0
- apsimo/harness_integration/context.py +41 -0
- apsimo/harness_integration/skills.py +231 -0
- apsimo/identity/__init__.py +26 -0
- apsimo/identity/participants.py +181 -0
- apsimo/identity/resolver.py +329 -0
- apsimo/identity_bootstrap/__init__.py +5 -0
- apsimo/identity_bootstrap/builder.py +208 -0
- apsimo/identity_bootstrap/corpus.py +443 -0
- apsimo/identity_bootstrap/models.py +54 -0
- apsimo/identity_bootstrap/runner.py +353 -0
- apsimo/identity_bootstrap/seeders/__init__.py +25 -0
- apsimo/identity_bootstrap/seeders/briefings.py +109 -0
- apsimo/identity_bootstrap/seeders/chain.py +57 -0
- apsimo/identity_bootstrap/seeders/goals.py +128 -0
- apsimo/identity_bootstrap/seeders/memory.py +191 -0
- apsimo/identity_bootstrap/seeders/neo4j_cognition.py +79 -0
- apsimo/identity_bootstrap/seeders/relationship.py +152 -0
- apsimo/identity_bootstrap/seeders/sessions.py +67 -0
- apsimo/identity_bootstrap/seeders/skills.py +92 -0
- apsimo/identity_bootstrap/seeders/task_queue.py +72 -0
- apsimo/identity_bootstrap/seeders/world_model.py +143 -0
- apsimo/identity_bootstrap/self_query.py +92 -0
- apsimo/identity_bootstrap/self_reflection.py +155 -0
- apsimo/identity_bootstrap/skill.py +37 -0
- apsimo/identity_bootstrap/verifier.py +436 -0
- apsimo/initiatives/__init__.py +20 -0
- apsimo/initiatives/action_registry.py +454 -0
- apsimo/initiatives/approval_authority.py +2105 -0
- apsimo/initiatives/approval_policy.py +123 -0
- apsimo/initiatives/assignment.py +263 -0
- apsimo/initiatives/backup_evidence.py +100 -0
- apsimo/initiatives/context_freshness.py +103 -0
- apsimo/initiatives/models.py +318 -0
- apsimo/initiatives/native_work.py +270 -0
- apsimo/initiatives/standing_approvals.py +232 -0
- apsimo/initiatives/store.py +1081 -0
- apsimo/initiatives/temporal_followup.py +410 -0
- apsimo/intelligence/__init__.py +1 -0
- apsimo/intelligence/cognition/__init__.py +24 -0
- apsimo/intelligence/cognition/gap_detector.py +148 -0
- apsimo/intelligence/cognition/metalearner.py +547 -0
- apsimo/intelligence/cognition/metrics_collector.py +217 -0
- apsimo/intelligence/cognition/performance_index.py +299 -0
- apsimo/intelligence/cognition/registry.py +192 -0
- apsimo/intelligence/cognition/strategy_adjuster.py +222 -0
- apsimo/intelligence/cognition/types.py +16 -0
- apsimo/intelligence/components/__init__.py +66 -0
- apsimo/intelligence/components/anomaly_detector.py +413 -0
- apsimo/intelligence/components/initiative_engine.py +2643 -0
- apsimo/intelligence/components/preference_learner.py +521 -0
- apsimo/intelligence/components/research_orchestrator.py +358 -0
- apsimo/intelligence/components/self_directed_thinker.py +221 -0
- apsimo/intelligence/components/self_reflector.py +252 -0
- apsimo/intelligence/components/session_continuity.py +154 -0
- apsimo/intelligence/components/task_planner.py +320 -0
- apsimo/intelligence/components/tool_learner.py +217 -0
- apsimo/intelligence/graph/__init__.py +79 -0
- apsimo/intelligence/graph/client.py +2483 -0
- apsimo/intelligence/graph/consolidator.py +405 -0
- apsimo/intelligence/graph/distiller.py +312 -0
- apsimo/intelligence/graph/migrations.py +129 -0
- apsimo/intelligence/graph/queries.py +248 -0
- apsimo/intelligence/graph/recall.py +281 -0
- apsimo/intelligence/graph/reconciler.py +144 -0
- apsimo/intelligence/graph/schema.py +337 -0
- apsimo/intelligence/graph/selection.py +252 -0
- apsimo/intelligence/learning/__init__.py +17 -0
- apsimo/intelligence/learning/continuous_learner.py +245 -0
- apsimo/intelligence/learning/feedback_store.py +321 -0
- apsimo/intelligence/mind_model/__init__.py +1 -0
- apsimo/intelligence/mind_model/graph_baseline.py +136 -0
- apsimo/intelligence/mind_model/signal_collector.py +361 -0
- apsimo/intelligence/relationships/__init__.py +11 -0
- apsimo/intelligence/relationships/profiler.py +389 -0
- apsimo/intelligence/relationships/scorer.py +560 -0
- apsimo/intelligence/relationships/signal_floor.py +66 -0
- apsimo/intelligence/relationships/trust_tiers.py +300 -0
- apsimo/intelligence/synthesis/__init__.py +40 -0
- apsimo/intelligence/synthesis/connection_discoverer.py +379 -0
- apsimo/intelligence/synthesis/cross_domain_analyzer.py +287 -0
- apsimo/intelligence/synthesis/insight_deliverer.py +171 -0
- apsimo/intelligence/synthesis/insight_store.py +79 -0
- apsimo/intelligence/synthesis/insight_validator.py +183 -0
- apsimo/intelligence/synthesis/novelty_scorer.py +267 -0
- apsimo/intelligence/turn_middleware/__init__.py +15 -0
- apsimo/intelligence/turn_middleware/memory_sync.py +119 -0
- apsimo/mcp/__init__.py +41 -0
- apsimo/mcp/__main__.py +6 -0
- apsimo/mcp/config.py +287 -0
- apsimo/mcp/server.py +501 -0
- apsimo/migrations.py +187 -0
- apsimo/mining/__init__.py +27 -0
- apsimo/mining/corpus.py +239 -0
- apsimo/mining/escalations.py +289 -0
- apsimo/mining/models.py +169 -0
- apsimo/mining/store.py +210 -0
- apsimo/models/__init__.py +30 -0
- apsimo/models/memory.py +80 -0
- apsimo/models/mesh.py +72 -0
- apsimo/models/person.py +104 -0
- apsimo/models/signal.py +108 -0
- apsimo/observations/__init__.py +15 -0
- apsimo/observations/store.py +277 -0
- apsimo/patterns/__init__.py +6 -0
- apsimo/patterns/extract.py +187 -0
- apsimo/patterns/store.py +227 -0
- apsimo/persona/__init__.py +1 -0
- apsimo/persona/engine.py +611 -0
- apsimo/persona/manifest.py +140 -0
- apsimo/projects/__init__.py +28 -0
- apsimo/projects/engine.py +1681 -0
- apsimo/projects/event_outbox.py +188 -0
- apsimo/projects/models.py +216 -0
- apsimo/projects/planner.py +181 -0
- apsimo/projects/store.py +1446 -0
- apsimo/proposals/__init__.py +12 -0
- apsimo/proposals/engine.py +114 -0
- apsimo/proposals/models.py +207 -0
- apsimo/qualification/__init__.py +1 -0
- apsimo/qualification/cases.py +75 -0
- apsimo/qualification/cli.py +51 -0
- apsimo/qualification/memory_cases.py +209 -0
- apsimo/qualification/records.py +92 -0
- apsimo/qualification/report.py +87 -0
- apsimo/qualification/runner.py +311 -0
- apsimo/qualification/structured_cases.py +131 -0
- apsimo/reasoning/__init__.py +13 -0
- apsimo/reasoning/executor.py +506 -0
- apsimo/reasoning/loop.py +373 -0
- apsimo/reasoning/native_tools/__init__.py +16 -0
- apsimo/reasoning/native_tools/calculate.py +141 -0
- apsimo/reasoning/native_tools/file_ops.py +150 -0
- apsimo/reasoning/native_tools/web_search.py +49 -0
- apsimo/reasoning/tool_policy.py +182 -0
- apsimo/redact/__init__.py +176 -0
- apsimo/repos/__init__.py +5 -0
- apsimo/repos/mirrors.py +204 -0
- apsimo/research/__init__.py +41 -0
- apsimo/research/artifact.py +482 -0
- apsimo/research/gatherer.py +387 -0
- apsimo/research/pipeline.py +513 -0
- apsimo/research/search/__init__.py +7 -0
- apsimo/research/search/base.py +41 -0
- apsimo/research/search/brave.py +59 -0
- apsimo/research/search/cache.py +51 -0
- apsimo/research/search/duckduckgo.py +103 -0
- apsimo/research/search/orchestrator.py +119 -0
- apsimo/research/search/serpapi.py +59 -0
- apsimo/research/search/tavily.py +59 -0
- apsimo/research/synthesizer.py +309 -0
- apsimo/router/__init__.py +30 -0
- apsimo/router/complexity_scorer.py +148 -0
- apsimo/router/endpoints.py +153 -0
- apsimo/router/fallback.py +58 -0
- apsimo/router/functions.py +243 -0
- apsimo/router/native_policy.py +52 -0
- apsimo/router/router.py +762 -0
- apsimo/router/self_learning.py +174 -0
- apsimo/router/tiers.py +677 -0
- apsimo/sandbox/__init__.py +21 -0
- apsimo/sandbox/backend.py +195 -0
- apsimo/sandbox/manager.py +173 -0
- apsimo/scope_bounds.py +7 -0
- apsimo/secrets/__init__.py +6 -0
- apsimo/secrets/backends/__init__.py +8 -0
- apsimo/secrets/backends/base.py +42 -0
- apsimo/secrets/backends/env.py +110 -0
- apsimo/secrets/backends/keyring.py +72 -0
- apsimo/secrets/backends/onepassword.py +232 -0
- apsimo/secrets/cli.py +191 -0
- apsimo/secrets/manager.py +160 -0
- apsimo/secrets/migration.py +101 -0
- apsimo/secrets/types.py +98 -0
- apsimo/seed.py +41 -0
- apsimo/self_model/__init__.py +37 -0
- apsimo/self_model/appraisals.py +673 -0
- apsimo/self_model/benchmark.py +1314 -0
- apsimo/self_model/brief.py +40 -0
- apsimo/self_model/event_concerns.py +1128 -0
- apsimo/self_model/execution_forecasts.py +353 -0
- apsimo/self_model/expectations.py +1595 -0
- apsimo/self_model/experiments.py +1150 -0
- apsimo/self_model/journal.py +148 -0
- apsimo/self_model/judgments.py +705 -0
- apsimo/self_model/native_outcomes.py +55 -0
- apsimo/self_model/params.py +220 -0
- apsimo/self_model/perspective.py +246 -0
- apsimo/self_model/reconcile.py +183 -0
- apsimo/self_model/reply_forecasts.py +381 -0
- apsimo/self_model/runtime_forecasts.py +296 -0
- apsimo/self_model/runtime_models.py +67 -0
- apsimo/self_model/settlement.py +207 -0
- apsimo/self_model/situation.py +1731 -0
- apsimo/self_model/store.py +883 -0
- apsimo/self_model/supervised.py +137 -0
- apsimo/self_model/thinker.py +99 -0
- apsimo/self_model/trust.py +388 -0
- apsimo/self_model/workspace.py +2388 -0
- apsimo/server.py +4197 -0
- apsimo/services/__init__.py +1 -0
- apsimo/services/agent_bridge.py +474 -0
- apsimo/services/initiative_executor.py +914 -0
- apsimo/services/instance.py +297 -0
- apsimo/sessions/__init__.py +22 -0
- apsimo/sessions/config.py +13 -0
- apsimo/sessions/context_loader.py +88 -0
- apsimo/sessions/federation_session.py +75 -0
- apsimo/sessions/isolated_session.py +98 -0
- apsimo/sessions/reports.py +84 -0
- apsimo/sessions/store.py +148 -0
- apsimo/setup.py +2818 -0
- apsimo/setup_hermes.py +879 -0
- apsimo/setup_local_work.py +218 -0
- apsimo/setup_native_goals.py +134 -0
- apsimo/setup_native_reviews.py +115 -0
- apsimo/skills/__init__.py +10 -0
- apsimo/skills/base.py +108 -0
- apsimo/skills/budget.py +28 -0
- apsimo/skills/executor.py +493 -0
- apsimo/skills/executors/__init__.py +1 -0
- apsimo/skills/executors/behavioral_correction.py +75 -0
- apsimo/skills/executors/capability_gap.py +38 -0
- apsimo/skills/executors/data_quality.py +163 -0
- apsimo/skills/executors/knowledge_acquisition.py +41 -0
- apsimo/skills/executors/operational_hygiene.py +185 -0
- apsimo/skills/executors/subsystem_health.py +169 -0
- apsimo/skills/hermes_export.py +431 -0
- apsimo/skills/index.py +123 -0
- apsimo/skills/learning/__init__.py +21 -0
- apsimo/skills/learning/novelty_detector.py +206 -0
- apsimo/skills/learning/pattern_extractor.py +199 -0
- apsimo/skills/learning/triggers.py +159 -0
- apsimo/skills/loader.py +246 -0
- apsimo/skills/migrations/002_progressive_loading.sql +6 -0
- apsimo/skills/migrations/backfill_triggers.py +20 -0
- apsimo/skills/models.py +202 -0
- apsimo/skills/packager.py +128 -0
- apsimo/skills/protocols.py +70 -0
- apsimo/skills/registry.py +191 -0
- apsimo/skills/runtime.py +58 -0
- apsimo/skills/sandbox_runner.py +229 -0
- apsimo/skills/scheduler.py +129 -0
- apsimo/skills/schema.py +79 -0
- apsimo/skills/security/__init__.py +12 -0
- apsimo/skills/security/guards.py +53 -0
- apsimo/skills/security/scanner.py +223 -0
- apsimo/skills_memory/__init__.py +26 -0
- apsimo/skills_memory/distill.py +159 -0
- apsimo/skills_memory/models.py +85 -0
- apsimo/skills_memory/retrieve.py +62 -0
- apsimo/skills_memory/store.py +172 -0
- apsimo/surprise/__init__.py +6 -0
- apsimo/surprise/accumulation.py +57 -0
- apsimo/surprise/scorer.py +102 -0
- apsimo/surprise/store.py +203 -0
- apsimo/task_queue/__init__.py +69 -0
- apsimo/task_queue/action_receipts.py +148 -0
- apsimo/task_queue/approval_relay_canary.py +108 -0
- apsimo/task_queue/config.py +85 -0
- apsimo/task_queue/contract.py +361 -0
- apsimo/task_queue/events.py +130 -0
- apsimo/task_queue/governor.py +1031 -0
- apsimo/task_queue/handlers/__init__.py +16 -0
- apsimo/task_queue/handlers/base.py +37 -0
- apsimo/task_queue/handlers/inference.py +640 -0
- apsimo/task_queue/handlers/monitoring.py +116 -0
- apsimo/task_queue/handlers/registry.py +75 -0
- apsimo/task_queue/handlers/subtask_handler.py +173 -0
- apsimo/task_queue/handlers/system_maintenance.py +147 -0
- apsimo/task_queue/mesh_integration.py +111 -0
- apsimo/task_queue/models.py +317 -0
- apsimo/task_queue/queue_manager.py +8286 -0
- apsimo/task_queue/routing.py +287 -0
- apsimo/task_queue/scheduler.py +252 -0
- apsimo/task_queue/schema.sql +197 -0
- apsimo/task_queue/work_control.py +342 -0
- apsimo/task_queue/worker.py +993 -0
- apsimo/telemetry.py +145 -0
- apsimo/tom/__init__.py +6 -0
- apsimo/tom/affect.py +387 -0
- apsimo/tom/approvals.py +171 -0
- apsimo/tom/arcs.py +896 -0
- apsimo/tom/asymmetry.py +131 -0
- apsimo/tom/eligibility.py +248 -0
- apsimo/tom/engagement.py +214 -0
- apsimo/tom/exposure.py +214 -0
- apsimo/tom/extractor.py +306 -0
- apsimo/tom/fact_adapters.py +144 -0
- apsimo/tom/facts.py +326 -0
- apsimo/tom/integration.py +592 -0
- apsimo/tom/leveled.py +118 -0
- apsimo/tom/levels.py +247 -0
- apsimo/tom/recipient_audit.py +995 -0
- apsimo/tom/recipient_simulator.py +593 -0
- apsimo/tom/source_lineage.py +93 -0
- apsimo/tom/tom2.py +277 -0
- apsimo/tom/visibility.py +559 -0
- apsimo/tom/visibility_store.py +414 -0
- apsimo/tools/__init__.py +0 -0
- apsimo/tools/definitions.py +740 -0
- apsimo/tools/handlers.py +943 -0
- apsimo/toolsmith/__init__.py +26 -0
- apsimo/toolsmith/authority.py +166 -0
- apsimo/toolsmith/engine.py +559 -0
- apsimo/toolsmith/integrity.py +100 -0
- apsimo/toolsmith/miner.py +145 -0
- apsimo/toolsmith/policy.py +110 -0
- apsimo/toolsmith/registry.py +635 -0
- apsimo/turns/__init__.py +17 -0
- apsimo/turns/audio.py +134 -0
- apsimo/turns/documents.py +235 -0
- apsimo/turns/executions.py +486 -0
- apsimo/turns/hermes_history.py +245 -0
- apsimo/turns/hermes_kanban.py +268 -0
- apsimo/turns/hermes_work.py +96 -0
- apsimo/turns/idempotency.py +752 -0
- apsimo/turns/local_work.py +115 -0
- apsimo/turns/media.py +581 -0
- apsimo/turns/reported_workers.py +196 -0
- apsimo/turns/source_annotations.py +283 -0
- apsimo/turns/source_attribution.py +154 -0
- apsimo/turns/source_read.py +351 -0
- apsimo/turns/source_vectors.py +263 -0
- apsimo/turns/video.py +210 -0
- apsimo/util/autonomy_preset.py +220 -0
- apsimo/util/instance.py +92 -0
- apsimo/util/model_output.py +25 -0
- apsimo/util/quiet_hours.py +27 -0
- apsimo/util/session_safety.py +37 -0
- apsimo/util/temporal.py +343 -0
- apsimo/vector/__init__.py +75 -0
- apsimo/vector/backfill.py +171 -0
- apsimo/vector/caption.py +114 -0
- apsimo/vector/collections.py +51 -0
- apsimo/vector/config.py +102 -0
- apsimo/vector/embedder.py +670 -0
- apsimo/vector/image_preprocess.py +406 -0
- apsimo/vector/image_store.py +296 -0
- apsimo/vector/indexes.py +162 -0
- apsimo/vector/migrate.py +334 -0
- apsimo/vector/multimodal_provider.py +417 -0
- apsimo/vector/multimodal_types.py +87 -0
- apsimo/vector/openai_provider.py +119 -0
- apsimo/vector/query.py +49 -0
- apsimo/vector/reranker.py +565 -0
- apsimo/vector/safety_image.py +159 -0
- apsimo/vector/scanner.py +197 -0
- apsimo/vector/setup.py +289 -0
- apsimo/vector/store.py +533 -0
- apsimo/vector/tiers.py +263 -0
- apsimo/work_orders.py +925 -0
- apsimo/workers/__init__.py +21 -0
- apsimo/workers/agent_bridge.py +640 -0
- apsimo/workers/colony_worker.py +382 -0
- apsimo/workers/queue_worker.py +441 -0
- apsimo/workers/skills_sync.py +152 -0
- apsimo/world_model/__init__.py +71 -0
- apsimo/world_model/causal_maintenance.py +131 -0
- apsimo/world_model/causal_policy.py +43 -0
- apsimo/world_model/causal_query.py +125 -0
- apsimo/world_model/confidence.py +54 -0
- apsimo/world_model/config.py +64 -0
- apsimo/world_model/constants.py +97 -0
- apsimo/world_model/entities.py +145 -0
- apsimo/world_model/expectation_resolvers.py +177 -0
- apsimo/world_model/extraction/__init__.py +7 -0
- apsimo/world_model/extraction/base.py +62 -0
- apsimo/world_model/extraction/conversation_extractor.py +262 -0
- apsimo/world_model/extraction/detector.py +74 -0
- apsimo/world_model/extraction/document_extractor.py +78 -0
- apsimo/world_model/extraction/formats/__init__.py +24 -0
- apsimo/world_model/extraction/formats/csv_fmt.py +68 -0
- apsimo/world_model/extraction/formats/html_fmt.py +72 -0
- apsimo/world_model/extraction/formats/json_fmt.py +68 -0
- apsimo/world_model/extraction/formats/pdf.py +43 -0
- apsimo/world_model/extraction/formats/text.py +27 -0
- apsimo/world_model/extraction/llm_extractor.py +164 -0
- apsimo/world_model/extraction/pipeline.py +73 -0
- apsimo/world_model/integrations/__init__.py +5 -0
- apsimo/world_model/integrations/mind_model_bridge.py +115 -0
- apsimo/world_model/integrations/social_intel_bridge.py +120 -0
- apsimo/world_model/jobs/__init__.py +4 -0
- apsimo/world_model/jobs/extraction_job.py +168 -0
- apsimo/world_model/llm_extract.py +572 -0
- apsimo/world_model/neo4j/__init__.py +5 -0
- apsimo/world_model/neo4j/backend.py +654 -0
- apsimo/world_model/observations.py +155 -0
- apsimo/world_model/populator.py +307 -0
- apsimo/world_model/postgres/__init__.py +1 -0
- apsimo/world_model/postgres/backend.py +683 -0
- apsimo/world_model/relationships.py +25 -0
- apsimo/world_model/resolution/__init__.py +13 -0
- apsimo/world_model/resolution/entity_resolver.py +232 -0
- apsimo/world_model/resolution/merge_audit.py +16 -0
- apsimo/world_model/resolution/merge_workflow.py +117 -0
- apsimo/world_model/source_reports.py +121 -0
- apsimo/world_model/sqlite/__init__.py +4 -0
- apsimo/world_model/sqlite/backend.py +855 -0
- apsimo/world_model/sqlite/schema.sql +132 -0
- apsimo/world_model/store.py +545 -0
- apsimo-1.3.0.dist-info/METADATA +78 -0
- apsimo-1.3.0.dist-info/RECORD +614 -0
- apsimo-1.3.0.dist-info/WHEEL +5 -0
- apsimo-1.3.0.dist-info/entry_points.txt +11 -0
- apsimo-1.3.0.dist-info/licenses/LICENSE +21 -0
- apsimo-1.3.0.dist-info/top_level.txt +2 -0
- colony_sidecar/__init__.py +4 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""DirectiveExtractor -- capture standing directives from owner messages.
|
|
2
|
+
|
|
3
|
+
Deterministic, cue-driven detection of prohibitions ("don't / stop / avoid /
|
|
4
|
+
leave X alone"), requirements ("always / from now on / make sure to X"), and
|
|
5
|
+
revocations ("you can X again / disregard that"). Owner-gated by the caller.
|
|
6
|
+
|
|
7
|
+
Only explicitly lasting clauses become standing rules. Ordinary task limits
|
|
8
|
+
stay in their source conversation. Pure communication-STYLE directives
|
|
9
|
+
("be concise", "no emoji") are left to the PreferenceLearner and skipped here.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import re
|
|
15
|
+
from typing import List
|
|
16
|
+
|
|
17
|
+
from apsimo.directives.models import (
|
|
18
|
+
Directive, Polarity, normalize_terms,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Prohibition openers -> capture the subject that follows.
|
|
22
|
+
_PROHIBIT_PATTERNS = [
|
|
23
|
+
re.compile(r"\b(?:do\s*not|don'?t|never|no\s+longer)\s+(?P<subj>.+)", re.I),
|
|
24
|
+
re.compile(r"\b(?:stop|avoid|quit|cease|drop|skip)\s+(?P<subj>.+)", re.I),
|
|
25
|
+
re.compile(r"\b(?:steer\s+clear\s+of|stay\s+away\s+from|lay\s+off|hands\s+off)\s+(?P<subj>.+)", re.I),
|
|
26
|
+
re.compile(r"\bleave\s+(?P<subj>.+?)\s+alone\b", re.I),
|
|
27
|
+
re.compile(r"\b(?:ignore|forget\s+about)\s+(?P<subj>.+)", re.I),
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
# Revocation openers -> the owner is lifting a prior boundary.
|
|
31
|
+
_REVOKE_PATTERNS = [
|
|
32
|
+
re.compile(r"\b(?:you\s+can\s+now|go\s+ahead\s+and|actually,?\s+(?:you\s+can|go\s+ahead)|nevermind|never\s+mind|disregard(?:\s+(?:that|what\s+i\s+said))?|forget\s+what\s+i\s+said)\s*(?P<subj>.*)", re.I),
|
|
33
|
+
re.compile(r"\b(?:resume|unpause|restart)\s+(?P<subj>.+)", re.I),
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
# One-command global pause (Amendment 1.5): the owner's kill switch. Any of
|
|
37
|
+
# these phrasings becomes an immediate GLOBAL ACT-level boundary: every
|
|
38
|
+
# autonomous act is refused until the owner lifts it (staged confirmation,
|
|
39
|
+
# like any boundary lift). Perception/reads stay open (ACT semantics).
|
|
40
|
+
_GLOBAL_PAUSE_PATTERNS = [
|
|
41
|
+
re.compile(r"\b(?:stop|pause|halt|freeze|suspend)\s+(?:(?:all|your|the|any)\s+)*"
|
|
42
|
+
r"(?:acting|autonomy|autonomous\s+(?:actions?|work|mode)|"
|
|
43
|
+
r"taking\s+actions?|doing\s+things)\b", re.I),
|
|
44
|
+
re.compile(r"\bstand\s+down\b", re.I),
|
|
45
|
+
re.compile(r"\bstop\s+acting\s+on\s+your\s+own\b", re.I),
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def make_global_pause_directive(raw_text: str = "",
|
|
50
|
+
source: str = "owner_explicit") -> Directive:
|
|
51
|
+
"""The global ACT-level pause boundary (kill switch)."""
|
|
52
|
+
from apsimo.directives.models import GLOBAL_PAUSE_TERM, Level
|
|
53
|
+
return Directive(
|
|
54
|
+
subject="all autonomous actions (global pause)",
|
|
55
|
+
polarity=Polarity.PROHIBIT,
|
|
56
|
+
raw_text=raw_text or "stop acting",
|
|
57
|
+
# GLOBAL_PAUSE_TERM makes the guard refuse every act-capability
|
|
58
|
+
# action; the plain terms let "resume autonomy/acting" find and lift
|
|
59
|
+
# this directive through the normal staged confirmation.
|
|
60
|
+
match_terms=[GLOBAL_PAUSE_TERM, "autonomy", "acting", "autonomous"],
|
|
61
|
+
source=source, confidence=1.0, level=Level.ACT,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Clause terminators: cut the subject at the first of these.
|
|
65
|
+
_CLAUSE_END = re.compile(r"\.(?=\s|$)|[;,!?\n]| but | and then | because | since | unless | so that ", re.I)
|
|
66
|
+
|
|
67
|
+
# Words that mark a pure communication-style directive (handled elsewhere).
|
|
68
|
+
_STYLE_ONLY = frozenset({
|
|
69
|
+
"concise", "verbose", "brief", "shorter", "longer", "emoji", "emojis",
|
|
70
|
+
"formal", "casual", "tone", "bullets", "bullet", "markdown", "wordy",
|
|
71
|
+
"replies", "reply", "responses", "response", "language", "words", "word",
|
|
72
|
+
# fillers that commonly accompany a pure style directive
|
|
73
|
+
"using", "use", "being", "sound", "sounding", "talking", "writing",
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _clean_subject(subj: str) -> str:
|
|
78
|
+
subj = _CLAUSE_END.split(subj, 1)[0].strip()
|
|
79
|
+
# strip a leading gerund/verb that adds no discriminating value is NOT done
|
|
80
|
+
# here: keeping "researching competitors" preserves intent; the guard's
|
|
81
|
+
# loose stem matching handles research/researching.
|
|
82
|
+
return subj.strip(" '\"")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _is_style_only(subj: str) -> bool:
|
|
86
|
+
terms = set(normalize_terms(subj))
|
|
87
|
+
return bool(terms) and terms.issubset(_STYLE_ONLY)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# Durable admission requires positive lasting scope. Task instructions remain
|
|
91
|
+
# canonical evidence, even when they contain imperatives such as "never".
|
|
92
|
+
_TASK_SCOPE = re.compile(
|
|
93
|
+
r"\b(?:for|during) (?:this|the current) (?:task|request|phase|run|example)\b"
|
|
94
|
+
r"|\b(?:this is|these are) (?:an? |the )?(?:temporary|one[- ]off) (?:task |standing )?(?:instruction|requirement|rule|constraint)s?\b"
|
|
95
|
+
r"|(?:^|[.!?;\n]\s*)(?:temporary|one[- ]off) (?:task )?(?:instruction|requirement|rule|constraint)s?\s*[:.!?;\n]"
|
|
96
|
+
r"|\bnot (?:a |my )?(?:personal preference|standing (?:rule|instruction|boundary))\b"
|
|
97
|
+
r"|\b(?:for now|until (?:this|the) (?:task|phase|run) (?:ends|finishes|completes))\b", re.I)
|
|
98
|
+
_STANDING = re.compile(r"^(?:please\s+)?(?:(?:from now on|going forward|as a standing rule|as a permanent rule|permanently)[,:]?\s+|(?:always|never)\s+)", re.I)
|
|
99
|
+
_SENTENCES = re.compile(r"(?<=[.!?])\s+|[;\n]+")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def standing_clauses(message: str) -> List[str]:
|
|
103
|
+
"""Bounded, exact clauses with explicit lasting intent, not inferred intent."""
|
|
104
|
+
text = (message or '').strip()
|
|
105
|
+
if not text or _TASK_SCOPE.search(text):
|
|
106
|
+
return []
|
|
107
|
+
admitted = []
|
|
108
|
+
for candidate in _SENTENCES.split(text):
|
|
109
|
+
clause = candidate.strip()
|
|
110
|
+
prefix = _STANDING.match(clause)
|
|
111
|
+
if prefix is None:
|
|
112
|
+
continue
|
|
113
|
+
# The keyword guard cannot enforce exceptions or contextual conditions.
|
|
114
|
+
# Keep these instructions in the conversation instead of broadening
|
|
115
|
+
# them into an unconditional standing prohibition.
|
|
116
|
+
if re.search(r'\b(?:unless|until|except|if|when|without)\b', clause, re.I):
|
|
117
|
+
continue
|
|
118
|
+
# Keep the exact rule clause, excluding explanatory or unrelated text.
|
|
119
|
+
ending = _CLAUSE_END.search(clause, prefix.end())
|
|
120
|
+
if ending:
|
|
121
|
+
end = ending.start() + (ending[0] in '.!?')
|
|
122
|
+
clause = clause[:end].strip()
|
|
123
|
+
if 0 < len(clause) <= 500:
|
|
124
|
+
if clause not in admitted:
|
|
125
|
+
admitted.append(clause)
|
|
126
|
+
return admitted
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def extract_directives(message: str, *, source: str = "owner_explicit") -> List[Directive]:
|
|
130
|
+
"""Extract explicit standing owner rules; one-off work is not a rule."""
|
|
131
|
+
from apsimo.directives.models import Level
|
|
132
|
+
text = (message or '').strip()
|
|
133
|
+
if not text:
|
|
134
|
+
return []
|
|
135
|
+
# The standalone pause command retains its immediate, explicit meaning.
|
|
136
|
+
command = re.sub(r'^please\s+', '', text.rstrip('.!?'), flags=re.I)
|
|
137
|
+
command = re.sub(r'\s+(?:for now|for a while)$', '', command, flags=re.I)
|
|
138
|
+
if any(pattern.fullmatch(command) for pattern in _GLOBAL_PAUSE_PATTERNS):
|
|
139
|
+
return [make_global_pause_directive(text, source=source)]
|
|
140
|
+
# Lifting a rule is explicit interaction with an existing rule, not new
|
|
141
|
+
# durable learning. Keep the existing confirmation behavior unchanged.
|
|
142
|
+
for pattern in _REVOKE_PATTERNS:
|
|
143
|
+
match = pattern.match(text)
|
|
144
|
+
if match and not _TASK_SCOPE.search(text):
|
|
145
|
+
subject = _clean_subject(match.group('subj') or '')
|
|
146
|
+
if subject and not _is_style_only(subject):
|
|
147
|
+
directive = Directive(subject=subject, polarity=Polarity.PREFER,
|
|
148
|
+
raw_text=text, source=source)
|
|
149
|
+
directive.__dict__['_revocation'] = True
|
|
150
|
+
return [directive]
|
|
151
|
+
return []
|
|
152
|
+
result = []
|
|
153
|
+
for clause in standing_clauses(text):
|
|
154
|
+
prefix = _STANDING.match(clause)
|
|
155
|
+
body = clause[prefix.end():].strip()
|
|
156
|
+
# Never/always themselves are the lasting imperative.
|
|
157
|
+
if re.search(r'\bnever\s+$', prefix[0], re.I):
|
|
158
|
+
body = 'never '+body
|
|
159
|
+
polarity, subject = Polarity.REQUIRE, body
|
|
160
|
+
for pattern in _PROHIBIT_PATTERNS:
|
|
161
|
+
match = pattern.match(body)
|
|
162
|
+
if match:
|
|
163
|
+
polarity, subject = Polarity.PROHIBIT, match.group('subj')
|
|
164
|
+
break
|
|
165
|
+
if polarity == Polarity.PROHIBIT and re.search(r'\b(?:before|after|once)\b', body, re.I):
|
|
166
|
+
continue
|
|
167
|
+
subject = re.sub(r'^always\s+', '', _clean_subject(subject), flags=re.I)
|
|
168
|
+
if polarity == Polarity.PROHIBIT and re.match(
|
|
169
|
+
r'^(?:(?:forget|fail|neglect|refuse)\s+to\b|stop\s+\w+ing\b)', subject, re.I):
|
|
170
|
+
continue # Negated omissions do not prohibit the underlying action.
|
|
171
|
+
if (not subject or len(subject) < 2 or _is_style_only(subject)
|
|
172
|
+
or re.match(r'^(?:(?:I|we|he|she|they|my|your|did|had|has|was|were|is|am|are)\b|have\s+(?:I|we|they)\b)', subject, re.I)
|
|
173
|
+
or re.match(r'^(?:(?:again|ever|really|actually|previously|once)\s+)*(?:did|do|does|have|has|had|am|is|are|was|were)\s+(?:I|we|he|she|they|you)\b', subject, re.I)
|
|
174
|
+
or polarity == Polarity.REQUIRE and not re.match(
|
|
175
|
+
r'^(?:ask|check|confirm|consult|contact|keep|leave|look|make|maintain|notify|obtain|read|remember|report|request|require|respect|review|run|save|seek|send|show|tell|test|track|use|verify|wait)\b', subject, re.I)):
|
|
176
|
+
continue
|
|
177
|
+
# Only the prohibited action determines observation versus action.
|
|
178
|
+
# Unrelated "read the documentation" elsewhere cannot escalate it.
|
|
179
|
+
perception = re.match(r'^(?:even\s+)?(?:look(?:ing)?\s+at|read(?:ing)?\b|watch(?:ing)?\b|monitor(?:ing)?\b|track(?:ing)?\b|stay\s+out\s+of|snoop|peek|observ(?:e|ing))', subject, re.I)
|
|
180
|
+
result.append(Directive(subject=subject, polarity=polarity, raw_text=clause,
|
|
181
|
+
match_terms=normalize_terms(subject), source=source,
|
|
182
|
+
confidence=0.9 if source == 'owner_explicit' else 0.6,
|
|
183
|
+
level=Level.OBSERVE if polarity == Polarity.PROHIBIT and perception else Level.ACT))
|
|
184
|
+
return result
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def is_revocation(directive: Directive) -> bool:
|
|
188
|
+
return bool(directive.__dict__.get("_revocation"))
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
"""DirectiveGuard -- enforce owner boundaries before any autonomous action.
|
|
2
|
+
|
|
3
|
+
Given a proposed action (deliver a message, execute a tool, run a directed
|
|
4
|
+
task, generate an initiative), the guard checks it against every active
|
|
5
|
+
PROHIBIT directive and returns a Verdict. Callers at each action chokepoint
|
|
6
|
+
(executor, delivery, generation, directed-action) consult it and REFUSE when
|
|
7
|
+
a boundary is violated.
|
|
8
|
+
|
|
9
|
+
Matching is deterministic and biased toward catching real violations on
|
|
10
|
+
SPECIFIC subjects (repo names, business names, proper nouns) while avoiding
|
|
11
|
+
false blocks on generic words. This hard, code-level gate is paired with
|
|
12
|
+
soft LLM awareness (active directives injected into reasoning context), so a
|
|
13
|
+
model can neither forget nor talk its way past a standing boundary.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
import re
|
|
20
|
+
import time
|
|
21
|
+
from collections import deque
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
from typing import Any, Dict, List, Optional
|
|
24
|
+
|
|
25
|
+
from apsimo.directives.models import (
|
|
26
|
+
GLOBAL_PAUSE_TERM, Directive, Level, Polarity, normalize_terms,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
# Capability classes (tiered boundary semantics): boundaries bind ACTION, not
|
|
32
|
+
# judgment or perception, unless explicitly about perception. READ kinds stay
|
|
33
|
+
# open under an ACT-level boundary and are blocked only by an OBSERVE
|
|
34
|
+
# blackout. Active autonomous work (research jobs, delegation, delivery,
|
|
35
|
+
# execution) is an ACT capability -- "stop researching X" must stop research
|
|
36
|
+
# about X, while awareness/reads of X survive "don't touch X".
|
|
37
|
+
_READ_KINDS = frozenset({"repo_read", "read", "recall", "populate", "observe"})
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class DirectiveStoreUnavailable(RuntimeError):
|
|
41
|
+
"""The active owner-boundary set could not be read safely.
|
|
42
|
+
|
|
43
|
+
An unavailable or malformed store is not equivalent to a healthy store
|
|
44
|
+
with zero active prohibitions. Enforcement callers use this distinct
|
|
45
|
+
failure to apply their capability-specific fail-closed policy.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def action_capability(kind: str) -> str:
|
|
50
|
+
return "read" if (kind or "").lower() in _READ_KINDS else "act"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def boundary_fail_closed() -> bool:
|
|
54
|
+
"""Whether an ERROR inside a boundary check refuses the action.
|
|
55
|
+
|
|
56
|
+
Default TRUE: a boundary is an owner-set prohibition, so an exception
|
|
57
|
+
while evaluating it must refuse (fail closed) rather than silently
|
|
58
|
+
allow the very action the owner may have forbidden. Set
|
|
59
|
+
COLONY_BOUNDARY_FAIL_CLOSED=false to restore the legacy allow-on-error
|
|
60
|
+
behavior.
|
|
61
|
+
"""
|
|
62
|
+
from apsimo.util.autonomy_preset import resolve_bool
|
|
63
|
+
return resolve_bool("COLONY_BOUNDARY_FAIL_CLOSED", True)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class Action:
|
|
68
|
+
"""A proposed autonomous action to be checked against boundaries."""
|
|
69
|
+
|
|
70
|
+
kind: str # deliver | execute_tool | directed_action | generate | research
|
|
71
|
+
text: str = "" # description / message / topic
|
|
72
|
+
target: str = "" # recipient / repo / business / subject
|
|
73
|
+
entity_id: str = "" # optional graph entity id
|
|
74
|
+
tool_name: str = ""
|
|
75
|
+
args: Optional[Dict[str, Any]] = None
|
|
76
|
+
high_risk: bool = False # outbound / mutating / directed -> fail-closed on ambiguity
|
|
77
|
+
|
|
78
|
+
def searchable_terms(self) -> List[str]:
|
|
79
|
+
parts = [self.text, self.target, self.tool_name]
|
|
80
|
+
if self.args:
|
|
81
|
+
for v in self.args.values():
|
|
82
|
+
if isinstance(v, (str, int, float)):
|
|
83
|
+
parts.append(str(v))
|
|
84
|
+
toks = set()
|
|
85
|
+
for p in parts:
|
|
86
|
+
toks.update(normalize_terms(p))
|
|
87
|
+
return list(toks)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass
|
|
91
|
+
class Verdict:
|
|
92
|
+
allowed: bool
|
|
93
|
+
violations: List[Directive] = field(default_factory=list)
|
|
94
|
+
reason: str = "ok"
|
|
95
|
+
|
|
96
|
+
def as_dict(self) -> Dict[str, Any]:
|
|
97
|
+
return {
|
|
98
|
+
"allowed": self.allowed,
|
|
99
|
+
"reason": self.reason,
|
|
100
|
+
"violations": [
|
|
101
|
+
{"id": d.id, **({} if d.evidence else {"subject": d.subject, "raw_text": d.raw_text})}
|
|
102
|
+
for d in self.violations
|
|
103
|
+
],
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _common_terms() -> frozenset:
|
|
108
|
+
"""Terms that appear in virtually every internal action text and must
|
|
109
|
+
never count as a match signal on their own: the product name, the agent's
|
|
110
|
+
own name, plus any deployment-supplied extras (COLONY_MATCH_COMMON_TERMS,
|
|
111
|
+
csv). Live incident 2026-07-05: a fragment boundary whose only surviving
|
|
112
|
+
term was "colony" matched every job that said "report to Colony"."""
|
|
113
|
+
import os
|
|
114
|
+
terms = {"colony"}
|
|
115
|
+
agent = os.environ.get("COLONY_AGENT_NAME", "")
|
|
116
|
+
for tok in re.findall(r"[a-z0-9]+", agent.lower()):
|
|
117
|
+
if len(tok) > 1:
|
|
118
|
+
terms.add(tok)
|
|
119
|
+
extra = os.environ.get("COLONY_MATCH_COMMON_TERMS", "")
|
|
120
|
+
for tok in extra.lower().split(","):
|
|
121
|
+
tok = tok.strip()
|
|
122
|
+
if tok:
|
|
123
|
+
terms.add(tok)
|
|
124
|
+
return frozenset(terms)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _is_distinctive(term: str) -> bool:
|
|
128
|
+
"""A term specific enough that a single match is a real signal.
|
|
129
|
+
|
|
130
|
+
The bar is deliberately high (>=8 chars, or a separator/digit shape like
|
|
131
|
+
repo/path/version tokens): mid-length generic words ("attempt",
|
|
132
|
+
"respond", "message") matching alone caused mass false blocks. Shorter
|
|
133
|
+
real subjects still match through the all-terms path below.
|
|
134
|
+
"""
|
|
135
|
+
if term in _common_terms():
|
|
136
|
+
return False
|
|
137
|
+
return (
|
|
138
|
+
len(term) >= 8
|
|
139
|
+
or any(c in term for c in "-_/.")
|
|
140
|
+
or any(c.isdigit() for c in term)
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _terms_match(directive_terms: List[str], action_terms: List[str]) -> bool:
|
|
145
|
+
"""True if the directive's subject matches the action's terms."""
|
|
146
|
+
if not directive_terms:
|
|
147
|
+
return False
|
|
148
|
+
action = set(action_terms)
|
|
149
|
+
|
|
150
|
+
def _hit(t: str) -> bool:
|
|
151
|
+
if t in action:
|
|
152
|
+
return True
|
|
153
|
+
# Stem/prefix containment for morphological variants only
|
|
154
|
+
# (research/researching, cert/certs, deploy/deployment). The shorter
|
|
155
|
+
# side must be a real stem: >=5 chars, or within 2 chars of the
|
|
156
|
+
# longer word. Without that floor, "what" counted as a variant of
|
|
157
|
+
# "whatsapp" and a WhatsApp boundary matched any sentence containing
|
|
158
|
+
# the word "what" (live false-block, 2026-07-05).
|
|
159
|
+
if len(t) >= 4:
|
|
160
|
+
for a in action:
|
|
161
|
+
if a == t:
|
|
162
|
+
return True
|
|
163
|
+
if len(a) >= 4 and (a.startswith(t) or t.startswith(a)):
|
|
164
|
+
shorter, longer = (a, t) if len(a) <= len(t) else (t, a)
|
|
165
|
+
if len(shorter) >= 5 or len(longer) - len(shorter) <= 2:
|
|
166
|
+
return True
|
|
167
|
+
return False
|
|
168
|
+
|
|
169
|
+
hits = [t for t in directive_terms if _hit(t)]
|
|
170
|
+
if not hits:
|
|
171
|
+
return False
|
|
172
|
+
# A distinctive subject term (repo/business/proper-noun-like) matching alone
|
|
173
|
+
# is a real signal. Generic short terms require the whole subject to match,
|
|
174
|
+
# and an all-terms match must include at least one substantive (len>=4,
|
|
175
|
+
# non-common) term so pronoun-grade fragments can never bind.
|
|
176
|
+
if any(_is_distinctive(t) for t in hits):
|
|
177
|
+
return True
|
|
178
|
+
common = _common_terms()
|
|
179
|
+
if not any(len(t) >= 4 and t not in common for t in hits):
|
|
180
|
+
return False
|
|
181
|
+
return len(hits) == len(directive_terms)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class DirectiveGuard:
|
|
185
|
+
"""Consults the directive store and enforces PROHIBIT boundaries."""
|
|
186
|
+
|
|
187
|
+
def __init__(self, store: Any) -> None:
|
|
188
|
+
self._store = store
|
|
189
|
+
# Recent refusals, for observability / "why didn't you do X" answers.
|
|
190
|
+
self._recent_blocks: deque = deque(maxlen=100)
|
|
191
|
+
# Entity-scoped matching (2): {frozenset(alias_tokens): entity_id}. Built
|
|
192
|
+
# from the world model as entities are learned; empty -> keyword-only.
|
|
193
|
+
self._entity_index: Dict[frozenset, str] = {}
|
|
194
|
+
|
|
195
|
+
def set_entity_index(self, entities: Dict[str, List[str]]) -> None:
|
|
196
|
+
"""Refresh the alias->entity_id index. entities = {entity_id: [names/aliases]}."""
|
|
197
|
+
index: Dict[frozenset, str] = {}
|
|
198
|
+
for eid, aliases in (entities or {}).items():
|
|
199
|
+
for alias in aliases:
|
|
200
|
+
toks = frozenset(normalize_terms(alias))
|
|
201
|
+
if toks:
|
|
202
|
+
index[toks] = eid
|
|
203
|
+
self._entity_index = index
|
|
204
|
+
|
|
205
|
+
def _resolve_entities(self, terms: set) -> set:
|
|
206
|
+
"""Entity IDs whose alias tokens are all present in `terms`."""
|
|
207
|
+
if not self._entity_index or not terms:
|
|
208
|
+
return set()
|
|
209
|
+
return {eid for alias_toks, eid in self._entity_index.items()
|
|
210
|
+
if alias_toks <= terms}
|
|
211
|
+
|
|
212
|
+
def _active_prohibitions(self) -> List[Directive]:
|
|
213
|
+
if self._store is None:
|
|
214
|
+
raise DirectiveStoreUnavailable("directive store is unavailable")
|
|
215
|
+
try:
|
|
216
|
+
active = self._store.active(polarity=Polarity.PROHIBIT)
|
|
217
|
+
except Exception as exc:
|
|
218
|
+
logger.warning("DirectiveGuard: store read failed: %s", exc)
|
|
219
|
+
raise DirectiveStoreUnavailable(
|
|
220
|
+
"directive store active-boundary read failed"
|
|
221
|
+
) from exc
|
|
222
|
+
|
|
223
|
+
if not isinstance(active, (list, tuple)) or any(
|
|
224
|
+
not isinstance(directive, Directive) for directive in active
|
|
225
|
+
):
|
|
226
|
+
logger.warning(
|
|
227
|
+
"DirectiveGuard: store returned a malformed active-boundary set"
|
|
228
|
+
)
|
|
229
|
+
raise DirectiveStoreUnavailable(
|
|
230
|
+
"directive store returned a malformed active-boundary set"
|
|
231
|
+
)
|
|
232
|
+
return list(active)
|
|
233
|
+
|
|
234
|
+
def check(self, action: Action) -> Verdict:
|
|
235
|
+
"""Return a Verdict. allowed=False means the action violates a boundary."""
|
|
236
|
+
prohibitions = self._active_prohibitions()
|
|
237
|
+
if not prohibitions:
|
|
238
|
+
return Verdict(allowed=True, reason="no_active_boundaries")
|
|
239
|
+
|
|
240
|
+
action_terms = action.searchable_terms()
|
|
241
|
+
action_term_set = set(action_terms)
|
|
242
|
+
capability = action_capability(action.kind)
|
|
243
|
+
|
|
244
|
+
# Global pause (Amendment 1.5): an active kill-switch boundary refuses
|
|
245
|
+
# every act-capability action immediately, no matching required.
|
|
246
|
+
# Reads/perception stay open (ACT semantics).
|
|
247
|
+
if capability != "read":
|
|
248
|
+
paused = [d for d in prohibitions
|
|
249
|
+
if GLOBAL_PAUSE_TERM in (d.match_terms or [])]
|
|
250
|
+
if paused:
|
|
251
|
+
d = paused[0]
|
|
252
|
+
action_summary = (action.text or action.target or action.tool_name)[:120]
|
|
253
|
+
logger.warning(
|
|
254
|
+
"DirectiveGuard GLOBAL PAUSE active [%s]: %s action (%r) "
|
|
255
|
+
"refused", d.id, action.kind, "source-bound action" if d.evidence else action_summary,
|
|
256
|
+
)
|
|
257
|
+
self._recent_blocks.append({
|
|
258
|
+
"ts": time.time(),
|
|
259
|
+
"action_kind": action.kind,
|
|
260
|
+
"capability": capability,
|
|
261
|
+
"action_summary": action_summary,
|
|
262
|
+
"directive_ids": [d.id],
|
|
263
|
+
"subjects": [d.subject],
|
|
264
|
+
"directives": [d.raw_text or d.subject],
|
|
265
|
+
})
|
|
266
|
+
return Verdict(allowed=False, violations=[d],
|
|
267
|
+
reason="global_pause_active")
|
|
268
|
+
# Entity-scoped: resolve the action's target/terms to entity IDs so a
|
|
269
|
+
# directive about an entity blocks actions naming it by ANY alias, not
|
|
270
|
+
# just a keyword hit. Keyword matching (below) stays as the fallback.
|
|
271
|
+
action_entities = {action.entity_id} if action.entity_id else set()
|
|
272
|
+
action_entities |= self._resolve_entities(action_term_set)
|
|
273
|
+
violations: List[Directive] = []
|
|
274
|
+
for d in prohibitions:
|
|
275
|
+
# Tiered semantics: an ACT-level boundary binds actions only;
|
|
276
|
+
# reads/perception stay open. Only an OBSERVE blackout binds reads.
|
|
277
|
+
if capability == "read" and (d.level or Level.ACT) != Level.OBSERVE:
|
|
278
|
+
continue
|
|
279
|
+
# scope by action kind if the directive restricts kinds
|
|
280
|
+
if d.action_kinds and action.kind not in d.action_kinds:
|
|
281
|
+
continue
|
|
282
|
+
directive_entities = set(d.entity_ids) | self._resolve_entities(set(d.match_terms))
|
|
283
|
+
if action_entities and directive_entities & action_entities:
|
|
284
|
+
violations.append(d)
|
|
285
|
+
continue
|
|
286
|
+
if _terms_match(d.match_terms, action_terms):
|
|
287
|
+
violations.append(d)
|
|
288
|
+
|
|
289
|
+
if violations:
|
|
290
|
+
subjects = "; ".join(v.id if v.evidence else v.subject for v in violations)
|
|
291
|
+
ids = ",".join(v.id for v in violations)
|
|
292
|
+
action_summary = (action.text or action.target or action.tool_name)[:120]
|
|
293
|
+
logged_summary = "source-bound action" if any(v.evidence for v in violations) else action_summary
|
|
294
|
+
logged_subjects = subjects
|
|
295
|
+
if capability == "read":
|
|
296
|
+
# OBSERVE blackout on a read: logged so introspection about the
|
|
297
|
+
# blindspot's existence still works ("not looking, per directive").
|
|
298
|
+
logger.info(
|
|
299
|
+
"DirectiveGuard: not looking, per directive [%s] -- %s read "
|
|
300
|
+
"of %r withheld (%s)",
|
|
301
|
+
ids, action.kind, logged_summary, logged_subjects,
|
|
302
|
+
)
|
|
303
|
+
else:
|
|
304
|
+
logger.warning(
|
|
305
|
+
"DirectiveGuard BLOCKED %s action (%r): violates boundary(ies) "
|
|
306
|
+
"[%s]: %s",
|
|
307
|
+
action.kind, logged_summary, ids, logged_subjects,
|
|
308
|
+
)
|
|
309
|
+
self._recent_blocks.append({
|
|
310
|
+
"ts": time.time(),
|
|
311
|
+
"action_kind": action.kind,
|
|
312
|
+
"capability": capability,
|
|
313
|
+
"action_summary": action_summary,
|
|
314
|
+
"directive_ids": [v.id for v in violations],
|
|
315
|
+
"subjects": [v.subject for v in violations],
|
|
316
|
+
"directives": [v.raw_text or v.subject for v in violations],
|
|
317
|
+
})
|
|
318
|
+
return Verdict(allowed=False, violations=violations,
|
|
319
|
+
reason=f"boundary_violation: {subjects}")
|
|
320
|
+
return Verdict(allowed=True, reason="ok")
|
|
321
|
+
|
|
322
|
+
def recent_blocks(self, limit: int = 20) -> List[Dict[str, Any]]:
|
|
323
|
+
"""Most-recent refusals (for the 'why didn't you do X' introspection)."""
|
|
324
|
+
# A forgotten/corrected source must not reappear through this cache.
|
|
325
|
+
items = [item for item in self._recent_blocks
|
|
326
|
+
if self._store is not None and all(self._store.get(identifier)
|
|
327
|
+
for identifier in item["directive_ids"])][-limit:]
|
|
328
|
+
items.reverse()
|
|
329
|
+
return items
|
|
330
|
+
|
|
331
|
+
def obligations(self) -> List[Directive]:
|
|
332
|
+
"""Active REQUIRE directives (standing obligations)."""
|
|
333
|
+
if self._store is None:
|
|
334
|
+
return []
|
|
335
|
+
try:
|
|
336
|
+
return self._store.active(polarity=Polarity.REQUIRE)
|
|
337
|
+
except Exception:
|
|
338
|
+
return []
|
|
339
|
+
|
|
340
|
+
def context_brief(self, limit: int = 20) -> str:
|
|
341
|
+
"""Render active boundaries/obligations for injection into reasoning."""
|
|
342
|
+
if self._store is None:
|
|
343
|
+
return ""
|
|
344
|
+
try:
|
|
345
|
+
active = self._store.active()
|
|
346
|
+
except Exception:
|
|
347
|
+
return ""
|
|
348
|
+
if not active:
|
|
349
|
+
return ""
|
|
350
|
+
lines: List[str] = []
|
|
351
|
+
prohibits = [d for d in active if d.polarity == Polarity.PROHIBIT][:limit]
|
|
352
|
+
requires = [d for d in active if d.polarity == Polarity.REQUIRE][:limit]
|
|
353
|
+
if prohibits:
|
|
354
|
+
lines.append("MUST NOT (standing boundaries from the owner):")
|
|
355
|
+
# Several extracted clauses can reference the same complete owner
|
|
356
|
+
# message. Keep every selected quotation, but render it only once
|
|
357
|
+
# per polarity; action enforcement still consults every directive.
|
|
358
|
+
for text in dict.fromkeys(d.raw_text or d.subject for d in prohibits):
|
|
359
|
+
lines.append(f" - {text}")
|
|
360
|
+
if requires:
|
|
361
|
+
lines.append("MUST (standing obligations from the owner):")
|
|
362
|
+
for text in dict.fromkeys(d.raw_text or d.subject for d in requires):
|
|
363
|
+
lines.append(f" - {text}")
|
|
364
|
+
return "\n".join(lines)
|