apsimo 1.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- apsimo/__init__.py +38 -0
- apsimo/__main__.py +6 -0
- apsimo/agent/__init__.py +6 -0
- apsimo/agent/client.py +276 -0
- apsimo/agent/models.py +46 -0
- apsimo/agents/__init__.py +20 -0
- apsimo/agents/models.py +264 -0
- apsimo/agents/store.py +861 -0
- apsimo/agents/websocket.py +522 -0
- apsimo/api/__init__.py +1 -0
- apsimo/api/auth_telemetry.py +287 -0
- apsimo/api/authority.py +1203 -0
- apsimo/api/contact_grants.py +347 -0
- apsimo/api/middleware.py +483 -0
- apsimo/api/routers/__init__.py +1 -0
- apsimo/api/routers/commitment_work.py +265 -0
- apsimo/api/routers/context_gate.py +123 -0
- apsimo/api/routers/executions.py +140 -0
- apsimo/api/routers/followup_plans.py +147 -0
- apsimo/api/routers/governed_actions.py +162 -0
- apsimo/api/routers/host.py +14473 -0
- apsimo/api/routers/initiative_work.py +115 -0
- apsimo/api/routers/mining.py +104 -0
- apsimo/api/routers/observations.py +110 -0
- apsimo/api/routers/social_state.py +225 -0
- apsimo/api/routers/task_queue.py +2715 -0
- apsimo/api/routers/temporal_followups.py +251 -0
- apsimo/api/routers/transport.py +110 -0
- apsimo/api/routers/transport_ingress_api.py +240 -0
- apsimo/api/schemas/__init__.py +1 -0
- apsimo/api/schemas/host.py +1949 -0
- apsimo/autonomy/cli.py +110 -0
- apsimo/autonomy/condition_worker.py +437 -0
- apsimo/autonomy/config.py +424 -0
- apsimo/autonomy/loop.py +4316 -0
- apsimo/autonomy/registry.py +339 -0
- apsimo/autonomy/scheduler.py +1822 -0
- apsimo/autonomy/synthesis.py +449 -0
- apsimo/backup.py +962 -0
- apsimo/beliefs/__init__.py +23 -0
- apsimo/beliefs/contradictions.py +109 -0
- apsimo/beliefs/decay.py +61 -0
- apsimo/beliefs/engine.py +479 -0
- apsimo/beliefs/models.py +67 -0
- apsimo/beliefs/promotion.py +41 -0
- apsimo/beliefs/resolve.py +58 -0
- apsimo/beliefs/source_claims.py +690 -0
- apsimo/beliefs/source_projection.py +883 -0
- apsimo/beliefs/source_time.py +208 -0
- apsimo/beliefs/store.py +133 -0
- apsimo/briefings/aggregators.py +824 -0
- apsimo/briefings/composer.py +420 -0
- apsimo/briefings/config.py +55 -0
- apsimo/briefings/delivery.py +439 -0
- apsimo/briefings/engagement.py +97 -0
- apsimo/briefings/engine.py +274 -0
- apsimo/briefings/enhancer.py +99 -0
- apsimo/briefings/models.py +183 -0
- apsimo/briefings/scheduler.py +382 -0
- apsimo/briefings/store.py +435 -0
- apsimo/chain/__init__.py +48 -0
- apsimo/chain/block.py +100 -0
- apsimo/chain/cli.py +704 -0
- apsimo/chain/genesis.py +443 -0
- apsimo/chain/identity.py +416 -0
- apsimo/chain/keys.py +1025 -0
- apsimo/chain/local_keys.py +187 -0
- apsimo/chain/manager.py +290 -0
- apsimo/chain/node.py +163 -0
- apsimo/chain/plugin_transactions.py +371 -0
- apsimo/chain/protocol.py +220 -0
- apsimo/chain/state_machine.py +676 -0
- apsimo/chain/storage.py +503 -0
- apsimo/chain/transactions.py +250 -0
- apsimo/chain/validation.py +397 -0
- apsimo/channels/__init__.py +1 -0
- apsimo/channels/manifest.py +31 -0
- apsimo/channels/migrations/001_channels_schema.sql +12 -0
- apsimo/channels/phone_gateways.py +42 -0
- apsimo/channels/presence.py +188 -0
- apsimo/channels/router.py +235 -0
- apsimo/channels/store.py +231 -0
- apsimo/cli.py +2688 -0
- apsimo/cognition/__init__.py +11 -0
- apsimo/cognition/charter.py +398 -0
- apsimo/cognition/drive_governance.py +3530 -0
- apsimo/cognition/evidence_pipeline.py +1627 -0
- apsimo/cognition/external_events.py +932 -0
- apsimo/cognition/goal_spine.py +3488 -0
- apsimo/cognition/introspection.py +214 -0
- apsimo/cognition/prompt.py +150 -0
- apsimo/cognition/runtime.py +108 -0
- apsimo/cognition/trigger.py +154 -0
- apsimo/commitments/__init__.py +18 -0
- apsimo/commitments/local_work.py +355 -0
- apsimo/commitments/store.py +1052 -0
- apsimo/commitments/work.py +91 -0
- apsimo/compat.py +53 -0
- apsimo/compression/__init__.py +467 -0
- apsimo/connectors/__init__.py +21 -0
- apsimo/connectors/base.py +152 -0
- apsimo/connectors/caldav_calendar.py +125 -0
- apsimo/connectors/fs_documents.py +85 -0
- apsimo/connectors/imap_email.py +138 -0
- apsimo/connectors/manager.py +218 -0
- apsimo/connectors/webhook_pull.py +88 -0
- apsimo/contacts/__init__.py +33 -0
- apsimo/contacts/comms.py +357 -0
- apsimo/contacts/config.py +79 -0
- apsimo/contacts/exporters/__init__.py +1 -0
- apsimo/contacts/exporters/vcard.py +71 -0
- apsimo/contacts/identity_links.py +251 -0
- apsimo/contacts/importer.py +280 -0
- apsimo/contacts/importers/__init__.py +1 -0
- apsimo/contacts/importers/batch.py +43 -0
- apsimo/contacts/importers/macos_contacts.py +101 -0
- apsimo/contacts/migrations/001_contacts_schema.sql +141 -0
- apsimo/contacts/migrations/002_trust_scopes.sql +36 -0
- apsimo/contacts/migrations/003_open_gateway_enum.sql +32 -0
- apsimo/contacts/migrations/004_contact_provision_operations.sql +18 -0
- apsimo/contacts/migrations/005_identity_links.sql +27 -0
- apsimo/contacts/models.py +308 -0
- apsimo/contacts/scoring.py +16 -0
- apsimo/contacts/store.py +1623 -0
- apsimo/contacts/transport_ingress.py +252 -0
- apsimo/contacts/world_bridge.py +314 -0
- apsimo/contextgate/__init__.py +69 -0
- apsimo/contextgate/chunker.py +169 -0
- apsimo/contextgate/estimate.py +54 -0
- apsimo/contextgate/gate.py +313 -0
- apsimo/contextgate/retrieve.py +115 -0
- apsimo/delivery/__init__.py +16 -0
- apsimo/delivery/bridge.py +1260 -0
- apsimo/delivery/channels.py +526 -0
- apsimo/delivery/classification.py +50 -0
- apsimo/delivery/rate_limiter.py +268 -0
- apsimo/delivery/reachout_policy.py +206 -0
- apsimo/directed/__init__.py +22 -0
- apsimo/directed/audit.py +167 -0
- apsimo/directed/intake.py +95 -0
- apsimo/directed/models.py +191 -0
- apsimo/directed/service.py +509 -0
- apsimo/directives/__init__.py +25 -0
- apsimo/directives/evidence.py +87 -0
- apsimo/directives/extractor.py +188 -0
- apsimo/directives/guard.py +364 -0
- apsimo/directives/models.py +206 -0
- apsimo/directives/service.py +372 -0
- apsimo/directives/store.py +167 -0
- apsimo/doctor.py +2173 -0
- apsimo/environment.py +43 -0
- apsimo/events/__init__.py +33 -0
- apsimo/events/broadcaster.py +98 -0
- apsimo/events/bus.py +217 -0
- apsimo/events/journal.py +863 -0
- apsimo/events/stream.py +131 -0
- apsimo/events/types.py +150 -0
- apsimo/execution_results.py +357 -0
- apsimo/feedback/__init__.py +5 -0
- apsimo/feedback/store.py +76 -0
- apsimo/feeds/__init__.py +19 -0
- apsimo/feeds/cli.py +84 -0
- apsimo/feeds/engine.py +437 -0
- apsimo/feeds/example-feed.yaml +77 -0
- apsimo/feeds/hermes_cron.py +126 -0
- apsimo/feeds/manager.py +235 -0
- apsimo/feeds/spec.py +250 -0
- apsimo/feeds/template.py +202 -0
- apsimo/gate/__init__.py +18 -0
- apsimo/gate/audit.py +61 -0
- apsimo/gate/communication_policy.py +166 -0
- apsimo/gate/config.py +72 -0
- apsimo/gate/context_provenance.py +170 -0
- apsimo/gate/env_risk.py +226 -0
- apsimo/gate/guard_audit.py +353 -0
- apsimo/gate/layers/__init__.py +1 -0
- apsimo/gate/layers/base.py +15 -0
- apsimo/gate/layers/l1_recipient.py +66 -0
- apsimo/gate/layers/l2_pii.py +134 -0
- apsimo/gate/layers/l3_cross_context.py +50 -0
- apsimo/gate/layers/l4_trust_tier.py +78 -0
- apsimo/gate/layers/l5_injection.py +199 -0
- apsimo/gate/layers/l6_review.py +86 -0
- apsimo/gate/layers/l7_delay.py +100 -0
- apsimo/gate/layers/tom2_epistemic.py +185 -0
- apsimo/gate/models.py +64 -0
- apsimo/gate/pending_dispatch.py +5 -0
- apsimo/gate/pipeline.py +206 -0
- apsimo/gate/rejection.py +259 -0
- apsimo/gate/response_guard.py +700 -0
- apsimo/gate/rulesets/injection_v1.yaml +51 -0
- apsimo/gate/surface_policy.py +189 -0
- apsimo/gate/taint.py +226 -0
- apsimo/genesis.json +9 -0
- apsimo/goals/__init__.py +100 -0
- apsimo/goals/config.py +38 -0
- apsimo/goals/decomposer.py +421 -0
- apsimo/goals/engine.py +617 -0
- apsimo/goals/inference.py +354 -0
- apsimo/goals/models.py +302 -0
- apsimo/goals/priority.py +270 -0
- apsimo/goals/queue_bridge.py +149 -0
- apsimo/goals/replan.py +450 -0
- apsimo/goals/schema.sql +89 -0
- apsimo/goals/store.py +692 -0
- apsimo/governed_actions.py +1708 -0
- apsimo/harness_integration/__init__.py +45 -0
- apsimo/harness_integration/context.py +41 -0
- apsimo/harness_integration/skills.py +231 -0
- apsimo/identity/__init__.py +26 -0
- apsimo/identity/participants.py +181 -0
- apsimo/identity/resolver.py +329 -0
- apsimo/identity_bootstrap/__init__.py +5 -0
- apsimo/identity_bootstrap/builder.py +208 -0
- apsimo/identity_bootstrap/corpus.py +443 -0
- apsimo/identity_bootstrap/models.py +54 -0
- apsimo/identity_bootstrap/runner.py +353 -0
- apsimo/identity_bootstrap/seeders/__init__.py +25 -0
- apsimo/identity_bootstrap/seeders/briefings.py +109 -0
- apsimo/identity_bootstrap/seeders/chain.py +57 -0
- apsimo/identity_bootstrap/seeders/goals.py +128 -0
- apsimo/identity_bootstrap/seeders/memory.py +191 -0
- apsimo/identity_bootstrap/seeders/neo4j_cognition.py +79 -0
- apsimo/identity_bootstrap/seeders/relationship.py +152 -0
- apsimo/identity_bootstrap/seeders/sessions.py +67 -0
- apsimo/identity_bootstrap/seeders/skills.py +92 -0
- apsimo/identity_bootstrap/seeders/task_queue.py +72 -0
- apsimo/identity_bootstrap/seeders/world_model.py +143 -0
- apsimo/identity_bootstrap/self_query.py +92 -0
- apsimo/identity_bootstrap/self_reflection.py +155 -0
- apsimo/identity_bootstrap/skill.py +37 -0
- apsimo/identity_bootstrap/verifier.py +436 -0
- apsimo/initiatives/__init__.py +20 -0
- apsimo/initiatives/action_registry.py +454 -0
- apsimo/initiatives/approval_authority.py +2105 -0
- apsimo/initiatives/approval_policy.py +123 -0
- apsimo/initiatives/assignment.py +263 -0
- apsimo/initiatives/backup_evidence.py +100 -0
- apsimo/initiatives/context_freshness.py +103 -0
- apsimo/initiatives/models.py +318 -0
- apsimo/initiatives/native_work.py +270 -0
- apsimo/initiatives/standing_approvals.py +232 -0
- apsimo/initiatives/store.py +1081 -0
- apsimo/initiatives/temporal_followup.py +410 -0
- apsimo/intelligence/__init__.py +1 -0
- apsimo/intelligence/cognition/__init__.py +24 -0
- apsimo/intelligence/cognition/gap_detector.py +148 -0
- apsimo/intelligence/cognition/metalearner.py +547 -0
- apsimo/intelligence/cognition/metrics_collector.py +217 -0
- apsimo/intelligence/cognition/performance_index.py +299 -0
- apsimo/intelligence/cognition/registry.py +192 -0
- apsimo/intelligence/cognition/strategy_adjuster.py +222 -0
- apsimo/intelligence/cognition/types.py +16 -0
- apsimo/intelligence/components/__init__.py +66 -0
- apsimo/intelligence/components/anomaly_detector.py +413 -0
- apsimo/intelligence/components/initiative_engine.py +2643 -0
- apsimo/intelligence/components/preference_learner.py +521 -0
- apsimo/intelligence/components/research_orchestrator.py +358 -0
- apsimo/intelligence/components/self_directed_thinker.py +221 -0
- apsimo/intelligence/components/self_reflector.py +252 -0
- apsimo/intelligence/components/session_continuity.py +154 -0
- apsimo/intelligence/components/task_planner.py +320 -0
- apsimo/intelligence/components/tool_learner.py +217 -0
- apsimo/intelligence/graph/__init__.py +79 -0
- apsimo/intelligence/graph/client.py +2483 -0
- apsimo/intelligence/graph/consolidator.py +405 -0
- apsimo/intelligence/graph/distiller.py +312 -0
- apsimo/intelligence/graph/migrations.py +129 -0
- apsimo/intelligence/graph/queries.py +248 -0
- apsimo/intelligence/graph/recall.py +281 -0
- apsimo/intelligence/graph/reconciler.py +144 -0
- apsimo/intelligence/graph/schema.py +337 -0
- apsimo/intelligence/graph/selection.py +252 -0
- apsimo/intelligence/learning/__init__.py +17 -0
- apsimo/intelligence/learning/continuous_learner.py +245 -0
- apsimo/intelligence/learning/feedback_store.py +321 -0
- apsimo/intelligence/mind_model/__init__.py +1 -0
- apsimo/intelligence/mind_model/graph_baseline.py +136 -0
- apsimo/intelligence/mind_model/signal_collector.py +361 -0
- apsimo/intelligence/relationships/__init__.py +11 -0
- apsimo/intelligence/relationships/profiler.py +389 -0
- apsimo/intelligence/relationships/scorer.py +560 -0
- apsimo/intelligence/relationships/signal_floor.py +66 -0
- apsimo/intelligence/relationships/trust_tiers.py +300 -0
- apsimo/intelligence/synthesis/__init__.py +40 -0
- apsimo/intelligence/synthesis/connection_discoverer.py +379 -0
- apsimo/intelligence/synthesis/cross_domain_analyzer.py +287 -0
- apsimo/intelligence/synthesis/insight_deliverer.py +171 -0
- apsimo/intelligence/synthesis/insight_store.py +79 -0
- apsimo/intelligence/synthesis/insight_validator.py +183 -0
- apsimo/intelligence/synthesis/novelty_scorer.py +267 -0
- apsimo/intelligence/turn_middleware/__init__.py +15 -0
- apsimo/intelligence/turn_middleware/memory_sync.py +119 -0
- apsimo/mcp/__init__.py +41 -0
- apsimo/mcp/__main__.py +6 -0
- apsimo/mcp/config.py +287 -0
- apsimo/mcp/server.py +501 -0
- apsimo/migrations.py +187 -0
- apsimo/mining/__init__.py +27 -0
- apsimo/mining/corpus.py +239 -0
- apsimo/mining/escalations.py +289 -0
- apsimo/mining/models.py +169 -0
- apsimo/mining/store.py +210 -0
- apsimo/models/__init__.py +30 -0
- apsimo/models/memory.py +80 -0
- apsimo/models/mesh.py +72 -0
- apsimo/models/person.py +104 -0
- apsimo/models/signal.py +108 -0
- apsimo/observations/__init__.py +15 -0
- apsimo/observations/store.py +277 -0
- apsimo/patterns/__init__.py +6 -0
- apsimo/patterns/extract.py +187 -0
- apsimo/patterns/store.py +227 -0
- apsimo/persona/__init__.py +1 -0
- apsimo/persona/engine.py +611 -0
- apsimo/persona/manifest.py +140 -0
- apsimo/projects/__init__.py +28 -0
- apsimo/projects/engine.py +1681 -0
- apsimo/projects/event_outbox.py +188 -0
- apsimo/projects/models.py +216 -0
- apsimo/projects/planner.py +181 -0
- apsimo/projects/store.py +1446 -0
- apsimo/proposals/__init__.py +12 -0
- apsimo/proposals/engine.py +114 -0
- apsimo/proposals/models.py +207 -0
- apsimo/qualification/__init__.py +1 -0
- apsimo/qualification/cases.py +75 -0
- apsimo/qualification/cli.py +51 -0
- apsimo/qualification/memory_cases.py +209 -0
- apsimo/qualification/records.py +92 -0
- apsimo/qualification/report.py +87 -0
- apsimo/qualification/runner.py +311 -0
- apsimo/qualification/structured_cases.py +131 -0
- apsimo/reasoning/__init__.py +13 -0
- apsimo/reasoning/executor.py +506 -0
- apsimo/reasoning/loop.py +373 -0
- apsimo/reasoning/native_tools/__init__.py +16 -0
- apsimo/reasoning/native_tools/calculate.py +141 -0
- apsimo/reasoning/native_tools/file_ops.py +150 -0
- apsimo/reasoning/native_tools/web_search.py +49 -0
- apsimo/reasoning/tool_policy.py +182 -0
- apsimo/redact/__init__.py +176 -0
- apsimo/repos/__init__.py +5 -0
- apsimo/repos/mirrors.py +204 -0
- apsimo/research/__init__.py +41 -0
- apsimo/research/artifact.py +482 -0
- apsimo/research/gatherer.py +387 -0
- apsimo/research/pipeline.py +513 -0
- apsimo/research/search/__init__.py +7 -0
- apsimo/research/search/base.py +41 -0
- apsimo/research/search/brave.py +59 -0
- apsimo/research/search/cache.py +51 -0
- apsimo/research/search/duckduckgo.py +103 -0
- apsimo/research/search/orchestrator.py +119 -0
- apsimo/research/search/serpapi.py +59 -0
- apsimo/research/search/tavily.py +59 -0
- apsimo/research/synthesizer.py +309 -0
- apsimo/router/__init__.py +30 -0
- apsimo/router/complexity_scorer.py +148 -0
- apsimo/router/endpoints.py +153 -0
- apsimo/router/fallback.py +58 -0
- apsimo/router/functions.py +243 -0
- apsimo/router/native_policy.py +52 -0
- apsimo/router/router.py +762 -0
- apsimo/router/self_learning.py +174 -0
- apsimo/router/tiers.py +677 -0
- apsimo/sandbox/__init__.py +21 -0
- apsimo/sandbox/backend.py +195 -0
- apsimo/sandbox/manager.py +173 -0
- apsimo/scope_bounds.py +7 -0
- apsimo/secrets/__init__.py +6 -0
- apsimo/secrets/backends/__init__.py +8 -0
- apsimo/secrets/backends/base.py +42 -0
- apsimo/secrets/backends/env.py +110 -0
- apsimo/secrets/backends/keyring.py +72 -0
- apsimo/secrets/backends/onepassword.py +232 -0
- apsimo/secrets/cli.py +191 -0
- apsimo/secrets/manager.py +160 -0
- apsimo/secrets/migration.py +101 -0
- apsimo/secrets/types.py +98 -0
- apsimo/seed.py +41 -0
- apsimo/self_model/__init__.py +37 -0
- apsimo/self_model/appraisals.py +673 -0
- apsimo/self_model/benchmark.py +1314 -0
- apsimo/self_model/brief.py +40 -0
- apsimo/self_model/event_concerns.py +1128 -0
- apsimo/self_model/execution_forecasts.py +353 -0
- apsimo/self_model/expectations.py +1595 -0
- apsimo/self_model/experiments.py +1150 -0
- apsimo/self_model/journal.py +148 -0
- apsimo/self_model/judgments.py +705 -0
- apsimo/self_model/native_outcomes.py +55 -0
- apsimo/self_model/params.py +220 -0
- apsimo/self_model/perspective.py +246 -0
- apsimo/self_model/reconcile.py +183 -0
- apsimo/self_model/reply_forecasts.py +381 -0
- apsimo/self_model/runtime_forecasts.py +296 -0
- apsimo/self_model/runtime_models.py +67 -0
- apsimo/self_model/settlement.py +207 -0
- apsimo/self_model/situation.py +1731 -0
- apsimo/self_model/store.py +883 -0
- apsimo/self_model/supervised.py +137 -0
- apsimo/self_model/thinker.py +99 -0
- apsimo/self_model/trust.py +388 -0
- apsimo/self_model/workspace.py +2388 -0
- apsimo/server.py +4197 -0
- apsimo/services/__init__.py +1 -0
- apsimo/services/agent_bridge.py +474 -0
- apsimo/services/initiative_executor.py +914 -0
- apsimo/services/instance.py +297 -0
- apsimo/sessions/__init__.py +22 -0
- apsimo/sessions/config.py +13 -0
- apsimo/sessions/context_loader.py +88 -0
- apsimo/sessions/federation_session.py +75 -0
- apsimo/sessions/isolated_session.py +98 -0
- apsimo/sessions/reports.py +84 -0
- apsimo/sessions/store.py +148 -0
- apsimo/setup.py +2818 -0
- apsimo/setup_hermes.py +879 -0
- apsimo/setup_local_work.py +218 -0
- apsimo/setup_native_goals.py +134 -0
- apsimo/setup_native_reviews.py +115 -0
- apsimo/skills/__init__.py +10 -0
- apsimo/skills/base.py +108 -0
- apsimo/skills/budget.py +28 -0
- apsimo/skills/executor.py +493 -0
- apsimo/skills/executors/__init__.py +1 -0
- apsimo/skills/executors/behavioral_correction.py +75 -0
- apsimo/skills/executors/capability_gap.py +38 -0
- apsimo/skills/executors/data_quality.py +163 -0
- apsimo/skills/executors/knowledge_acquisition.py +41 -0
- apsimo/skills/executors/operational_hygiene.py +185 -0
- apsimo/skills/executors/subsystem_health.py +169 -0
- apsimo/skills/hermes_export.py +431 -0
- apsimo/skills/index.py +123 -0
- apsimo/skills/learning/__init__.py +21 -0
- apsimo/skills/learning/novelty_detector.py +206 -0
- apsimo/skills/learning/pattern_extractor.py +199 -0
- apsimo/skills/learning/triggers.py +159 -0
- apsimo/skills/loader.py +246 -0
- apsimo/skills/migrations/002_progressive_loading.sql +6 -0
- apsimo/skills/migrations/backfill_triggers.py +20 -0
- apsimo/skills/models.py +202 -0
- apsimo/skills/packager.py +128 -0
- apsimo/skills/protocols.py +70 -0
- apsimo/skills/registry.py +191 -0
- apsimo/skills/runtime.py +58 -0
- apsimo/skills/sandbox_runner.py +229 -0
- apsimo/skills/scheduler.py +129 -0
- apsimo/skills/schema.py +79 -0
- apsimo/skills/security/__init__.py +12 -0
- apsimo/skills/security/guards.py +53 -0
- apsimo/skills/security/scanner.py +223 -0
- apsimo/skills_memory/__init__.py +26 -0
- apsimo/skills_memory/distill.py +159 -0
- apsimo/skills_memory/models.py +85 -0
- apsimo/skills_memory/retrieve.py +62 -0
- apsimo/skills_memory/store.py +172 -0
- apsimo/surprise/__init__.py +6 -0
- apsimo/surprise/accumulation.py +57 -0
- apsimo/surprise/scorer.py +102 -0
- apsimo/surprise/store.py +203 -0
- apsimo/task_queue/__init__.py +69 -0
- apsimo/task_queue/action_receipts.py +148 -0
- apsimo/task_queue/approval_relay_canary.py +108 -0
- apsimo/task_queue/config.py +85 -0
- apsimo/task_queue/contract.py +361 -0
- apsimo/task_queue/events.py +130 -0
- apsimo/task_queue/governor.py +1031 -0
- apsimo/task_queue/handlers/__init__.py +16 -0
- apsimo/task_queue/handlers/base.py +37 -0
- apsimo/task_queue/handlers/inference.py +640 -0
- apsimo/task_queue/handlers/monitoring.py +116 -0
- apsimo/task_queue/handlers/registry.py +75 -0
- apsimo/task_queue/handlers/subtask_handler.py +173 -0
- apsimo/task_queue/handlers/system_maintenance.py +147 -0
- apsimo/task_queue/mesh_integration.py +111 -0
- apsimo/task_queue/models.py +317 -0
- apsimo/task_queue/queue_manager.py +8286 -0
- apsimo/task_queue/routing.py +287 -0
- apsimo/task_queue/scheduler.py +252 -0
- apsimo/task_queue/schema.sql +197 -0
- apsimo/task_queue/work_control.py +342 -0
- apsimo/task_queue/worker.py +993 -0
- apsimo/telemetry.py +145 -0
- apsimo/tom/__init__.py +6 -0
- apsimo/tom/affect.py +387 -0
- apsimo/tom/approvals.py +171 -0
- apsimo/tom/arcs.py +896 -0
- apsimo/tom/asymmetry.py +131 -0
- apsimo/tom/eligibility.py +248 -0
- apsimo/tom/engagement.py +214 -0
- apsimo/tom/exposure.py +214 -0
- apsimo/tom/extractor.py +306 -0
- apsimo/tom/fact_adapters.py +144 -0
- apsimo/tom/facts.py +326 -0
- apsimo/tom/integration.py +592 -0
- apsimo/tom/leveled.py +118 -0
- apsimo/tom/levels.py +247 -0
- apsimo/tom/recipient_audit.py +995 -0
- apsimo/tom/recipient_simulator.py +593 -0
- apsimo/tom/source_lineage.py +93 -0
- apsimo/tom/tom2.py +277 -0
- apsimo/tom/visibility.py +559 -0
- apsimo/tom/visibility_store.py +414 -0
- apsimo/tools/__init__.py +0 -0
- apsimo/tools/definitions.py +740 -0
- apsimo/tools/handlers.py +943 -0
- apsimo/toolsmith/__init__.py +26 -0
- apsimo/toolsmith/authority.py +166 -0
- apsimo/toolsmith/engine.py +559 -0
- apsimo/toolsmith/integrity.py +100 -0
- apsimo/toolsmith/miner.py +145 -0
- apsimo/toolsmith/policy.py +110 -0
- apsimo/toolsmith/registry.py +635 -0
- apsimo/turns/__init__.py +17 -0
- apsimo/turns/audio.py +134 -0
- apsimo/turns/documents.py +235 -0
- apsimo/turns/executions.py +486 -0
- apsimo/turns/hermes_history.py +245 -0
- apsimo/turns/hermes_kanban.py +268 -0
- apsimo/turns/hermes_work.py +96 -0
- apsimo/turns/idempotency.py +752 -0
- apsimo/turns/local_work.py +115 -0
- apsimo/turns/media.py +581 -0
- apsimo/turns/reported_workers.py +196 -0
- apsimo/turns/source_annotations.py +283 -0
- apsimo/turns/source_attribution.py +154 -0
- apsimo/turns/source_read.py +351 -0
- apsimo/turns/source_vectors.py +263 -0
- apsimo/turns/video.py +210 -0
- apsimo/util/autonomy_preset.py +220 -0
- apsimo/util/instance.py +92 -0
- apsimo/util/model_output.py +25 -0
- apsimo/util/quiet_hours.py +27 -0
- apsimo/util/session_safety.py +37 -0
- apsimo/util/temporal.py +343 -0
- apsimo/vector/__init__.py +75 -0
- apsimo/vector/backfill.py +171 -0
- apsimo/vector/caption.py +114 -0
- apsimo/vector/collections.py +51 -0
- apsimo/vector/config.py +102 -0
- apsimo/vector/embedder.py +670 -0
- apsimo/vector/image_preprocess.py +406 -0
- apsimo/vector/image_store.py +296 -0
- apsimo/vector/indexes.py +162 -0
- apsimo/vector/migrate.py +334 -0
- apsimo/vector/multimodal_provider.py +417 -0
- apsimo/vector/multimodal_types.py +87 -0
- apsimo/vector/openai_provider.py +119 -0
- apsimo/vector/query.py +49 -0
- apsimo/vector/reranker.py +565 -0
- apsimo/vector/safety_image.py +159 -0
- apsimo/vector/scanner.py +197 -0
- apsimo/vector/setup.py +289 -0
- apsimo/vector/store.py +533 -0
- apsimo/vector/tiers.py +263 -0
- apsimo/work_orders.py +925 -0
- apsimo/workers/__init__.py +21 -0
- apsimo/workers/agent_bridge.py +640 -0
- apsimo/workers/colony_worker.py +382 -0
- apsimo/workers/queue_worker.py +441 -0
- apsimo/workers/skills_sync.py +152 -0
- apsimo/world_model/__init__.py +71 -0
- apsimo/world_model/causal_maintenance.py +131 -0
- apsimo/world_model/causal_policy.py +43 -0
- apsimo/world_model/causal_query.py +125 -0
- apsimo/world_model/confidence.py +54 -0
- apsimo/world_model/config.py +64 -0
- apsimo/world_model/constants.py +97 -0
- apsimo/world_model/entities.py +145 -0
- apsimo/world_model/expectation_resolvers.py +177 -0
- apsimo/world_model/extraction/__init__.py +7 -0
- apsimo/world_model/extraction/base.py +62 -0
- apsimo/world_model/extraction/conversation_extractor.py +262 -0
- apsimo/world_model/extraction/detector.py +74 -0
- apsimo/world_model/extraction/document_extractor.py +78 -0
- apsimo/world_model/extraction/formats/__init__.py +24 -0
- apsimo/world_model/extraction/formats/csv_fmt.py +68 -0
- apsimo/world_model/extraction/formats/html_fmt.py +72 -0
- apsimo/world_model/extraction/formats/json_fmt.py +68 -0
- apsimo/world_model/extraction/formats/pdf.py +43 -0
- apsimo/world_model/extraction/formats/text.py +27 -0
- apsimo/world_model/extraction/llm_extractor.py +164 -0
- apsimo/world_model/extraction/pipeline.py +73 -0
- apsimo/world_model/integrations/__init__.py +5 -0
- apsimo/world_model/integrations/mind_model_bridge.py +115 -0
- apsimo/world_model/integrations/social_intel_bridge.py +120 -0
- apsimo/world_model/jobs/__init__.py +4 -0
- apsimo/world_model/jobs/extraction_job.py +168 -0
- apsimo/world_model/llm_extract.py +572 -0
- apsimo/world_model/neo4j/__init__.py +5 -0
- apsimo/world_model/neo4j/backend.py +654 -0
- apsimo/world_model/observations.py +155 -0
- apsimo/world_model/populator.py +307 -0
- apsimo/world_model/postgres/__init__.py +1 -0
- apsimo/world_model/postgres/backend.py +683 -0
- apsimo/world_model/relationships.py +25 -0
- apsimo/world_model/resolution/__init__.py +13 -0
- apsimo/world_model/resolution/entity_resolver.py +232 -0
- apsimo/world_model/resolution/merge_audit.py +16 -0
- apsimo/world_model/resolution/merge_workflow.py +117 -0
- apsimo/world_model/source_reports.py +121 -0
- apsimo/world_model/sqlite/__init__.py +4 -0
- apsimo/world_model/sqlite/backend.py +855 -0
- apsimo/world_model/sqlite/schema.sql +132 -0
- apsimo/world_model/store.py +545 -0
- apsimo-1.3.0.dist-info/METADATA +78 -0
- apsimo-1.3.0.dist-info/RECORD +614 -0
- apsimo-1.3.0.dist-info/WHEEL +5 -0
- apsimo-1.3.0.dist-info/entry_points.txt +11 -0
- apsimo-1.3.0.dist-info/licenses/LICENSE +21 -0
- apsimo-1.3.0.dist-info/top_level.txt +2 -0
- colony_sidecar/__init__.py +4 -0
apsimo/feeds/template.py
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""Render distill / digest / discovery agent prompts from a FeedSpec.
|
|
2
|
+
|
|
3
|
+
The structure is distilled from a battle-tested single-feed deployment:
|
|
4
|
+
actionable framing ("what changes because of this"), hard link requirements,
|
|
5
|
+
tiered promotion rules for human-shared links, a privacy block, and a
|
|
6
|
+
learning-loop hook that records source promotions so the registry evolves.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from .spec import FeedSpec
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _archive_hint(spec: FeedSpec, suffix: str = "") -> str:
|
|
15
|
+
tail = f"-{suffix}" if suffix else "-HHMM"
|
|
16
|
+
return f"{spec.briefs_dir}/YYYY-MM-DD{tail}.md"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _privacy_block(spec: FeedSpec) -> str:
|
|
20
|
+
lines = ["PRIVACY (hard rules for anything you post):"]
|
|
21
|
+
for item in spec.privacy.get("forbidden", []):
|
|
22
|
+
lines.append(f"- Never mention {item}.")
|
|
23
|
+
aliases = spec.privacy.get("aliases") or []
|
|
24
|
+
if aliases:
|
|
25
|
+
lines.append("- Reference hardware/platforms by public product name only "
|
|
26
|
+
f"({', '.join(aliases)}).")
|
|
27
|
+
note = spec.privacy.get("note")
|
|
28
|
+
if note:
|
|
29
|
+
lines.append(f"- {note}")
|
|
30
|
+
return "\n".join(lines)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _registry_block(spec: FeedSpec) -> str:
|
|
34
|
+
if not spec.registry:
|
|
35
|
+
return ""
|
|
36
|
+
by_tier: dict[str, list[str]] = {"P0": [], "P1": [], "P2": []}
|
|
37
|
+
for e in spec.registry:
|
|
38
|
+
label = e.get("handle") or e.get("name")
|
|
39
|
+
cat = e.get("category", "")
|
|
40
|
+
by_tier.setdefault(e.get("weight", "P1"), []).append(
|
|
41
|
+
f"{label}" + (f" ({cat})" if cat else ""))
|
|
42
|
+
lines = ["SOURCE REGISTRY (tiered; used by the promotion rules):"]
|
|
43
|
+
tier_meaning = {"P0": "always include, even a bare link",
|
|
44
|
+
"P1": "include; promote to P0 when a curator flags it",
|
|
45
|
+
"P2": "background only"}
|
|
46
|
+
for tier in ("P0", "P1", "P2"):
|
|
47
|
+
if by_tier.get(tier):
|
|
48
|
+
lines.append(f"- {tier} ({tier_meaning[tier]}): " + ", ".join(by_tier[tier]))
|
|
49
|
+
return "\n".join(lines)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _promotion_block(spec: FeedSpec) -> str:
|
|
53
|
+
return """PROMOTION RULES for human-shared links (apply in order, first match wins):
|
|
54
|
+
1. Link's domain/author matches a P0 source AND content is a new SOTA / reproducible recipe / breakthrough -> P0
|
|
55
|
+
2. Link's domain/author matches a P0 source -> P0
|
|
56
|
+
3. Link's domain/author matches a P1 source AND a curator flagged it important/!!!/P0 -> P0
|
|
57
|
+
4. Link's domain/author matches a P1 source -> P1
|
|
58
|
+
5. Link's domain/author matches a P2 source -> P2
|
|
59
|
+
6. A curator flagged it important/!!! -> P0
|
|
60
|
+
7. A curator provided any context note -> P1
|
|
61
|
+
8. Bare link from an unknown source -> P2
|
|
62
|
+
|
|
63
|
+
Treat every human message as a link share and PRESERVE their context notes verbatim:
|
|
64
|
+
text before a URL is their note about it; text after is an annotation (e.g. "and the
|
|
65
|
+
first reply" means follow that reply too). Never summarize their notes away - they are
|
|
66
|
+
first-class signal."""
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _community_block(spec: FeedSpec) -> str:
|
|
70
|
+
read_back = spec.destination.get("read_back", "")
|
|
71
|
+
if spec.audience != "group" or not read_back:
|
|
72
|
+
return ""
|
|
73
|
+
return f"""Read the destination group's recent messages from human participants since the last
|
|
74
|
+
brief. Use: {read_back}
|
|
75
|
+
|
|
76
|
+
{_promotion_block(spec)}
|
|
77
|
+
|
|
78
|
+
COMMUNITY INPUT section: every link humans posted in this window, in order, each with
|
|
79
|
+
its priority and a one-line framing answering "{spec.brief['framing']}".
|
|
80
|
+
|
|
81
|
+
ABSOLUTE RULES for the group:
|
|
82
|
+
- The group is a one-way broadcast channel for you: read for intel, post briefs, nothing else.
|
|
83
|
+
- Do NOT react to, reply to, or engage with any message in the group, ever.
|
|
84
|
+
- If a participant asks you a direct question there, do not answer it in the group."""
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _delivery_block(spec: FeedSpec, what: str = "brief") -> str:
|
|
88
|
+
kind = spec.destination.get("kind")
|
|
89
|
+
if kind == "command":
|
|
90
|
+
return (f"Post the {what} to its destination by piping it to:\n"
|
|
91
|
+
f" {spec.destination['send_command']}")
|
|
92
|
+
if kind == "deliver":
|
|
93
|
+
return (f"Your final response IS the {what}; the scheduler delivers it automatically. "
|
|
94
|
+
"Do not send it yourself with any messaging tool.")
|
|
95
|
+
return (f"Do NOT post the {what} anywhere; this feed is archive-only. "
|
|
96
|
+
"Your final response should be the brief text.")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _context_files_block(spec: FeedSpec) -> str:
|
|
100
|
+
files = spec.brief.get("context_files") or []
|
|
101
|
+
if not files:
|
|
102
|
+
return ""
|
|
103
|
+
lines = ["Also read these context files if they exist and fold relevant movement into the brief:"]
|
|
104
|
+
for f in files:
|
|
105
|
+
lines.append(f"- {f.get('label', 'context')}: {f['path']}")
|
|
106
|
+
return "\n".join(lines)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _learning_block(spec: FeedSpec) -> str:
|
|
110
|
+
if not spec.learning_loop:
|
|
111
|
+
return ""
|
|
112
|
+
return f"""LEARNING LOOP HOOK: after archiving, whenever you promoted a link from an unknown
|
|
113
|
+
source to P0/P1 on content weight alone, append one line to {spec.deltas_log}:
|
|
114
|
+
TIMESTAMP new_source=domain-or-handle (reason) promoted_to=P1 link=...
|
|
115
|
+
This is how the source registry evolves. Skip the log when nothing was promoted."""
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def render_distill(spec: FeedSpec) -> str:
|
|
119
|
+
parts = [
|
|
120
|
+
f"You are the {spec.title} distillation agent.",
|
|
121
|
+
f"TOPIC CHARTER: {spec.topic}",
|
|
122
|
+
f"""Your job each run:
|
|
123
|
+
1. Read the feed queue at {spec.queue_path}
|
|
124
|
+
2. Distill the top items into a brief. Plain text, no markdown. Structure:
|
|
125
|
+
- Header line with feed title and date/time (local timezone)
|
|
126
|
+
- HIGH PRIORITY section (P0 and P1 items), each with: title, 1-2 sentence summary,
|
|
127
|
+
a "why it matters" line, benchmark numbers if any, and SOURCE LINK
|
|
128
|
+
- STANDARD section (P2 items): one line each with link, cap at the top
|
|
129
|
+
{spec.brief['standard_cap']} and drop the rest silently
|
|
130
|
+
3. ALWAYS include a source link for every item so readers can click through.
|
|
131
|
+
|
|
132
|
+
OUTPUT FRAMING: for every HIGH PRIORITY item the "why it matters" line MUST answer:
|
|
133
|
+
"{spec.brief['framing']}"
|
|
134
|
+
Not "what does this article say" - the framing has to be actionable.""",
|
|
135
|
+
]
|
|
136
|
+
for block in (_context_files_block(spec), _community_block(spec),
|
|
137
|
+
_privacy_block(spec), _registry_block(spec)):
|
|
138
|
+
if block:
|
|
139
|
+
parts.append(block)
|
|
140
|
+
parts.append(_delivery_block(spec))
|
|
141
|
+
parts.append(f"Archive the brief at {_archive_hint(spec)}")
|
|
142
|
+
lb = _learning_block(spec)
|
|
143
|
+
if lb:
|
|
144
|
+
parts.append(lb)
|
|
145
|
+
extra = spec.brief.get("extra_instructions")
|
|
146
|
+
if extra:
|
|
147
|
+
parts.append(extra)
|
|
148
|
+
parts.append('If the queue is empty and there is genuinely nothing to report, '
|
|
149
|
+
'respond with exactly "[SILENT]" and post nothing.')
|
|
150
|
+
return "\n\n".join(parts)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def render_digest(spec: FeedSpec) -> str:
|
|
154
|
+
parts = [
|
|
155
|
+
f"You are the {spec.title} daily digest agent.",
|
|
156
|
+
f"TOPIC CHARTER: {spec.topic}",
|
|
157
|
+
f"""Your job: produce ONE daily rollup of the last 24h.
|
|
158
|
+
1. Read the feed queue at {spec.queue_path}
|
|
159
|
+
2. Read the briefs archived in the last 24h under {spec.briefs_dir}/
|
|
160
|
+
3. Synthesize: the day's 3-7 most consequential items (dedup against what briefs
|
|
161
|
+
already said - lead with what CHANGED over the day, not a re-list), each with a
|
|
162
|
+
"why it matters" line answering "{spec.brief['framing']}" and a SOURCE LINK.
|
|
163
|
+
4. Close with a 2-3 sentence trend read: where is this topic moving?""",
|
|
164
|
+
]
|
|
165
|
+
for block in (_context_files_block(spec), _community_block(spec), _privacy_block(spec)):
|
|
166
|
+
if block:
|
|
167
|
+
parts.append(block)
|
|
168
|
+
parts.append(_delivery_block(spec, what="digest"))
|
|
169
|
+
parts.append(f"Archive the digest at {_archive_hint(spec, 'digest')}")
|
|
170
|
+
parts.append('If nothing meaningful happened, respond with exactly "[SILENT]".')
|
|
171
|
+
return "\n\n".join(parts)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def render_discovery(spec: FeedSpec) -> str:
|
|
175
|
+
return f"""You are the source-discovery agent for the {spec.title}.
|
|
176
|
+
|
|
177
|
+
TOPIC CHARTER: {spec.topic}
|
|
178
|
+
|
|
179
|
+
Your job: find NEW media sources worth adding to this feed, qualify them yourself,
|
|
180
|
+
and propose only the best. Search broadly with web search and VARY your angles each
|
|
181
|
+
run: niche experts on X/social platforms, technical blogs and newsletters, active
|
|
182
|
+
GitHub authors/orgs, forums and communities, benchmark/evaluation sites, academic
|
|
183
|
+
groups publishing with code.
|
|
184
|
+
|
|
185
|
+
QUALIFY each candidate before proposing: original signal (not aggregation)?
|
|
186
|
+
Track record of real numbers/working code? Active at least weekly? Not already
|
|
187
|
+
covered by the registry? High signal-to-noise?
|
|
188
|
+
|
|
189
|
+
FILTER HARD: zero good proposals beats one bad one. Diminishing returns over time
|
|
190
|
+
are expected - be honest when a sweep comes up empty.
|
|
191
|
+
|
|
192
|
+
For each qualified source propose: name + link, 2-3 recent high-signal examples with
|
|
193
|
+
direct links, why it fits this feed's topic charter, and how to monitor it (account
|
|
194
|
+
watch, RSS URL, forum URL, keyword filter). The operator applies accepted proposals
|
|
195
|
+
to the feed's spec file (registry + sources sections).
|
|
196
|
+
|
|
197
|
+
{_privacy_block(spec)}
|
|
198
|
+
|
|
199
|
+
{_registry_block(spec) or 'SOURCE REGISTRY: (none yet - everything qualified is new)'}
|
|
200
|
+
|
|
201
|
+
Your final response is the proposal report (or a one-line "no qualified sources this
|
|
202
|
+
sweep"); the scheduler delivers it."""
|
apsimo/gate/__init__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Response Gate — deterministic 7-layer response pipeline."""
|
|
2
|
+
|
|
3
|
+
from apsimo.gate.models import GatePayload, GateDecision, GateResultCode, DispatchResult
|
|
4
|
+
from apsimo.gate.config import GateConfig
|
|
5
|
+
from apsimo.gate.pipeline import ResponseGate
|
|
6
|
+
from apsimo.gate.audit import GateAuditLog, GateAuditRecord, InMemoryAuditLog
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"GatePayload",
|
|
10
|
+
"GateDecision",
|
|
11
|
+
"GateResultCode",
|
|
12
|
+
"DispatchResult",
|
|
13
|
+
"GateConfig",
|
|
14
|
+
"ResponseGate",
|
|
15
|
+
"GateAuditLog",
|
|
16
|
+
"GateAuditRecord",
|
|
17
|
+
"InMemoryAuditLog",
|
|
18
|
+
]
|
apsimo/gate/audit.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""GateAuditLog — append-only audit log for all gate decisions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import uuid
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from typing import Optional, Protocol
|
|
10
|
+
|
|
11
|
+
from apsimo.gate.models import GateDecision
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class GateAuditRecord:
|
|
16
|
+
record_id: str
|
|
17
|
+
turn_id: str
|
|
18
|
+
session_id: str
|
|
19
|
+
contact_id: str
|
|
20
|
+
result_code: str
|
|
21
|
+
blocked: bool
|
|
22
|
+
blocking_layer: Optional[int]
|
|
23
|
+
block_reason: Optional[str]
|
|
24
|
+
layer_results: dict
|
|
25
|
+
sensitivity_level: str
|
|
26
|
+
overrides_applied: list
|
|
27
|
+
evaluated_at: datetime
|
|
28
|
+
response_text_hash: str # SHA-256 of response_text (not plaintext)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class GateAuditLog(Protocol):
|
|
32
|
+
"""Append-only audit log interface."""
|
|
33
|
+
|
|
34
|
+
async def record(self, decision: GateDecision) -> None:
|
|
35
|
+
"""Write a gate decision to the audit log before dispatch."""
|
|
36
|
+
...
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class InMemoryAuditLog:
|
|
40
|
+
"""In-memory audit log for testing."""
|
|
41
|
+
|
|
42
|
+
def __init__(self) -> None:
|
|
43
|
+
self.records: list[GateAuditRecord] = []
|
|
44
|
+
|
|
45
|
+
async def record(self, decision: GateDecision) -> None:
|
|
46
|
+
rec = GateAuditRecord(
|
|
47
|
+
record_id=str(uuid.uuid4()),
|
|
48
|
+
turn_id=decision.payload_turn_id,
|
|
49
|
+
session_id=decision.layer_results.get("_session_id", ""),
|
|
50
|
+
contact_id=decision.layer_results.get("_contact_id", ""),
|
|
51
|
+
result_code=decision.result_code.value,
|
|
52
|
+
blocked=decision.blocked,
|
|
53
|
+
blocking_layer=decision.blocking_layer,
|
|
54
|
+
block_reason=decision.block_reason,
|
|
55
|
+
layer_results=decision.layer_results,
|
|
56
|
+
sensitivity_level=decision.layer_results.get("_sensitivity", "standard"),
|
|
57
|
+
overrides_applied=decision.layer_results.get("_overrides_applied", []),
|
|
58
|
+
evaluated_at=decision.evaluated_at,
|
|
59
|
+
response_text_hash=decision.layer_results.get("_response_hash", ""),
|
|
60
|
+
)
|
|
61
|
+
self.records.append(rec)
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Versioned communication constraints for one ResponseGuard evaluation.
|
|
2
|
+
|
|
3
|
+
The embedding host remains the authority source. This contract only binds a
|
|
4
|
+
candidate response to the exact target, route, grant, and semantic policy that
|
|
5
|
+
the host says constrain it. It deliberately cannot grant execution/control
|
|
6
|
+
authority or select private owner context.
|
|
7
|
+
|
|
8
|
+
``policy_digest`` identifies the host's complete immutable policy record. The
|
|
9
|
+
separate ``context_digest`` covers the exact, reduced record Colony received
|
|
10
|
+
and made visible to guard/model inputs, so callers can detect substitution or
|
|
11
|
+
field-level tampering without Colony needing the host's private policy store.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import hashlib
|
|
17
|
+
import json
|
|
18
|
+
import re
|
|
19
|
+
from typing import Any, Dict, Literal
|
|
20
|
+
|
|
21
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
COMMUNICATION_POLICY_CONTEXT_SCHEMA = "CommunicationPolicyContextV1"
|
|
25
|
+
COMMUNICATION_POLICY_CONTEXT_VERSION = 1
|
|
26
|
+
COMMUNICATION_DISCLOSURE_CLASSES = frozenset(
|
|
27
|
+
{"none", "public", "contact_scoped", "owner_explicit"}
|
|
28
|
+
)
|
|
29
|
+
MAX_COMMUNICATION_PURPOSE_CHARS = 1_024
|
|
30
|
+
MAX_COMMUNICATION_DISCLOSURE_STATEMENT_CHARS = 2_048
|
|
31
|
+
|
|
32
|
+
_IDENTIFIER_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:/-]{7,255}$")
|
|
33
|
+
_DIGEST_RE = re.compile(r"^[a-f0-9]{64}$")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _canonical_json(value: object) -> str:
|
|
37
|
+
"""Return the one stable JSON representation used by ``context_digest``."""
|
|
38
|
+
|
|
39
|
+
return json.dumps(
|
|
40
|
+
value,
|
|
41
|
+
ensure_ascii=False,
|
|
42
|
+
allow_nan=False,
|
|
43
|
+
separators=(",", ":"),
|
|
44
|
+
sort_keys=True,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class CommunicationPolicyContextV1(BaseModel):
|
|
49
|
+
"""Exact, immutable communication-policy input for ResponseGuard.
|
|
50
|
+
|
|
51
|
+
Every field is required, including ``schema``, ``version``, and the three
|
|
52
|
+
literal-false capability statements. That makes a partial policy invalid
|
|
53
|
+
instead of allowing defaults to silently widen or reinterpret it.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
model_config = ConfigDict(extra="forbid", frozen=True, strict=True)
|
|
57
|
+
|
|
58
|
+
schema_name: Literal["CommunicationPolicyContextV1"] = Field(
|
|
59
|
+
alias="schema", serialization_alias="schema"
|
|
60
|
+
)
|
|
61
|
+
version: Literal[1]
|
|
62
|
+
target_contact_id: str = Field(min_length=1, max_length=128)
|
|
63
|
+
route_id: str = Field(min_length=8, max_length=256)
|
|
64
|
+
grant_id: str = Field(min_length=8, max_length=256)
|
|
65
|
+
grant_digest: str = Field(min_length=64, max_length=64)
|
|
66
|
+
policy_id: str = Field(min_length=8, max_length=256)
|
|
67
|
+
policy_digest: str = Field(min_length=64, max_length=64)
|
|
68
|
+
purpose: str = Field(min_length=1, max_length=MAX_COMMUNICATION_PURPOSE_CHARS)
|
|
69
|
+
disclosure_class: Literal[
|
|
70
|
+
"none", "public", "contact_scoped", "owner_explicit"
|
|
71
|
+
]
|
|
72
|
+
disclosure_statement: str = Field(
|
|
73
|
+
min_length=1,
|
|
74
|
+
max_length=MAX_COMMUNICATION_DISCLOSURE_STATEMENT_CHARS,
|
|
75
|
+
)
|
|
76
|
+
grants_execution_authority: Literal[False]
|
|
77
|
+
grants_control_authority: Literal[False]
|
|
78
|
+
selects_owner_private_context: Literal[False]
|
|
79
|
+
|
|
80
|
+
@field_validator(
|
|
81
|
+
"target_contact_id",
|
|
82
|
+
"purpose",
|
|
83
|
+
"disclosure_statement",
|
|
84
|
+
)
|
|
85
|
+
@classmethod
|
|
86
|
+
def _canonical_text(cls, value: str) -> str:
|
|
87
|
+
if value != value.strip():
|
|
88
|
+
raise ValueError("communication policy text must be canonical")
|
|
89
|
+
if any(ord(character) < 0x20 or ord(character) == 0x7F for character in value):
|
|
90
|
+
raise ValueError("communication policy text contains control characters")
|
|
91
|
+
return value
|
|
92
|
+
|
|
93
|
+
@field_validator("target_contact_id")
|
|
94
|
+
@classmethod
|
|
95
|
+
def _exact_target(cls, value: str) -> str:
|
|
96
|
+
if (
|
|
97
|
+
value.lower() in {"all", "any", "everyone"}
|
|
98
|
+
or any(character in value for character in ("*", "?", "[", "]"))
|
|
99
|
+
):
|
|
100
|
+
raise ValueError("communication policy target must be exact")
|
|
101
|
+
return value
|
|
102
|
+
|
|
103
|
+
@field_validator("route_id", "grant_id", "policy_id")
|
|
104
|
+
@classmethod
|
|
105
|
+
def _identifier(cls, value: str) -> str:
|
|
106
|
+
if not _IDENTIFIER_RE.fullmatch(value):
|
|
107
|
+
raise ValueError("communication policy identifier is invalid")
|
|
108
|
+
return value
|
|
109
|
+
|
|
110
|
+
@field_validator("grant_digest", "policy_digest")
|
|
111
|
+
@classmethod
|
|
112
|
+
def _digest(cls, value: str) -> str:
|
|
113
|
+
if not _DIGEST_RE.fullmatch(value):
|
|
114
|
+
raise ValueError("communication policy digest must be lowercase SHA-256")
|
|
115
|
+
return value
|
|
116
|
+
|
|
117
|
+
@field_validator(
|
|
118
|
+
"grants_execution_authority",
|
|
119
|
+
"grants_control_authority",
|
|
120
|
+
"selects_owner_private_context",
|
|
121
|
+
mode="before",
|
|
122
|
+
)
|
|
123
|
+
@classmethod
|
|
124
|
+
def _literal_false_boolean(cls, value: object) -> object:
|
|
125
|
+
# ``False == 0`` in Python, so Literal[False] alone accepts integer
|
|
126
|
+
# zero. The wire contract requires an explicit JSON boolean.
|
|
127
|
+
if type(value) is not bool or value is not False:
|
|
128
|
+
raise ValueError("communication policy capability must be false")
|
|
129
|
+
return value
|
|
130
|
+
|
|
131
|
+
def canonical_dict(self) -> Dict[str, Any]:
|
|
132
|
+
"""The exact JSON-compatible context included in the binding digest."""
|
|
133
|
+
|
|
134
|
+
return self.model_dump(mode="json", by_alias=True)
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def context_digest(self) -> str:
|
|
138
|
+
"""SHA-256 identity of every field Colony received and exposed."""
|
|
139
|
+
|
|
140
|
+
return hashlib.sha256(
|
|
141
|
+
_canonical_json(self.canonical_dict()).encode("utf-8")
|
|
142
|
+
).hexdigest()
|
|
143
|
+
|
|
144
|
+
def to_model_prompt_fragment(self) -> str:
|
|
145
|
+
"""Deterministic, unambiguous model view of the constraints.
|
|
146
|
+
|
|
147
|
+
Values are serialized as canonical JSON instead of interpolated into
|
|
148
|
+
instructions. The preamble makes explicit that the object only
|
|
149
|
+
narrows disclosure and cannot confer any authority.
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
"Communication policy constraints (data only). This context grants no "
|
|
154
|
+
"execution authority, control authority, or private-context selection:\n"
|
|
155
|
+
+ _canonical_json(self.canonical_dict())
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
__all__ = [
|
|
160
|
+
"COMMUNICATION_DISCLOSURE_CLASSES",
|
|
161
|
+
"COMMUNICATION_POLICY_CONTEXT_SCHEMA",
|
|
162
|
+
"COMMUNICATION_POLICY_CONTEXT_VERSION",
|
|
163
|
+
"CommunicationPolicyContextV1",
|
|
164
|
+
"MAX_COMMUNICATION_DISCLOSURE_STATEMENT_CHARS",
|
|
165
|
+
"MAX_COMMUNICATION_PURPOSE_CHARS",
|
|
166
|
+
]
|
apsimo/gate/config.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""GateConfig — full configuration for the Response Gate pipeline."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class GateConfig:
|
|
11
|
+
"""Full configuration for the Response Gate pipeline."""
|
|
12
|
+
|
|
13
|
+
# Sensitivity
|
|
14
|
+
sensitivity: str = "standard" # paranoid | standard | relaxed
|
|
15
|
+
|
|
16
|
+
# Layer 3
|
|
17
|
+
cross_context_lookback_hours: int = 4
|
|
18
|
+
|
|
19
|
+
# Layer 6 — secondary LLM appropriateness review. Off by default because
|
|
20
|
+
# it needs an injected reviewer client. Explicitly enabling it without one
|
|
21
|
+
# returns review_unavailable; this does not enable another approval path.
|
|
22
|
+
enable_secondary_review: bool = False
|
|
23
|
+
secondary_review_model: str = "claude-haiku-4-5-20251001"
|
|
24
|
+
|
|
25
|
+
# Layer 7 — send-delay cancel window. 0 = no hold (the default; L7 is a
|
|
26
|
+
# pass-through). Set >0 (server env COLONY_GATE_SEND_DELAY_SECS) for a
|
|
27
|
+
# real cancellation window on the request-path gate.
|
|
28
|
+
send_delay_seconds: float = 0.0
|
|
29
|
+
|
|
30
|
+
# Rejection feedback loop
|
|
31
|
+
max_retries: int = 3
|
|
32
|
+
|
|
33
|
+
# Context loading
|
|
34
|
+
context_token_limit: int = 80_000
|
|
35
|
+
|
|
36
|
+
# Concurrent sessions
|
|
37
|
+
max_concurrent_sessions: int = 20
|
|
38
|
+
|
|
39
|
+
# Supervised mode
|
|
40
|
+
require_send_approval: bool = False
|
|
41
|
+
|
|
42
|
+
# Owner-shareable patterns (bypass Layer 2)
|
|
43
|
+
owner_shareable_patterns: list = field(default_factory=list)
|
|
44
|
+
|
|
45
|
+
# Per-contact overrides
|
|
46
|
+
contact_overrides: list = field(default_factory=list)
|
|
47
|
+
|
|
48
|
+
# Custom PII patterns
|
|
49
|
+
custom_pii_patterns: list = field(default_factory=list)
|
|
50
|
+
|
|
51
|
+
# Injection ruleset
|
|
52
|
+
injection_ruleset_path: str = "colony/gate/rulesets/injection_v1.yaml"
|
|
53
|
+
ruleset_hot_reload: bool = False
|
|
54
|
+
|
|
55
|
+
# Audit
|
|
56
|
+
audit_retention_days: int = 365
|
|
57
|
+
|
|
58
|
+
# Workspace
|
|
59
|
+
log_full_deliberation: bool = False
|
|
60
|
+
workspace_memory_ttl_days: int = 90
|
|
61
|
+
|
|
62
|
+
def is_owner_shareable(self, value: str, pattern_name: str) -> bool:
|
|
63
|
+
for item in self.owner_shareable_patterns:
|
|
64
|
+
if item.get("pattern_name") == pattern_name and item.get("value") == value:
|
|
65
|
+
return True
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
def get_contact_override(self, contact_id: str) -> Optional[dict]:
|
|
69
|
+
for override in self.contact_overrides:
|
|
70
|
+
if override.get("contact_id") == contact_id:
|
|
71
|
+
return override
|
|
72
|
+
return None
|