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,206 @@
|
|
|
1
|
+
"""Directive / boundary data model.
|
|
2
|
+
|
|
3
|
+
A Directive is a durable standing instruction from the owner, captured from
|
|
4
|
+
conversation or set explicitly. It has a polarity:
|
|
5
|
+
|
|
6
|
+
* PROHIBIT -- "don't / avoid / ignore / stop / leave alone X". A binding
|
|
7
|
+
boundary: autonomous actions that match it are REFUSED.
|
|
8
|
+
* REQUIRE -- "always / make sure to / from now on do X". A standing
|
|
9
|
+
obligation surfaced to the reasoner and the initiative layer.
|
|
10
|
+
* PREFER -- a soft preference (context only, never blocks).
|
|
11
|
+
|
|
12
|
+
Directives are deployment-agnostic: the subject text and match terms come
|
|
13
|
+
from the owner's own words / config, never hardcoded here.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import re
|
|
19
|
+
import json
|
|
20
|
+
import time
|
|
21
|
+
import uuid
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
from enum import Enum
|
|
24
|
+
from typing import Any, Dict, List, Optional
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Polarity(str, Enum):
|
|
28
|
+
PROHIBIT = "prohibit"
|
|
29
|
+
REQUIRE = "require"
|
|
30
|
+
PREFER = "prefer"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Marker term for the one-command global pause (Amendment 1.5). A PROHIBIT
|
|
34
|
+
# directive carrying this term refuses EVERY act-capability action while
|
|
35
|
+
# active. It never matches by keyword (leading underscores keep it out of
|
|
36
|
+
# normalize_terms output), so only the explicit kill switch can set it.
|
|
37
|
+
GLOBAL_PAUSE_TERM = "__all_autonomy__"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Level(str, Enum):
|
|
41
|
+
"""How deep a PROHIBIT boundary cuts.
|
|
42
|
+
|
|
43
|
+
Boundaries bind ACTION, not judgment or perception, except when the
|
|
44
|
+
boundary is explicitly about perception (privacy). Ignorance-based safety
|
|
45
|
+
is brittle: perception and learning stay maximally broad while action is
|
|
46
|
+
gated.
|
|
47
|
+
|
|
48
|
+
ACT (default): blocks mutations, delegated tasks, and autonomous outbound
|
|
49
|
+
actions about the subject. Reads, awareness, reflection, and answering
|
|
50
|
+
the owner's direct questions remain OPEN.
|
|
51
|
+
OBSERVE: full blackout -- blocks reads/perception too. Chosen only when
|
|
52
|
+
the owner uses explicit perception language ("don't look at / don't
|
|
53
|
+
read / stay out of / don't monitor").
|
|
54
|
+
"""
|
|
55
|
+
ACT = "act"
|
|
56
|
+
OBSERVE = "observe"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def default_level() -> "Level":
|
|
60
|
+
"""Config knob: what an unqualified prohibition means (default act)."""
|
|
61
|
+
import os
|
|
62
|
+
raw = os.environ.get("COLONY_BOUNDARY_DEFAULT_LEVEL", "act").strip().lower()
|
|
63
|
+
try:
|
|
64
|
+
return Level(raw)
|
|
65
|
+
except ValueError:
|
|
66
|
+
return Level.ACT
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# Explicit perception language -> OBSERVE (full blackout).
|
|
70
|
+
PERCEPTION_RE = re.compile(
|
|
71
|
+
r"\b(?:look(?:ing)?\s+at|read(?:ing)?(?!-only\b)|watch(?:ing)?|monitor(?:ing)?|"
|
|
72
|
+
r"track(?:ing)?|stay\s+out\s+of|snoop|peek|observe|observing|"
|
|
73
|
+
r"even\s+look)\b", re.IGNORECASE,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def level_from_text(text: Optional[str]) -> "Level":
|
|
78
|
+
"""Derive the boundary level from the owner's phrasing."""
|
|
79
|
+
if text and PERCEPTION_RE.search(str(text)):
|
|
80
|
+
return Level.OBSERVE
|
|
81
|
+
return default_level()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class DirectiveStatus(str, Enum):
|
|
85
|
+
ACTIVE = "active"
|
|
86
|
+
REVOKED = "revoked"
|
|
87
|
+
EXPIRED = "expired"
|
|
88
|
+
SUPERSEDED = "superseded"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# Generic tokens that carry no discriminating meaning for subject matching.
|
|
92
|
+
_STOPWORDS = frozenset({
|
|
93
|
+
"the", "a", "an", "to", "of", "on", "in", "for", "with", "about", "at",
|
|
94
|
+
"my", "your", "me", "you", "it", "that", "this", "any", "some", "and",
|
|
95
|
+
"or", "please", "just", "really", "anymore", "again", "do", "not", "dont",
|
|
96
|
+
"don", "ever", "stop", "avoid", "ignore", "never", "leave", "alone",
|
|
97
|
+
"touch", "is", "are", "be", "when", "if", "should", "would", "can",
|
|
98
|
+
# generic filler verbs / quantifiers that carry no subject meaning
|
|
99
|
+
"track", "tracking", "anything", "everything", "something", "stuff",
|
|
100
|
+
"thing", "things", "worry", "worrying", "bother", "bothering", "mention",
|
|
101
|
+
"mentioning", "care", "dealing", "deal", "regarding", "worried",
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
_WORD = re.compile(r"[a-z0-9][a-z0-9_+.\-/]*")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def normalize_terms(text: Optional[str]) -> List[str]:
|
|
108
|
+
"""Lowercase significant tokens from a subject/target string."""
|
|
109
|
+
if not text:
|
|
110
|
+
return []
|
|
111
|
+
toks = _WORD.findall(str(text).lower())
|
|
112
|
+
return [t for t in toks if t not in _STOPWORDS and len(t) > 1]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@dataclass
|
|
116
|
+
class Directive:
|
|
117
|
+
"""A standing owner directive / boundary."""
|
|
118
|
+
|
|
119
|
+
subject: str # human-readable subject ("repo colony-web")
|
|
120
|
+
polarity: Polarity = Polarity.PROHIBIT
|
|
121
|
+
raw_text: str = "" # the owner's original words
|
|
122
|
+
match_terms: List[str] = field(default_factory=list) # normalized subject tokens
|
|
123
|
+
entity_ids: List[str] = field(default_factory=list) # optional graph entity ids
|
|
124
|
+
action_kinds: List[str] = field(default_factory=list) # limit to kinds; empty = all
|
|
125
|
+
source: str = "owner_explicit" # owner_explicit | inferred | config
|
|
126
|
+
confidence: float = 0.9
|
|
127
|
+
status: DirectiveStatus = DirectiveStatus.ACTIVE
|
|
128
|
+
level: Optional[Level] = None # act | observe (PROHIBIT only; None -> derive)
|
|
129
|
+
id: str = field(default_factory=lambda: f"dir-{uuid.uuid4().hex[:12]}")
|
|
130
|
+
created_at: float = field(default_factory=time.time)
|
|
131
|
+
updated_at: float = field(default_factory=time.time)
|
|
132
|
+
expires_at: Optional[float] = None
|
|
133
|
+
evidence: Optional[Dict[str, Any]] = None
|
|
134
|
+
|
|
135
|
+
def __post_init__(self) -> None:
|
|
136
|
+
if isinstance(self.polarity, str):
|
|
137
|
+
self.polarity = Polarity(self.polarity)
|
|
138
|
+
if isinstance(self.status, str):
|
|
139
|
+
self.status = DirectiveStatus(self.status)
|
|
140
|
+
if isinstance(self.level, str):
|
|
141
|
+
self.level = Level(self.level)
|
|
142
|
+
if self.level is None:
|
|
143
|
+
# Conservative migration/default: derive from phrasing; only
|
|
144
|
+
# perception-explicit wording escalates to a full blackout.
|
|
145
|
+
self.level = level_from_text(self.raw_text or self.subject)
|
|
146
|
+
if not self.match_terms:
|
|
147
|
+
self.match_terms = normalize_terms(self.subject)
|
|
148
|
+
|
|
149
|
+
def is_active(self, now: Optional[float] = None) -> bool:
|
|
150
|
+
if self.status != DirectiveStatus.ACTIVE:
|
|
151
|
+
return False
|
|
152
|
+
if self.expires_at is not None and (now or time.time()) > self.expires_at:
|
|
153
|
+
return False
|
|
154
|
+
return True
|
|
155
|
+
|
|
156
|
+
def to_row(self) -> Dict[str, Any]:
|
|
157
|
+
row = {
|
|
158
|
+
"id": self.id,
|
|
159
|
+
"subject": self.subject,
|
|
160
|
+
"polarity": self.polarity.value,
|
|
161
|
+
"raw_text": self.raw_text,
|
|
162
|
+
"match_terms": " ".join(self.match_terms),
|
|
163
|
+
"entity_ids": " ".join(self.entity_ids),
|
|
164
|
+
"action_kinds": " ".join(self.action_kinds),
|
|
165
|
+
"source": self.source,
|
|
166
|
+
"confidence": self.confidence,
|
|
167
|
+
"status": self.status.value,
|
|
168
|
+
"level": self.level.value if self.level else "act",
|
|
169
|
+
"created_at": self.created_at,
|
|
170
|
+
"updated_at": self.updated_at,
|
|
171
|
+
"expires_at": self.expires_at,
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if self.evidence is not None:
|
|
175
|
+
# Existing metadata column, no second prose copy or schema. Older
|
|
176
|
+
# readers see an empty subject rather than resurrecting erased text.
|
|
177
|
+
row.update(subject='', raw_text='', match_terms='',
|
|
178
|
+
source='canonical-directive-v1:'+json.dumps({**self.evidence, 'method': self.source}, sort_keys=True, separators=(',', ':')))
|
|
179
|
+
return row
|
|
180
|
+
|
|
181
|
+
@classmethod
|
|
182
|
+
def from_row(cls, row: Dict[str, Any]) -> "Directive":
|
|
183
|
+
def _split(v: Any) -> List[str]:
|
|
184
|
+
return [t for t in str(v or "").split(" ") if t]
|
|
185
|
+
source = row.get('source', 'owner_explicit') or 'owner_explicit'
|
|
186
|
+
evidence = None
|
|
187
|
+
if source.startswith('canonical-directive-v1:'):
|
|
188
|
+
evidence = json.loads(source.removeprefix('canonical-directive-v1:'))
|
|
189
|
+
source = evidence.get('method', 'owner_explicit')
|
|
190
|
+
return cls(
|
|
191
|
+
evidence=evidence,
|
|
192
|
+
id=row["id"],
|
|
193
|
+
subject=row["subject"],
|
|
194
|
+
polarity=Polarity(row["polarity"]),
|
|
195
|
+
raw_text=row.get("raw_text", "") or "",
|
|
196
|
+
match_terms=_split(row.get("match_terms")),
|
|
197
|
+
entity_ids=_split(row.get("entity_ids")),
|
|
198
|
+
action_kinds=_split(row.get("action_kinds")),
|
|
199
|
+
source=source,
|
|
200
|
+
confidence=float(row.get("confidence", 0.9) or 0.9),
|
|
201
|
+
status=DirectiveStatus(row.get("status", "active")),
|
|
202
|
+
level=(Level(row["level"]) if row.get("level") else None),
|
|
203
|
+
created_at=float(row.get("created_at") or time.time()),
|
|
204
|
+
updated_at=float(row.get("updated_at") or time.time()),
|
|
205
|
+
expires_at=(float(row["expires_at"]) if row.get("expires_at") not in (None, "") else None),
|
|
206
|
+
)
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
"""DirectiveManager -- capture, store, revoke, and enforce owner boundaries.
|
|
2
|
+
|
|
3
|
+
The single entry point the rest of Colony uses: the turn pipeline calls
|
|
4
|
+
``capture_from_message`` on owner messages; the executor / delivery /
|
|
5
|
+
generation chokepoints call ``guard.check(action)``; the context assembler
|
|
6
|
+
calls ``guard.context_brief()``.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
import re
|
|
13
|
+
import time
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from typing import Any, List, Optional
|
|
16
|
+
|
|
17
|
+
from apsimo.directives.extractor import extract_directives, is_revocation
|
|
18
|
+
from apsimo.directives.guard import DirectiveGuard, Action, Verdict
|
|
19
|
+
from apsimo.directives.models import (
|
|
20
|
+
Directive, Level, Polarity, normalize_terms,
|
|
21
|
+
)
|
|
22
|
+
from apsimo.directives.store import DirectiveStore
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
# Affirmations that confirm a pending boundary lift. Deliberately short/explicit.
|
|
27
|
+
_AFFIRM = re.compile(
|
|
28
|
+
r"^\s*(?:yes|yep|yeah|yup|confirm(?:ed)?|do it|go ahead|correct|affirmative|"
|
|
29
|
+
r"sure|ok(?:ay)?|please do|resume it|lift it)\b", re.IGNORECASE,
|
|
30
|
+
)
|
|
31
|
+
# How long a pending confirmation stays valid (owner must confirm promptly).
|
|
32
|
+
_PENDING_TTL_SECS = 900.0
|
|
33
|
+
|
|
34
|
+
# Subjects that start with an unresolved anaphor or a dangling conjunction are
|
|
35
|
+
# sentence FRAGMENTS ("that and wipe it from colony", "or manage this job",
|
|
36
|
+
# "attempt them"): the real referent was lost at extraction, so storing them
|
|
37
|
+
# creates boundaries that fuzzy-match everything and nothing the owner meant.
|
|
38
|
+
# Found live 2026-07-05: a store poisoned with these plus test canaries blocked
|
|
39
|
+
# every routine job once the worker governor went live.
|
|
40
|
+
_ANAPHORA_LEAD = re.compile(
|
|
41
|
+
r"^(?:that|this|it|them|those|these|him|her|or|and|but|so|then|there|"
|
|
42
|
+
r"y|x)\b", re.IGNORECASE,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class CaptureResult:
|
|
48
|
+
captured: List[Directive] = field(default_factory=list)
|
|
49
|
+
revoked: List[Directive] = field(default_factory=list)
|
|
50
|
+
ack: Optional[str] = None # confirmation echo for the owner
|
|
51
|
+
needs_confirmation: Optional[str] = None # a lift awaiting explicit confirmation
|
|
52
|
+
|
|
53
|
+
def any(self) -> bool:
|
|
54
|
+
return bool(self.captured or self.revoked or self.ack or self.needs_confirmation)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class DirectiveManager:
|
|
58
|
+
def __init__(self, store: DirectiveStore) -> None:
|
|
59
|
+
self.store = store
|
|
60
|
+
self.guard = DirectiveGuard(store)
|
|
61
|
+
# Pending boundary lift awaiting explicit owner confirmation (1c).
|
|
62
|
+
self._pending_lift: Optional[dict] = None
|
|
63
|
+
# One-shot acknowledgment to echo back to the owner (1a).
|
|
64
|
+
self._last_ack: Optional[str] = None
|
|
65
|
+
self._ack_ids: List[str] = []
|
|
66
|
+
|
|
67
|
+
# -- capture -------------------------------------------------------
|
|
68
|
+
def capture_from_message(self, message: str, *, source: str = "owner_explicit",
|
|
69
|
+
source_id: str = "", contact_id: str = "") -> CaptureResult:
|
|
70
|
+
"""Extract directives from an OWNER message and persist them.
|
|
71
|
+
|
|
72
|
+
Asymmetric friction (1c): SETTING a boundary is one-turn easy; LIFTING
|
|
73
|
+
one is staged as a pending confirmation and only applied when the owner
|
|
74
|
+
explicitly confirms on a subsequent message. Caller MUST gate to owner.
|
|
75
|
+
"""
|
|
76
|
+
result = CaptureResult()
|
|
77
|
+
text = (message or "").strip()
|
|
78
|
+
|
|
79
|
+
# 0) Machine-origin text never sets or lifts a boundary: skill/system/
|
|
80
|
+
# interrupted-turn residue that reaches this path with the owner's
|
|
81
|
+
# contact id (cron review prompts, tool echoes) must not become
|
|
82
|
+
# standing directives. (Live incident 2026-07-05: the executor's own
|
|
83
|
+
# refusal text "Do not attempt them" was re-captured as a boundary.)
|
|
84
|
+
try:
|
|
85
|
+
from apsimo.delivery.reachout_policy import is_system_origin
|
|
86
|
+
if is_system_origin(text):
|
|
87
|
+
return result
|
|
88
|
+
except ImportError:
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
# 1) If a lift is pending, ONLY an explicit affirmation on the immediate
|
|
92
|
+
# next message confirms it; anything else clears it (fail-safe: a
|
|
93
|
+
# prohibition is never lifted by an attribution error or stray text).
|
|
94
|
+
if self.store.ledger is not None and not source_id:
|
|
95
|
+
return result
|
|
96
|
+
if self._pending_lift is not None and not self._pending_lift_current():
|
|
97
|
+
self._pending_lift = None
|
|
98
|
+
if self._pending_lift is not None:
|
|
99
|
+
expired = time.time() - self._pending_lift["ts"] > _PENDING_TTL_SECS
|
|
100
|
+
if not expired and _AFFIRM.match(text):
|
|
101
|
+
if self.store.ledger is not None:
|
|
102
|
+
from .evidence import bind
|
|
103
|
+
if bind(self.store.ledger, source_id=source_id, contact_id=contact_id,
|
|
104
|
+
message=text, clause=text) is None:
|
|
105
|
+
return result
|
|
106
|
+
revoked = self._apply_revocation_ids(self._pending_lift["ids"])
|
|
107
|
+
subj = self._pending_lift["subject"]
|
|
108
|
+
self._pending_lift = None
|
|
109
|
+
result.revoked = revoked
|
|
110
|
+
result.ack = f"Confirmed. I will resume {subj}."
|
|
111
|
+
self._last_ack = result.ack
|
|
112
|
+
self._ack_ids = [d.id for d in revoked]
|
|
113
|
+
return result
|
|
114
|
+
# not confirmed -> the boundary stays; drop the pending lift
|
|
115
|
+
self._pending_lift = None
|
|
116
|
+
|
|
117
|
+
found = extract_directives(text, source=source)
|
|
118
|
+
|
|
119
|
+
# 2) A revocation stages a pending confirmation (never lifts immediately).
|
|
120
|
+
revocations = [d for d in found if is_revocation(d)]
|
|
121
|
+
if revocations:
|
|
122
|
+
rev = revocations[0]
|
|
123
|
+
terms = set(rev.match_terms or normalize_terms(rev.subject))
|
|
124
|
+
matches = [d for d in self.store.active(polarity=Polarity.PROHIBIT)
|
|
125
|
+
if set(d.match_terms) & terms]
|
|
126
|
+
if matches:
|
|
127
|
+
evidence = None
|
|
128
|
+
if self.store.ledger is not None:
|
|
129
|
+
from .evidence import bind
|
|
130
|
+
evidence = bind(self.store.ledger, source_id=source_id, contact_id=contact_id,
|
|
131
|
+
message=text, clause=text)
|
|
132
|
+
if evidence is None:
|
|
133
|
+
return result
|
|
134
|
+
self._pending_lift = {
|
|
135
|
+
"subject": rev.subject, "ids": [d.id for d in matches],
|
|
136
|
+
"ts": time.time(), "evidence": evidence,
|
|
137
|
+
}
|
|
138
|
+
subs = "; ".join(d.raw_text or d.subject for d in matches)
|
|
139
|
+
result.needs_confirmation = (
|
|
140
|
+
f"You asked me to resume: {rev.subject}. That lifts a boundary "
|
|
141
|
+
f"you set ({subs}). Confirm and I will resume it; otherwise it stays in place."
|
|
142
|
+
)
|
|
143
|
+
return result
|
|
144
|
+
|
|
145
|
+
# 3) New prohibitions / requirements are stored immediately -- after
|
|
146
|
+
# the quality gates: fragments are refused and duplicates never pile up.
|
|
147
|
+
for d in found:
|
|
148
|
+
if not self._subject_acceptable(d):
|
|
149
|
+
logger.info("Directive capture skipped: degenerate subject")
|
|
150
|
+
continue
|
|
151
|
+
if self._duplicate_active(d):
|
|
152
|
+
logger.debug("Directive capture skipped: duplicate active rule")
|
|
153
|
+
continue
|
|
154
|
+
if source_id:
|
|
155
|
+
from .evidence import bind
|
|
156
|
+
d.evidence = bind(self.store.ledger, source_id=source_id, contact_id=contact_id,
|
|
157
|
+
message=text, clause=d.raw_text)
|
|
158
|
+
if d.evidence is None:
|
|
159
|
+
continue
|
|
160
|
+
self.store.add(d)
|
|
161
|
+
result.captured.append(d)
|
|
162
|
+
if result.captured:
|
|
163
|
+
parts = []
|
|
164
|
+
for d in result.captured:
|
|
165
|
+
# The echo STATES the interpretation (tiered semantics) so the
|
|
166
|
+
# owner can correct it in one turn.
|
|
167
|
+
from apsimo.directives.models import GLOBAL_PAUSE_TERM
|
|
168
|
+
if GLOBAL_PAUSE_TERM in (d.match_terms or []):
|
|
169
|
+
parts.append(
|
|
170
|
+
"GLOBAL PAUSE engaged: I will take no autonomous "
|
|
171
|
+
"actions until you tell me to resume (say 'resume "
|
|
172
|
+
"autonomy')")
|
|
173
|
+
elif d.polarity != Polarity.PROHIBIT:
|
|
174
|
+
parts.append(f"I will make sure to {d.subject}")
|
|
175
|
+
elif d.level == Level.OBSERVE:
|
|
176
|
+
parts.append(
|
|
177
|
+
f"full blackout on {d.subject}: I will not act on it "
|
|
178
|
+
"or look at it")
|
|
179
|
+
else:
|
|
180
|
+
parts.append(
|
|
181
|
+
f"I will stop acting on {d.subject}. I will still keep "
|
|
182
|
+
"awareness of it; say 'don't even look at it' for a "
|
|
183
|
+
"full blackout")
|
|
184
|
+
result.ack = "Noted: " + "; ".join(parts) + "."
|
|
185
|
+
self._last_ack = result.ack
|
|
186
|
+
self._ack_ids = [d.id for d in result.captured]
|
|
187
|
+
return result
|
|
188
|
+
|
|
189
|
+
# -- capture quality gates ------------------------------------------
|
|
190
|
+
_PRONOUNS = frozenset({"it", "that", "this", "them", "those", "these",
|
|
191
|
+
"him", "her", "they", "he", "she"})
|
|
192
|
+
|
|
193
|
+
def _subject_acceptable(self, d: Directive) -> bool:
|
|
194
|
+
"""A storable subject names something concrete: no unresolved
|
|
195
|
+
anaphora fragments, and at least one substantive term survives
|
|
196
|
+
normalization."""
|
|
197
|
+
subj = (d.subject or "").strip()
|
|
198
|
+
if not subj:
|
|
199
|
+
return False
|
|
200
|
+
if _ANAPHORA_LEAD.match(subj):
|
|
201
|
+
return False
|
|
202
|
+
raw_tokens = re.findall(r"[a-z]+", subj.lower())
|
|
203
|
+
terms = d.match_terms or normalize_terms(subj)
|
|
204
|
+
substantive = [t for t in terms
|
|
205
|
+
if len(t) >= 4 and t not in self._PRONOUNS]
|
|
206
|
+
if not substantive:
|
|
207
|
+
return False
|
|
208
|
+
# A bare pronoun object with a thin subject ("attempt them",
|
|
209
|
+
# "resend it") is an unresolved reference: the thing the owner meant
|
|
210
|
+
# never made it into the subject.
|
|
211
|
+
if any(t in self._PRONOUNS for t in raw_tokens) and len(substantive) < 2:
|
|
212
|
+
return False
|
|
213
|
+
return True
|
|
214
|
+
|
|
215
|
+
def _duplicate_active(self, d: Directive) -> bool:
|
|
216
|
+
"""An active directive with the same polarity and identical
|
|
217
|
+
normalized terms already covers this capture."""
|
|
218
|
+
key = set(d.match_terms or normalize_terms(d.subject))
|
|
219
|
+
if not key:
|
|
220
|
+
return False
|
|
221
|
+
try:
|
|
222
|
+
for e in self.store.active(polarity=d.polarity):
|
|
223
|
+
if set(e.match_terms or []) == key:
|
|
224
|
+
return True
|
|
225
|
+
except Exception:
|
|
226
|
+
pass
|
|
227
|
+
return False
|
|
228
|
+
|
|
229
|
+
def consume_ack(self) -> Optional[str]:
|
|
230
|
+
"""Return and clear the one-shot acknowledgment (echoed once)."""
|
|
231
|
+
ack, self._last_ack = self._last_ack, None
|
|
232
|
+
identifiers, self._ack_ids = self._ack_ids, []
|
|
233
|
+
if any(self.store.get(identifier) is None for identifier in identifiers):
|
|
234
|
+
return None
|
|
235
|
+
return ack
|
|
236
|
+
|
|
237
|
+
def _pending_lift_current(self) -> bool:
|
|
238
|
+
pending = self._pending_lift
|
|
239
|
+
if (pending is None or time.time() - pending["ts"] > _PENDING_TTL_SECS
|
|
240
|
+
or not any(self.store.get(identifier) for identifier in pending["ids"])):
|
|
241
|
+
return False
|
|
242
|
+
if self.store.ledger is not None:
|
|
243
|
+
from .evidence import _message
|
|
244
|
+
return bool(pending.get("evidence") and _message(self.store.ledger, pending["evidence"]) is not None)
|
|
245
|
+
return True
|
|
246
|
+
|
|
247
|
+
def pending_confirmation(self) -> Optional[str]:
|
|
248
|
+
"""The current pending boundary-lift prompt, if any (for context echo)."""
|
|
249
|
+
if self._pending_lift is None:
|
|
250
|
+
return None
|
|
251
|
+
if not self._pending_lift_current():
|
|
252
|
+
self._pending_lift = None
|
|
253
|
+
return None
|
|
254
|
+
subj = self._pending_lift["subject"]
|
|
255
|
+
return f"AWAITING CONFIRMATION: the owner asked to resume {subj}; do not resume until they confirm."
|
|
256
|
+
|
|
257
|
+
def _apply_revocation_ids(self, ids: List[str]) -> List[Directive]:
|
|
258
|
+
revoked = []
|
|
259
|
+
for did in ids:
|
|
260
|
+
d = self.store.get(did)
|
|
261
|
+
if d and self.store.revoke(did):
|
|
262
|
+
revoked.append(d)
|
|
263
|
+
logger.info("Directive revoked by owner (confirmed): id=%s", did)
|
|
264
|
+
return revoked
|
|
265
|
+
|
|
266
|
+
def add_explicit(self, subject: str, polarity: str = "prohibit",
|
|
267
|
+
raw_text: str = "", source: str = "owner_explicit",
|
|
268
|
+
entity_ids: Optional[List[str]] = None) -> Directive:
|
|
269
|
+
d = Directive(
|
|
270
|
+
subject=subject, polarity=Polarity(polarity), raw_text=raw_text or subject,
|
|
271
|
+
source=source, entity_ids=entity_ids or [],
|
|
272
|
+
)
|
|
273
|
+
return self.store.add(d)
|
|
274
|
+
|
|
275
|
+
# -- boundary-respecting critical flag (high-severity path) ---------
|
|
276
|
+
def set_delivery_router(self, router: Any) -> None:
|
|
277
|
+
"""Async callable(payload)->bool routing through the guarded reach-out
|
|
278
|
+
path. Used ONLY for the once-per-boundary critical flag."""
|
|
279
|
+
self._delivery_router = router
|
|
280
|
+
|
|
281
|
+
async def flag_critical(self, subject: str, finding: str,
|
|
282
|
+
severity: float = 0.9) -> dict:
|
|
283
|
+
"""Under an ACT boundary, reflection may surface something critical
|
|
284
|
+
(security vulnerability, data loss, financial risk). Correct behavior:
|
|
285
|
+
an internal note plus AT MOST ONE flag surfaced to the owner through
|
|
286
|
+
the guarded reach-out path, clearly marked as boundary-respecting.
|
|
287
|
+
Never silent action; never silent omission of a critical fact.
|
|
288
|
+
"""
|
|
289
|
+
import os
|
|
290
|
+
try:
|
|
291
|
+
threshold = float(os.environ.get("COLONY_BOUNDARY_FLAG_MIN_SEVERITY", "0.85"))
|
|
292
|
+
except (TypeError, ValueError):
|
|
293
|
+
threshold = 0.85
|
|
294
|
+
logger.info("boundary-concern noted (severity=%.2f): %s -- %s",
|
|
295
|
+
severity, subject, finding[:160])
|
|
296
|
+
if severity < threshold:
|
|
297
|
+
return {"flagged": False, "reason": "below_threshold",
|
|
298
|
+
"noted_internally": True}
|
|
299
|
+
|
|
300
|
+
# Which active prohibition covers this subject?
|
|
301
|
+
terms = set(normalize_terms(subject))
|
|
302
|
+
match = None
|
|
303
|
+
for d in self.store.active(polarity=Polarity.PROHIBIT):
|
|
304
|
+
if set(d.match_terms) & terms:
|
|
305
|
+
match = d
|
|
306
|
+
break
|
|
307
|
+
if match is None:
|
|
308
|
+
return {"flagged": False, "reason": "no_matching_boundary",
|
|
309
|
+
"noted_internally": True}
|
|
310
|
+
if self.store.has_flag(match.id):
|
|
311
|
+
return {"flagged": False, "reason": "already_flagged_once",
|
|
312
|
+
"noted_internally": True}
|
|
313
|
+
if not self.store.record_flag(match.id, finding, severity):
|
|
314
|
+
return {"flagged": False, "reason": "already_flagged_once",
|
|
315
|
+
"noted_internally": True}
|
|
316
|
+
|
|
317
|
+
delivered = False
|
|
318
|
+
router = getattr(self, "_delivery_router", None)
|
|
319
|
+
if router is not None:
|
|
320
|
+
try:
|
|
321
|
+
from apsimo.proposals import Proposal, proposal_to_payload
|
|
322
|
+
prop = Proposal(
|
|
323
|
+
title=f"Boundary-respecting flag: {subject[:60]}",
|
|
324
|
+
finding=(f"You asked me to leave {match.subject} alone, and I have. "
|
|
325
|
+
f"But you should know: {finding}"),
|
|
326
|
+
why_it_helps="a critical fact should never be silently omitted",
|
|
327
|
+
suggested_action="Tell me whether to keep hands off or investigate.",
|
|
328
|
+
source=f"boundary-flag:{match.id}",
|
|
329
|
+
initiative_type="proposal",
|
|
330
|
+
confidence=min(1.0, max(0.0, severity)),
|
|
331
|
+
)
|
|
332
|
+
delivered = bool(await router(proposal_to_payload(prop)))
|
|
333
|
+
except Exception:
|
|
334
|
+
logger.debug("boundary flag delivery failed", exc_info=True)
|
|
335
|
+
logger.warning(
|
|
336
|
+
"BOUNDARY-RESPECTING CRITICAL FLAG surfaced once for [%s] %r: %s",
|
|
337
|
+
match.id, match.subject, finding[:200],
|
|
338
|
+
)
|
|
339
|
+
return {"flagged": True, "directive_id": match.id, "delivered": delivered}
|
|
340
|
+
|
|
341
|
+
# -- entity-scoped matching (2) ------------------------------------
|
|
342
|
+
def set_entity_index(self, entities: dict) -> None:
|
|
343
|
+
"""Provide {entity_id: [names/aliases]} so boundaries resolve by entity."""
|
|
344
|
+
self.guard.set_entity_index(entities)
|
|
345
|
+
|
|
346
|
+
async def refresh_entity_index(self, world_store: Any, limit: int = 500) -> int:
|
|
347
|
+
"""Load entities + aliases from the world model into the guard index."""
|
|
348
|
+
if world_store is None:
|
|
349
|
+
return 0
|
|
350
|
+
try:
|
|
351
|
+
ents = await world_store.find_entities(query="", limit=limit)
|
|
352
|
+
except Exception:
|
|
353
|
+
return 0
|
|
354
|
+
mapping = {}
|
|
355
|
+
for e in ents or []:
|
|
356
|
+
eid = getattr(e, "id", None) or (e.get("id") if isinstance(e, dict) else None)
|
|
357
|
+
name = getattr(e, "name", None) or (e.get("name") if isinstance(e, dict) else None)
|
|
358
|
+
aliases = getattr(e, "aliases", None) or (e.get("aliases") if isinstance(e, dict) else []) or []
|
|
359
|
+
if eid and name:
|
|
360
|
+
mapping[eid] = [name] + list(aliases)
|
|
361
|
+
self.guard.set_entity_index(mapping)
|
|
362
|
+
return len(mapping)
|
|
363
|
+
|
|
364
|
+
# -- enforce -------------------------------------------------------
|
|
365
|
+
def check(self, action: Action) -> Verdict:
|
|
366
|
+
return self.guard.check(action)
|
|
367
|
+
|
|
368
|
+
def context_brief(self) -> str:
|
|
369
|
+
return self.guard.context_brief()
|
|
370
|
+
|
|
371
|
+
def active(self) -> List[Directive]:
|
|
372
|
+
return self.store.active()
|