apsimo 1.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- apsimo/__init__.py +38 -0
- apsimo/__main__.py +6 -0
- apsimo/agent/__init__.py +6 -0
- apsimo/agent/client.py +276 -0
- apsimo/agent/models.py +46 -0
- apsimo/agents/__init__.py +20 -0
- apsimo/agents/models.py +264 -0
- apsimo/agents/store.py +861 -0
- apsimo/agents/websocket.py +522 -0
- apsimo/api/__init__.py +1 -0
- apsimo/api/auth_telemetry.py +287 -0
- apsimo/api/authority.py +1203 -0
- apsimo/api/contact_grants.py +347 -0
- apsimo/api/middleware.py +483 -0
- apsimo/api/routers/__init__.py +1 -0
- apsimo/api/routers/commitment_work.py +265 -0
- apsimo/api/routers/context_gate.py +123 -0
- apsimo/api/routers/executions.py +140 -0
- apsimo/api/routers/followup_plans.py +147 -0
- apsimo/api/routers/governed_actions.py +162 -0
- apsimo/api/routers/host.py +14473 -0
- apsimo/api/routers/initiative_work.py +115 -0
- apsimo/api/routers/mining.py +104 -0
- apsimo/api/routers/observations.py +110 -0
- apsimo/api/routers/social_state.py +225 -0
- apsimo/api/routers/task_queue.py +2715 -0
- apsimo/api/routers/temporal_followups.py +251 -0
- apsimo/api/routers/transport.py +110 -0
- apsimo/api/routers/transport_ingress_api.py +240 -0
- apsimo/api/schemas/__init__.py +1 -0
- apsimo/api/schemas/host.py +1949 -0
- apsimo/autonomy/cli.py +110 -0
- apsimo/autonomy/condition_worker.py +437 -0
- apsimo/autonomy/config.py +424 -0
- apsimo/autonomy/loop.py +4316 -0
- apsimo/autonomy/registry.py +339 -0
- apsimo/autonomy/scheduler.py +1822 -0
- apsimo/autonomy/synthesis.py +449 -0
- apsimo/backup.py +962 -0
- apsimo/beliefs/__init__.py +23 -0
- apsimo/beliefs/contradictions.py +109 -0
- apsimo/beliefs/decay.py +61 -0
- apsimo/beliefs/engine.py +479 -0
- apsimo/beliefs/models.py +67 -0
- apsimo/beliefs/promotion.py +41 -0
- apsimo/beliefs/resolve.py +58 -0
- apsimo/beliefs/source_claims.py +690 -0
- apsimo/beliefs/source_projection.py +883 -0
- apsimo/beliefs/source_time.py +208 -0
- apsimo/beliefs/store.py +133 -0
- apsimo/briefings/aggregators.py +824 -0
- apsimo/briefings/composer.py +420 -0
- apsimo/briefings/config.py +55 -0
- apsimo/briefings/delivery.py +439 -0
- apsimo/briefings/engagement.py +97 -0
- apsimo/briefings/engine.py +274 -0
- apsimo/briefings/enhancer.py +99 -0
- apsimo/briefings/models.py +183 -0
- apsimo/briefings/scheduler.py +382 -0
- apsimo/briefings/store.py +435 -0
- apsimo/chain/__init__.py +48 -0
- apsimo/chain/block.py +100 -0
- apsimo/chain/cli.py +704 -0
- apsimo/chain/genesis.py +443 -0
- apsimo/chain/identity.py +416 -0
- apsimo/chain/keys.py +1025 -0
- apsimo/chain/local_keys.py +187 -0
- apsimo/chain/manager.py +290 -0
- apsimo/chain/node.py +163 -0
- apsimo/chain/plugin_transactions.py +371 -0
- apsimo/chain/protocol.py +220 -0
- apsimo/chain/state_machine.py +676 -0
- apsimo/chain/storage.py +503 -0
- apsimo/chain/transactions.py +250 -0
- apsimo/chain/validation.py +397 -0
- apsimo/channels/__init__.py +1 -0
- apsimo/channels/manifest.py +31 -0
- apsimo/channels/migrations/001_channels_schema.sql +12 -0
- apsimo/channels/phone_gateways.py +42 -0
- apsimo/channels/presence.py +188 -0
- apsimo/channels/router.py +235 -0
- apsimo/channels/store.py +231 -0
- apsimo/cli.py +2688 -0
- apsimo/cognition/__init__.py +11 -0
- apsimo/cognition/charter.py +398 -0
- apsimo/cognition/drive_governance.py +3530 -0
- apsimo/cognition/evidence_pipeline.py +1627 -0
- apsimo/cognition/external_events.py +932 -0
- apsimo/cognition/goal_spine.py +3488 -0
- apsimo/cognition/introspection.py +214 -0
- apsimo/cognition/prompt.py +150 -0
- apsimo/cognition/runtime.py +108 -0
- apsimo/cognition/trigger.py +154 -0
- apsimo/commitments/__init__.py +18 -0
- apsimo/commitments/local_work.py +355 -0
- apsimo/commitments/store.py +1052 -0
- apsimo/commitments/work.py +91 -0
- apsimo/compat.py +53 -0
- apsimo/compression/__init__.py +467 -0
- apsimo/connectors/__init__.py +21 -0
- apsimo/connectors/base.py +152 -0
- apsimo/connectors/caldav_calendar.py +125 -0
- apsimo/connectors/fs_documents.py +85 -0
- apsimo/connectors/imap_email.py +138 -0
- apsimo/connectors/manager.py +218 -0
- apsimo/connectors/webhook_pull.py +88 -0
- apsimo/contacts/__init__.py +33 -0
- apsimo/contacts/comms.py +357 -0
- apsimo/contacts/config.py +79 -0
- apsimo/contacts/exporters/__init__.py +1 -0
- apsimo/contacts/exporters/vcard.py +71 -0
- apsimo/contacts/identity_links.py +251 -0
- apsimo/contacts/importer.py +280 -0
- apsimo/contacts/importers/__init__.py +1 -0
- apsimo/contacts/importers/batch.py +43 -0
- apsimo/contacts/importers/macos_contacts.py +101 -0
- apsimo/contacts/migrations/001_contacts_schema.sql +141 -0
- apsimo/contacts/migrations/002_trust_scopes.sql +36 -0
- apsimo/contacts/migrations/003_open_gateway_enum.sql +32 -0
- apsimo/contacts/migrations/004_contact_provision_operations.sql +18 -0
- apsimo/contacts/migrations/005_identity_links.sql +27 -0
- apsimo/contacts/models.py +308 -0
- apsimo/contacts/scoring.py +16 -0
- apsimo/contacts/store.py +1623 -0
- apsimo/contacts/transport_ingress.py +252 -0
- apsimo/contacts/world_bridge.py +314 -0
- apsimo/contextgate/__init__.py +69 -0
- apsimo/contextgate/chunker.py +169 -0
- apsimo/contextgate/estimate.py +54 -0
- apsimo/contextgate/gate.py +313 -0
- apsimo/contextgate/retrieve.py +115 -0
- apsimo/delivery/__init__.py +16 -0
- apsimo/delivery/bridge.py +1260 -0
- apsimo/delivery/channels.py +526 -0
- apsimo/delivery/classification.py +50 -0
- apsimo/delivery/rate_limiter.py +268 -0
- apsimo/delivery/reachout_policy.py +206 -0
- apsimo/directed/__init__.py +22 -0
- apsimo/directed/audit.py +167 -0
- apsimo/directed/intake.py +95 -0
- apsimo/directed/models.py +191 -0
- apsimo/directed/service.py +509 -0
- apsimo/directives/__init__.py +25 -0
- apsimo/directives/evidence.py +87 -0
- apsimo/directives/extractor.py +188 -0
- apsimo/directives/guard.py +364 -0
- apsimo/directives/models.py +206 -0
- apsimo/directives/service.py +372 -0
- apsimo/directives/store.py +167 -0
- apsimo/doctor.py +2173 -0
- apsimo/environment.py +43 -0
- apsimo/events/__init__.py +33 -0
- apsimo/events/broadcaster.py +98 -0
- apsimo/events/bus.py +217 -0
- apsimo/events/journal.py +863 -0
- apsimo/events/stream.py +131 -0
- apsimo/events/types.py +150 -0
- apsimo/execution_results.py +357 -0
- apsimo/feedback/__init__.py +5 -0
- apsimo/feedback/store.py +76 -0
- apsimo/feeds/__init__.py +19 -0
- apsimo/feeds/cli.py +84 -0
- apsimo/feeds/engine.py +437 -0
- apsimo/feeds/example-feed.yaml +77 -0
- apsimo/feeds/hermes_cron.py +126 -0
- apsimo/feeds/manager.py +235 -0
- apsimo/feeds/spec.py +250 -0
- apsimo/feeds/template.py +202 -0
- apsimo/gate/__init__.py +18 -0
- apsimo/gate/audit.py +61 -0
- apsimo/gate/communication_policy.py +166 -0
- apsimo/gate/config.py +72 -0
- apsimo/gate/context_provenance.py +170 -0
- apsimo/gate/env_risk.py +226 -0
- apsimo/gate/guard_audit.py +353 -0
- apsimo/gate/layers/__init__.py +1 -0
- apsimo/gate/layers/base.py +15 -0
- apsimo/gate/layers/l1_recipient.py +66 -0
- apsimo/gate/layers/l2_pii.py +134 -0
- apsimo/gate/layers/l3_cross_context.py +50 -0
- apsimo/gate/layers/l4_trust_tier.py +78 -0
- apsimo/gate/layers/l5_injection.py +199 -0
- apsimo/gate/layers/l6_review.py +86 -0
- apsimo/gate/layers/l7_delay.py +100 -0
- apsimo/gate/layers/tom2_epistemic.py +185 -0
- apsimo/gate/models.py +64 -0
- apsimo/gate/pending_dispatch.py +5 -0
- apsimo/gate/pipeline.py +206 -0
- apsimo/gate/rejection.py +259 -0
- apsimo/gate/response_guard.py +700 -0
- apsimo/gate/rulesets/injection_v1.yaml +51 -0
- apsimo/gate/surface_policy.py +189 -0
- apsimo/gate/taint.py +226 -0
- apsimo/genesis.json +9 -0
- apsimo/goals/__init__.py +100 -0
- apsimo/goals/config.py +38 -0
- apsimo/goals/decomposer.py +421 -0
- apsimo/goals/engine.py +617 -0
- apsimo/goals/inference.py +354 -0
- apsimo/goals/models.py +302 -0
- apsimo/goals/priority.py +270 -0
- apsimo/goals/queue_bridge.py +149 -0
- apsimo/goals/replan.py +450 -0
- apsimo/goals/schema.sql +89 -0
- apsimo/goals/store.py +692 -0
- apsimo/governed_actions.py +1708 -0
- apsimo/harness_integration/__init__.py +45 -0
- apsimo/harness_integration/context.py +41 -0
- apsimo/harness_integration/skills.py +231 -0
- apsimo/identity/__init__.py +26 -0
- apsimo/identity/participants.py +181 -0
- apsimo/identity/resolver.py +329 -0
- apsimo/identity_bootstrap/__init__.py +5 -0
- apsimo/identity_bootstrap/builder.py +208 -0
- apsimo/identity_bootstrap/corpus.py +443 -0
- apsimo/identity_bootstrap/models.py +54 -0
- apsimo/identity_bootstrap/runner.py +353 -0
- apsimo/identity_bootstrap/seeders/__init__.py +25 -0
- apsimo/identity_bootstrap/seeders/briefings.py +109 -0
- apsimo/identity_bootstrap/seeders/chain.py +57 -0
- apsimo/identity_bootstrap/seeders/goals.py +128 -0
- apsimo/identity_bootstrap/seeders/memory.py +191 -0
- apsimo/identity_bootstrap/seeders/neo4j_cognition.py +79 -0
- apsimo/identity_bootstrap/seeders/relationship.py +152 -0
- apsimo/identity_bootstrap/seeders/sessions.py +67 -0
- apsimo/identity_bootstrap/seeders/skills.py +92 -0
- apsimo/identity_bootstrap/seeders/task_queue.py +72 -0
- apsimo/identity_bootstrap/seeders/world_model.py +143 -0
- apsimo/identity_bootstrap/self_query.py +92 -0
- apsimo/identity_bootstrap/self_reflection.py +155 -0
- apsimo/identity_bootstrap/skill.py +37 -0
- apsimo/identity_bootstrap/verifier.py +436 -0
- apsimo/initiatives/__init__.py +20 -0
- apsimo/initiatives/action_registry.py +454 -0
- apsimo/initiatives/approval_authority.py +2105 -0
- apsimo/initiatives/approval_policy.py +123 -0
- apsimo/initiatives/assignment.py +263 -0
- apsimo/initiatives/backup_evidence.py +100 -0
- apsimo/initiatives/context_freshness.py +103 -0
- apsimo/initiatives/models.py +318 -0
- apsimo/initiatives/native_work.py +270 -0
- apsimo/initiatives/standing_approvals.py +232 -0
- apsimo/initiatives/store.py +1081 -0
- apsimo/initiatives/temporal_followup.py +410 -0
- apsimo/intelligence/__init__.py +1 -0
- apsimo/intelligence/cognition/__init__.py +24 -0
- apsimo/intelligence/cognition/gap_detector.py +148 -0
- apsimo/intelligence/cognition/metalearner.py +547 -0
- apsimo/intelligence/cognition/metrics_collector.py +217 -0
- apsimo/intelligence/cognition/performance_index.py +299 -0
- apsimo/intelligence/cognition/registry.py +192 -0
- apsimo/intelligence/cognition/strategy_adjuster.py +222 -0
- apsimo/intelligence/cognition/types.py +16 -0
- apsimo/intelligence/components/__init__.py +66 -0
- apsimo/intelligence/components/anomaly_detector.py +413 -0
- apsimo/intelligence/components/initiative_engine.py +2643 -0
- apsimo/intelligence/components/preference_learner.py +521 -0
- apsimo/intelligence/components/research_orchestrator.py +358 -0
- apsimo/intelligence/components/self_directed_thinker.py +221 -0
- apsimo/intelligence/components/self_reflector.py +252 -0
- apsimo/intelligence/components/session_continuity.py +154 -0
- apsimo/intelligence/components/task_planner.py +320 -0
- apsimo/intelligence/components/tool_learner.py +217 -0
- apsimo/intelligence/graph/__init__.py +79 -0
- apsimo/intelligence/graph/client.py +2483 -0
- apsimo/intelligence/graph/consolidator.py +405 -0
- apsimo/intelligence/graph/distiller.py +312 -0
- apsimo/intelligence/graph/migrations.py +129 -0
- apsimo/intelligence/graph/queries.py +248 -0
- apsimo/intelligence/graph/recall.py +281 -0
- apsimo/intelligence/graph/reconciler.py +144 -0
- apsimo/intelligence/graph/schema.py +337 -0
- apsimo/intelligence/graph/selection.py +252 -0
- apsimo/intelligence/learning/__init__.py +17 -0
- apsimo/intelligence/learning/continuous_learner.py +245 -0
- apsimo/intelligence/learning/feedback_store.py +321 -0
- apsimo/intelligence/mind_model/__init__.py +1 -0
- apsimo/intelligence/mind_model/graph_baseline.py +136 -0
- apsimo/intelligence/mind_model/signal_collector.py +361 -0
- apsimo/intelligence/relationships/__init__.py +11 -0
- apsimo/intelligence/relationships/profiler.py +389 -0
- apsimo/intelligence/relationships/scorer.py +560 -0
- apsimo/intelligence/relationships/signal_floor.py +66 -0
- apsimo/intelligence/relationships/trust_tiers.py +300 -0
- apsimo/intelligence/synthesis/__init__.py +40 -0
- apsimo/intelligence/synthesis/connection_discoverer.py +379 -0
- apsimo/intelligence/synthesis/cross_domain_analyzer.py +287 -0
- apsimo/intelligence/synthesis/insight_deliverer.py +171 -0
- apsimo/intelligence/synthesis/insight_store.py +79 -0
- apsimo/intelligence/synthesis/insight_validator.py +183 -0
- apsimo/intelligence/synthesis/novelty_scorer.py +267 -0
- apsimo/intelligence/turn_middleware/__init__.py +15 -0
- apsimo/intelligence/turn_middleware/memory_sync.py +119 -0
- apsimo/mcp/__init__.py +41 -0
- apsimo/mcp/__main__.py +6 -0
- apsimo/mcp/config.py +287 -0
- apsimo/mcp/server.py +501 -0
- apsimo/migrations.py +187 -0
- apsimo/mining/__init__.py +27 -0
- apsimo/mining/corpus.py +239 -0
- apsimo/mining/escalations.py +289 -0
- apsimo/mining/models.py +169 -0
- apsimo/mining/store.py +210 -0
- apsimo/models/__init__.py +30 -0
- apsimo/models/memory.py +80 -0
- apsimo/models/mesh.py +72 -0
- apsimo/models/person.py +104 -0
- apsimo/models/signal.py +108 -0
- apsimo/observations/__init__.py +15 -0
- apsimo/observations/store.py +277 -0
- apsimo/patterns/__init__.py +6 -0
- apsimo/patterns/extract.py +187 -0
- apsimo/patterns/store.py +227 -0
- apsimo/persona/__init__.py +1 -0
- apsimo/persona/engine.py +611 -0
- apsimo/persona/manifest.py +140 -0
- apsimo/projects/__init__.py +28 -0
- apsimo/projects/engine.py +1681 -0
- apsimo/projects/event_outbox.py +188 -0
- apsimo/projects/models.py +216 -0
- apsimo/projects/planner.py +181 -0
- apsimo/projects/store.py +1446 -0
- apsimo/proposals/__init__.py +12 -0
- apsimo/proposals/engine.py +114 -0
- apsimo/proposals/models.py +207 -0
- apsimo/qualification/__init__.py +1 -0
- apsimo/qualification/cases.py +75 -0
- apsimo/qualification/cli.py +51 -0
- apsimo/qualification/memory_cases.py +209 -0
- apsimo/qualification/records.py +92 -0
- apsimo/qualification/report.py +87 -0
- apsimo/qualification/runner.py +311 -0
- apsimo/qualification/structured_cases.py +131 -0
- apsimo/reasoning/__init__.py +13 -0
- apsimo/reasoning/executor.py +506 -0
- apsimo/reasoning/loop.py +373 -0
- apsimo/reasoning/native_tools/__init__.py +16 -0
- apsimo/reasoning/native_tools/calculate.py +141 -0
- apsimo/reasoning/native_tools/file_ops.py +150 -0
- apsimo/reasoning/native_tools/web_search.py +49 -0
- apsimo/reasoning/tool_policy.py +182 -0
- apsimo/redact/__init__.py +176 -0
- apsimo/repos/__init__.py +5 -0
- apsimo/repos/mirrors.py +204 -0
- apsimo/research/__init__.py +41 -0
- apsimo/research/artifact.py +482 -0
- apsimo/research/gatherer.py +387 -0
- apsimo/research/pipeline.py +513 -0
- apsimo/research/search/__init__.py +7 -0
- apsimo/research/search/base.py +41 -0
- apsimo/research/search/brave.py +59 -0
- apsimo/research/search/cache.py +51 -0
- apsimo/research/search/duckduckgo.py +103 -0
- apsimo/research/search/orchestrator.py +119 -0
- apsimo/research/search/serpapi.py +59 -0
- apsimo/research/search/tavily.py +59 -0
- apsimo/research/synthesizer.py +309 -0
- apsimo/router/__init__.py +30 -0
- apsimo/router/complexity_scorer.py +148 -0
- apsimo/router/endpoints.py +153 -0
- apsimo/router/fallback.py +58 -0
- apsimo/router/functions.py +243 -0
- apsimo/router/native_policy.py +52 -0
- apsimo/router/router.py +762 -0
- apsimo/router/self_learning.py +174 -0
- apsimo/router/tiers.py +677 -0
- apsimo/sandbox/__init__.py +21 -0
- apsimo/sandbox/backend.py +195 -0
- apsimo/sandbox/manager.py +173 -0
- apsimo/scope_bounds.py +7 -0
- apsimo/secrets/__init__.py +6 -0
- apsimo/secrets/backends/__init__.py +8 -0
- apsimo/secrets/backends/base.py +42 -0
- apsimo/secrets/backends/env.py +110 -0
- apsimo/secrets/backends/keyring.py +72 -0
- apsimo/secrets/backends/onepassword.py +232 -0
- apsimo/secrets/cli.py +191 -0
- apsimo/secrets/manager.py +160 -0
- apsimo/secrets/migration.py +101 -0
- apsimo/secrets/types.py +98 -0
- apsimo/seed.py +41 -0
- apsimo/self_model/__init__.py +37 -0
- apsimo/self_model/appraisals.py +673 -0
- apsimo/self_model/benchmark.py +1314 -0
- apsimo/self_model/brief.py +40 -0
- apsimo/self_model/event_concerns.py +1128 -0
- apsimo/self_model/execution_forecasts.py +353 -0
- apsimo/self_model/expectations.py +1595 -0
- apsimo/self_model/experiments.py +1150 -0
- apsimo/self_model/journal.py +148 -0
- apsimo/self_model/judgments.py +705 -0
- apsimo/self_model/native_outcomes.py +55 -0
- apsimo/self_model/params.py +220 -0
- apsimo/self_model/perspective.py +246 -0
- apsimo/self_model/reconcile.py +183 -0
- apsimo/self_model/reply_forecasts.py +381 -0
- apsimo/self_model/runtime_forecasts.py +296 -0
- apsimo/self_model/runtime_models.py +67 -0
- apsimo/self_model/settlement.py +207 -0
- apsimo/self_model/situation.py +1731 -0
- apsimo/self_model/store.py +883 -0
- apsimo/self_model/supervised.py +137 -0
- apsimo/self_model/thinker.py +99 -0
- apsimo/self_model/trust.py +388 -0
- apsimo/self_model/workspace.py +2388 -0
- apsimo/server.py +4197 -0
- apsimo/services/__init__.py +1 -0
- apsimo/services/agent_bridge.py +474 -0
- apsimo/services/initiative_executor.py +914 -0
- apsimo/services/instance.py +297 -0
- apsimo/sessions/__init__.py +22 -0
- apsimo/sessions/config.py +13 -0
- apsimo/sessions/context_loader.py +88 -0
- apsimo/sessions/federation_session.py +75 -0
- apsimo/sessions/isolated_session.py +98 -0
- apsimo/sessions/reports.py +84 -0
- apsimo/sessions/store.py +148 -0
- apsimo/setup.py +2818 -0
- apsimo/setup_hermes.py +879 -0
- apsimo/setup_local_work.py +218 -0
- apsimo/setup_native_goals.py +134 -0
- apsimo/setup_native_reviews.py +115 -0
- apsimo/skills/__init__.py +10 -0
- apsimo/skills/base.py +108 -0
- apsimo/skills/budget.py +28 -0
- apsimo/skills/executor.py +493 -0
- apsimo/skills/executors/__init__.py +1 -0
- apsimo/skills/executors/behavioral_correction.py +75 -0
- apsimo/skills/executors/capability_gap.py +38 -0
- apsimo/skills/executors/data_quality.py +163 -0
- apsimo/skills/executors/knowledge_acquisition.py +41 -0
- apsimo/skills/executors/operational_hygiene.py +185 -0
- apsimo/skills/executors/subsystem_health.py +169 -0
- apsimo/skills/hermes_export.py +431 -0
- apsimo/skills/index.py +123 -0
- apsimo/skills/learning/__init__.py +21 -0
- apsimo/skills/learning/novelty_detector.py +206 -0
- apsimo/skills/learning/pattern_extractor.py +199 -0
- apsimo/skills/learning/triggers.py +159 -0
- apsimo/skills/loader.py +246 -0
- apsimo/skills/migrations/002_progressive_loading.sql +6 -0
- apsimo/skills/migrations/backfill_triggers.py +20 -0
- apsimo/skills/models.py +202 -0
- apsimo/skills/packager.py +128 -0
- apsimo/skills/protocols.py +70 -0
- apsimo/skills/registry.py +191 -0
- apsimo/skills/runtime.py +58 -0
- apsimo/skills/sandbox_runner.py +229 -0
- apsimo/skills/scheduler.py +129 -0
- apsimo/skills/schema.py +79 -0
- apsimo/skills/security/__init__.py +12 -0
- apsimo/skills/security/guards.py +53 -0
- apsimo/skills/security/scanner.py +223 -0
- apsimo/skills_memory/__init__.py +26 -0
- apsimo/skills_memory/distill.py +159 -0
- apsimo/skills_memory/models.py +85 -0
- apsimo/skills_memory/retrieve.py +62 -0
- apsimo/skills_memory/store.py +172 -0
- apsimo/surprise/__init__.py +6 -0
- apsimo/surprise/accumulation.py +57 -0
- apsimo/surprise/scorer.py +102 -0
- apsimo/surprise/store.py +203 -0
- apsimo/task_queue/__init__.py +69 -0
- apsimo/task_queue/action_receipts.py +148 -0
- apsimo/task_queue/approval_relay_canary.py +108 -0
- apsimo/task_queue/config.py +85 -0
- apsimo/task_queue/contract.py +361 -0
- apsimo/task_queue/events.py +130 -0
- apsimo/task_queue/governor.py +1031 -0
- apsimo/task_queue/handlers/__init__.py +16 -0
- apsimo/task_queue/handlers/base.py +37 -0
- apsimo/task_queue/handlers/inference.py +640 -0
- apsimo/task_queue/handlers/monitoring.py +116 -0
- apsimo/task_queue/handlers/registry.py +75 -0
- apsimo/task_queue/handlers/subtask_handler.py +173 -0
- apsimo/task_queue/handlers/system_maintenance.py +147 -0
- apsimo/task_queue/mesh_integration.py +111 -0
- apsimo/task_queue/models.py +317 -0
- apsimo/task_queue/queue_manager.py +8286 -0
- apsimo/task_queue/routing.py +287 -0
- apsimo/task_queue/scheduler.py +252 -0
- apsimo/task_queue/schema.sql +197 -0
- apsimo/task_queue/work_control.py +342 -0
- apsimo/task_queue/worker.py +993 -0
- apsimo/telemetry.py +145 -0
- apsimo/tom/__init__.py +6 -0
- apsimo/tom/affect.py +387 -0
- apsimo/tom/approvals.py +171 -0
- apsimo/tom/arcs.py +896 -0
- apsimo/tom/asymmetry.py +131 -0
- apsimo/tom/eligibility.py +248 -0
- apsimo/tom/engagement.py +214 -0
- apsimo/tom/exposure.py +214 -0
- apsimo/tom/extractor.py +306 -0
- apsimo/tom/fact_adapters.py +144 -0
- apsimo/tom/facts.py +326 -0
- apsimo/tom/integration.py +592 -0
- apsimo/tom/leveled.py +118 -0
- apsimo/tom/levels.py +247 -0
- apsimo/tom/recipient_audit.py +995 -0
- apsimo/tom/recipient_simulator.py +593 -0
- apsimo/tom/source_lineage.py +93 -0
- apsimo/tom/tom2.py +277 -0
- apsimo/tom/visibility.py +559 -0
- apsimo/tom/visibility_store.py +414 -0
- apsimo/tools/__init__.py +0 -0
- apsimo/tools/definitions.py +740 -0
- apsimo/tools/handlers.py +943 -0
- apsimo/toolsmith/__init__.py +26 -0
- apsimo/toolsmith/authority.py +166 -0
- apsimo/toolsmith/engine.py +559 -0
- apsimo/toolsmith/integrity.py +100 -0
- apsimo/toolsmith/miner.py +145 -0
- apsimo/toolsmith/policy.py +110 -0
- apsimo/toolsmith/registry.py +635 -0
- apsimo/turns/__init__.py +17 -0
- apsimo/turns/audio.py +134 -0
- apsimo/turns/documents.py +235 -0
- apsimo/turns/executions.py +486 -0
- apsimo/turns/hermes_history.py +245 -0
- apsimo/turns/hermes_kanban.py +268 -0
- apsimo/turns/hermes_work.py +96 -0
- apsimo/turns/idempotency.py +752 -0
- apsimo/turns/local_work.py +115 -0
- apsimo/turns/media.py +581 -0
- apsimo/turns/reported_workers.py +196 -0
- apsimo/turns/source_annotations.py +283 -0
- apsimo/turns/source_attribution.py +154 -0
- apsimo/turns/source_read.py +351 -0
- apsimo/turns/source_vectors.py +263 -0
- apsimo/turns/video.py +210 -0
- apsimo/util/autonomy_preset.py +220 -0
- apsimo/util/instance.py +92 -0
- apsimo/util/model_output.py +25 -0
- apsimo/util/quiet_hours.py +27 -0
- apsimo/util/session_safety.py +37 -0
- apsimo/util/temporal.py +343 -0
- apsimo/vector/__init__.py +75 -0
- apsimo/vector/backfill.py +171 -0
- apsimo/vector/caption.py +114 -0
- apsimo/vector/collections.py +51 -0
- apsimo/vector/config.py +102 -0
- apsimo/vector/embedder.py +670 -0
- apsimo/vector/image_preprocess.py +406 -0
- apsimo/vector/image_store.py +296 -0
- apsimo/vector/indexes.py +162 -0
- apsimo/vector/migrate.py +334 -0
- apsimo/vector/multimodal_provider.py +417 -0
- apsimo/vector/multimodal_types.py +87 -0
- apsimo/vector/openai_provider.py +119 -0
- apsimo/vector/query.py +49 -0
- apsimo/vector/reranker.py +565 -0
- apsimo/vector/safety_image.py +159 -0
- apsimo/vector/scanner.py +197 -0
- apsimo/vector/setup.py +289 -0
- apsimo/vector/store.py +533 -0
- apsimo/vector/tiers.py +263 -0
- apsimo/work_orders.py +925 -0
- apsimo/workers/__init__.py +21 -0
- apsimo/workers/agent_bridge.py +640 -0
- apsimo/workers/colony_worker.py +382 -0
- apsimo/workers/queue_worker.py +441 -0
- apsimo/workers/skills_sync.py +152 -0
- apsimo/world_model/__init__.py +71 -0
- apsimo/world_model/causal_maintenance.py +131 -0
- apsimo/world_model/causal_policy.py +43 -0
- apsimo/world_model/causal_query.py +125 -0
- apsimo/world_model/confidence.py +54 -0
- apsimo/world_model/config.py +64 -0
- apsimo/world_model/constants.py +97 -0
- apsimo/world_model/entities.py +145 -0
- apsimo/world_model/expectation_resolvers.py +177 -0
- apsimo/world_model/extraction/__init__.py +7 -0
- apsimo/world_model/extraction/base.py +62 -0
- apsimo/world_model/extraction/conversation_extractor.py +262 -0
- apsimo/world_model/extraction/detector.py +74 -0
- apsimo/world_model/extraction/document_extractor.py +78 -0
- apsimo/world_model/extraction/formats/__init__.py +24 -0
- apsimo/world_model/extraction/formats/csv_fmt.py +68 -0
- apsimo/world_model/extraction/formats/html_fmt.py +72 -0
- apsimo/world_model/extraction/formats/json_fmt.py +68 -0
- apsimo/world_model/extraction/formats/pdf.py +43 -0
- apsimo/world_model/extraction/formats/text.py +27 -0
- apsimo/world_model/extraction/llm_extractor.py +164 -0
- apsimo/world_model/extraction/pipeline.py +73 -0
- apsimo/world_model/integrations/__init__.py +5 -0
- apsimo/world_model/integrations/mind_model_bridge.py +115 -0
- apsimo/world_model/integrations/social_intel_bridge.py +120 -0
- apsimo/world_model/jobs/__init__.py +4 -0
- apsimo/world_model/jobs/extraction_job.py +168 -0
- apsimo/world_model/llm_extract.py +572 -0
- apsimo/world_model/neo4j/__init__.py +5 -0
- apsimo/world_model/neo4j/backend.py +654 -0
- apsimo/world_model/observations.py +155 -0
- apsimo/world_model/populator.py +307 -0
- apsimo/world_model/postgres/__init__.py +1 -0
- apsimo/world_model/postgres/backend.py +683 -0
- apsimo/world_model/relationships.py +25 -0
- apsimo/world_model/resolution/__init__.py +13 -0
- apsimo/world_model/resolution/entity_resolver.py +232 -0
- apsimo/world_model/resolution/merge_audit.py +16 -0
- apsimo/world_model/resolution/merge_workflow.py +117 -0
- apsimo/world_model/source_reports.py +121 -0
- apsimo/world_model/sqlite/__init__.py +4 -0
- apsimo/world_model/sqlite/backend.py +855 -0
- apsimo/world_model/sqlite/schema.sql +132 -0
- apsimo/world_model/store.py +545 -0
- apsimo-1.3.0.dist-info/METADATA +78 -0
- apsimo-1.3.0.dist-info/RECORD +614 -0
- apsimo-1.3.0.dist-info/WHEEL +5 -0
- apsimo-1.3.0.dist-info/entry_points.txt +11 -0
- apsimo-1.3.0.dist-info/licenses/LICENSE +21 -0
- apsimo-1.3.0.dist-info/top_level.txt +2 -0
- colony_sidecar/__init__.py +4 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""Crash-recoverable ProjectStore to host-journal projection.
|
|
2
|
+
|
|
3
|
+
The project ledger and host event journal are separate durable stores. A
|
|
4
|
+
ProjectStore transaction therefore stages immutable event data in its own
|
|
5
|
+
outbox first. This projector uses the journal's keyed append handshake, then
|
|
6
|
+
records the returned sequence in the project database. Replaying after a
|
|
7
|
+
crash can finish either side without creating a second host event.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import inspect
|
|
13
|
+
from typing import Any, Callable, Dict, Mapping, Optional
|
|
14
|
+
|
|
15
|
+
from apsimo.events.journal import event_record_request_digest
|
|
16
|
+
from apsimo.projects.store import ProjectStore
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
_RECEIPT_ACKNOWLEDGEMENT_FIELDS = frozenset({
|
|
20
|
+
"expected_seq",
|
|
21
|
+
"expected_event_id",
|
|
22
|
+
"expected_recorded_at",
|
|
23
|
+
"expected_request_digest",
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ProjectEventProjector:
|
|
28
|
+
"""Drain immutable project events into the canonical host journal."""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
store: ProjectStore,
|
|
33
|
+
*,
|
|
34
|
+
journal_projector: Optional[Callable[..., Optional[Mapping[str, Any]]]] = None,
|
|
35
|
+
journal_acknowledger: Optional[Callable[..., bool]] = None,
|
|
36
|
+
) -> None:
|
|
37
|
+
self.store = store
|
|
38
|
+
if journal_projector is None:
|
|
39
|
+
from apsimo.events.journal import append_event_record
|
|
40
|
+
|
|
41
|
+
journal_projector = append_event_record
|
|
42
|
+
if journal_acknowledger is None:
|
|
43
|
+
from apsimo.events.journal import acknowledge_event_record
|
|
44
|
+
|
|
45
|
+
journal_acknowledger = acknowledge_event_record
|
|
46
|
+
self._project = journal_projector
|
|
47
|
+
self._acknowledge = journal_acknowledger
|
|
48
|
+
self._acknowledge_with_receipt = self._supports_receipt_acknowledgement(
|
|
49
|
+
journal_acknowledger
|
|
50
|
+
)
|
|
51
|
+
if not self._acknowledge_with_receipt:
|
|
52
|
+
raise TypeError(
|
|
53
|
+
"project journal acknowledger must accept the exact projection "
|
|
54
|
+
"receipt fields"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def _supports_receipt_acknowledgement(callback: Callable[..., bool]) -> bool:
|
|
59
|
+
"""Require an acknowledger that validates the exact journal receipt."""
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
parameters = inspect.signature(callback).parameters
|
|
63
|
+
except (TypeError, ValueError):
|
|
64
|
+
return False
|
|
65
|
+
if any(
|
|
66
|
+
parameter.kind is inspect.Parameter.VAR_KEYWORD
|
|
67
|
+
for parameter in parameters.values()
|
|
68
|
+
):
|
|
69
|
+
return True
|
|
70
|
+
return _RECEIPT_ACKNOWLEDGEMENT_FIELDS.issubset(parameters)
|
|
71
|
+
|
|
72
|
+
def _validated_payload(self, row: Mapping[str, Any]) -> Dict[str, Any]:
|
|
73
|
+
payload_error = str(row.get("payload_error") or "")
|
|
74
|
+
payload = row.get("payload")
|
|
75
|
+
if payload_error:
|
|
76
|
+
raise ValueError(payload_error)
|
|
77
|
+
if not isinstance(payload, dict):
|
|
78
|
+
raise ValueError("project outbox payload is not an object")
|
|
79
|
+
expected = self.store.project_event_envelope_digest(
|
|
80
|
+
event_key=str(row.get("event_key") or ""),
|
|
81
|
+
event_type=str(row.get("event_type") or ""),
|
|
82
|
+
occurred_at=str(row.get("occurred_at") or ""),
|
|
83
|
+
payload=payload,
|
|
84
|
+
)
|
|
85
|
+
if expected != str(row.get("event_digest") or ""):
|
|
86
|
+
raise ValueError("project outbox envelope digest mismatch")
|
|
87
|
+
return payload
|
|
88
|
+
|
|
89
|
+
def _validated_projection_receipt(
|
|
90
|
+
self,
|
|
91
|
+
row: Mapping[str, Any],
|
|
92
|
+
payload: Mapping[str, Any],
|
|
93
|
+
) -> Dict[str, Any]:
|
|
94
|
+
if row.get("state") != "projected":
|
|
95
|
+
raise ValueError("project outbox event is not projected")
|
|
96
|
+
expected = self.store.project_event_projection_receipt_digest(
|
|
97
|
+
event_key=str(row.get("event_key") or ""),
|
|
98
|
+
event_type=str(row.get("event_type") or ""),
|
|
99
|
+
event_digest=str(row.get("event_digest") or ""),
|
|
100
|
+
occurred_at=str(row.get("occurred_at") or ""),
|
|
101
|
+
journal_seq=row.get("journal_seq"),
|
|
102
|
+
journal_event_id=str(row.get("journal_event_id") or ""),
|
|
103
|
+
journal_recorded_at=str(row.get("journal_recorded_at") or ""),
|
|
104
|
+
)
|
|
105
|
+
if str(row.get("projection_receipt_digest") or "") != expected:
|
|
106
|
+
raise ValueError("project outbox projection receipt digest mismatch")
|
|
107
|
+
return {
|
|
108
|
+
"expected_seq": row["journal_seq"],
|
|
109
|
+
"expected_event_id": str(row["journal_event_id"]),
|
|
110
|
+
"expected_recorded_at": str(row["journal_recorded_at"]),
|
|
111
|
+
"expected_request_digest": event_record_request_digest(
|
|
112
|
+
str(row["event_type"]),
|
|
113
|
+
dict(payload),
|
|
114
|
+
str(row["occurred_at"]),
|
|
115
|
+
),
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
def _acknowledge_row(self, row: Mapping[str, Any]) -> None:
|
|
119
|
+
key = str(row.get("event_key") or "")
|
|
120
|
+
payload = self._validated_payload(row)
|
|
121
|
+
receipt = self._validated_projection_receipt(row, payload)
|
|
122
|
+
acknowledged = self._acknowledge(key, **receipt)
|
|
123
|
+
if not acknowledged:
|
|
124
|
+
raise RuntimeError("journal event-key acknowledgement failed")
|
|
125
|
+
self.store.acknowledge_project_event(key)
|
|
126
|
+
|
|
127
|
+
def _cleanup_acknowledgements(self, *, limit: int) -> tuple[int, int]:
|
|
128
|
+
cleaned = 0
|
|
129
|
+
failed = 0
|
|
130
|
+
for row in self.store.unacknowledged_project_events(limit=limit):
|
|
131
|
+
key = str(row["event_key"])
|
|
132
|
+
try:
|
|
133
|
+
self._acknowledge_row(row)
|
|
134
|
+
cleaned += 1
|
|
135
|
+
except Exception as exc:
|
|
136
|
+
self.store.fail_project_event(key, str(exc))
|
|
137
|
+
failed += 1
|
|
138
|
+
return cleaned, failed
|
|
139
|
+
|
|
140
|
+
def run_once(self, *, limit: int = 100) -> Dict[str, Any]:
|
|
141
|
+
bound = max(1, min(500, int(limit)))
|
|
142
|
+
acknowledged, acknowledgement_failures = self._cleanup_acknowledgements(
|
|
143
|
+
limit=bound,
|
|
144
|
+
)
|
|
145
|
+
projected = 0
|
|
146
|
+
failed = 0
|
|
147
|
+
retained = 0
|
|
148
|
+
for row in self.store.pending_project_events(limit=bound):
|
|
149
|
+
key = str(row["event_key"])
|
|
150
|
+
try:
|
|
151
|
+
payload = self._validated_payload(row)
|
|
152
|
+
record = self._project(
|
|
153
|
+
str(row["event_type"]),
|
|
154
|
+
payload,
|
|
155
|
+
occurred_at=str(row["occurred_at"]),
|
|
156
|
+
event_key=key,
|
|
157
|
+
)
|
|
158
|
+
if not isinstance(record, Mapping):
|
|
159
|
+
raise RuntimeError("project event journal projection failed")
|
|
160
|
+
completed = self.store.complete_project_event(key, dict(record))
|
|
161
|
+
projected += 1
|
|
162
|
+
retained += int(bool(record.get("retained", True)))
|
|
163
|
+
except Exception as exc:
|
|
164
|
+
self.store.fail_project_event(key, str(exc))
|
|
165
|
+
failed += 1
|
|
166
|
+
continue
|
|
167
|
+
try:
|
|
168
|
+
self._acknowledge_row(completed)
|
|
169
|
+
acknowledged += 1
|
|
170
|
+
except Exception as exc:
|
|
171
|
+
self.store.fail_project_event(key, str(exc))
|
|
172
|
+
acknowledgement_failures += 1
|
|
173
|
+
status = self.store.project_event_outbox_status()
|
|
174
|
+
return {
|
|
175
|
+
"processed": projected + failed,
|
|
176
|
+
"projected": projected,
|
|
177
|
+
"failed": failed,
|
|
178
|
+
"retained": retained,
|
|
179
|
+
"acknowledged": acknowledged,
|
|
180
|
+
"acknowledgement_failures": acknowledgement_failures,
|
|
181
|
+
"outbox": status,
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
def status(self) -> Dict[str, Any]:
|
|
185
|
+
return self.store.project_event_outbox_status()
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
__all__ = ["ProjectEventProjector"]
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""Project + Step data model."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import time
|
|
8
|
+
import uuid
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
# Fixed step-action vocabulary. Each kind routes through an existing gated
|
|
13
|
+
# sub-path; anything outside this set is dropped at validation.
|
|
14
|
+
# analyze / research / internal -> sidecar reasoning turn (internal tools)
|
|
15
|
+
# directed -> DirectedActionService (dry_run/approval)
|
|
16
|
+
# deliver -> guarded proposal delivery
|
|
17
|
+
ACTION_KINDS = frozenset({"analyze", "research", "directed", "deliver", "internal"})
|
|
18
|
+
|
|
19
|
+
PROJECT_STATUSES = ("planning", "active", "blocked", "completed", "abandoned")
|
|
20
|
+
STEP_STATUSES = ("pending", "active", "done", "failed", "skipped")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def projects_mode() -> str:
|
|
24
|
+
from apsimo.util.autonomy_preset import resolve
|
|
25
|
+
return resolve("COLONY_PROJECTS_MODE", ("off", "shadow", "live"), "shadow")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def projects_max_steps() -> int:
|
|
29
|
+
try:
|
|
30
|
+
return max(1, min(50, int(os.environ.get("COLONY_PROJECTS_MAX_STEPS", "12"))))
|
|
31
|
+
except (TypeError, ValueError):
|
|
32
|
+
return 12
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def projects_max_replans() -> int:
|
|
36
|
+
try:
|
|
37
|
+
return max(0, int(os.environ.get("COLONY_PROJECTS_MAX_REPLANS", "3")))
|
|
38
|
+
except (TypeError, ValueError):
|
|
39
|
+
return 3
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def projects_review_secs() -> float:
|
|
43
|
+
try:
|
|
44
|
+
return max(30.0, float(os.environ.get("COLONY_PROJECTS_REVIEW_SECS", "900")))
|
|
45
|
+
except (TypeError, ValueError):
|
|
46
|
+
return 900.0
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class Project:
|
|
51
|
+
title: str
|
|
52
|
+
objective: str = ""
|
|
53
|
+
source: str = "owner" # owner | thinker | directive
|
|
54
|
+
status: str = "planning"
|
|
55
|
+
outcome: str = "pending" # lifecycle status != evidence outcome
|
|
56
|
+
entity_ids: List[str] = field(default_factory=list)
|
|
57
|
+
reason: str = "" # abandon/complete/blocked reason
|
|
58
|
+
replans: int = 0
|
|
59
|
+
next_review_at: float = 0.0 # 0 -> due immediately
|
|
60
|
+
# P3 autonomous-goal provenance. Legacy/owner projects leave these
|
|
61
|
+
# empty; cognition-spine projects carry every upstream ID and scope into
|
|
62
|
+
# WorkOrder.context_refs without copying private context text.
|
|
63
|
+
concern_id: str = ""
|
|
64
|
+
source_event_refs: List[str] = field(default_factory=list)
|
|
65
|
+
thought_job_id: str = ""
|
|
66
|
+
thought_result_ref: str = ""
|
|
67
|
+
goal_proposal_id: str = ""
|
|
68
|
+
evidence_refs: List[str] = field(default_factory=list)
|
|
69
|
+
policy_decision_refs: List[str] = field(default_factory=list)
|
|
70
|
+
subject_person_id: str = ""
|
|
71
|
+
viewer_scope: str = "owner"
|
|
72
|
+
shareability: str = "owner_private"
|
|
73
|
+
capability_allowlist: List[str] = field(default_factory=list)
|
|
74
|
+
goal_fingerprint: str = ""
|
|
75
|
+
id: str = field(default_factory=lambda: f"proj-{uuid.uuid4().hex[:12]}")
|
|
76
|
+
created_at: float = field(default_factory=time.time)
|
|
77
|
+
updated_at: float = field(default_factory=time.time)
|
|
78
|
+
|
|
79
|
+
def subject_text(self) -> str:
|
|
80
|
+
"""Boundary-checkable subject for this project."""
|
|
81
|
+
return f"{self.title} {self.objective}"
|
|
82
|
+
|
|
83
|
+
def provenance_refs(self) -> List[str]:
|
|
84
|
+
"""Return bounded reference-only lineage for a WorkOrder."""
|
|
85
|
+
|
|
86
|
+
refs: List[str] = []
|
|
87
|
+
if self.concern_id:
|
|
88
|
+
refs.append(f"concern:{self.concern_id}")
|
|
89
|
+
refs.extend(self.source_event_refs)
|
|
90
|
+
if self.thought_job_id:
|
|
91
|
+
refs.append(f"thought-job:{self.thought_job_id}")
|
|
92
|
+
if self.thought_result_ref:
|
|
93
|
+
refs.append(self.thought_result_ref)
|
|
94
|
+
if self.goal_proposal_id:
|
|
95
|
+
refs.append(self.goal_proposal_id)
|
|
96
|
+
refs.extend(self.evidence_refs)
|
|
97
|
+
refs.extend(self.policy_decision_refs)
|
|
98
|
+
return list(dict.fromkeys(ref for ref in refs if ref))[:60]
|
|
99
|
+
|
|
100
|
+
def to_row(self) -> Dict[str, Any]:
|
|
101
|
+
return {
|
|
102
|
+
"id": self.id, "title": self.title, "objective": self.objective,
|
|
103
|
+
"source": self.source, "status": self.status,
|
|
104
|
+
"outcome": self.outcome,
|
|
105
|
+
"entity_ids": json.dumps(self.entity_ids), "reason": self.reason,
|
|
106
|
+
"replans": self.replans, "next_review_at": self.next_review_at,
|
|
107
|
+
"concern_id": self.concern_id,
|
|
108
|
+
"source_event_refs": json.dumps(self.source_event_refs),
|
|
109
|
+
"thought_job_id": self.thought_job_id,
|
|
110
|
+
"thought_result_ref": self.thought_result_ref,
|
|
111
|
+
"goal_proposal_id": self.goal_proposal_id,
|
|
112
|
+
"evidence_refs": json.dumps(self.evidence_refs),
|
|
113
|
+
"policy_decision_refs": json.dumps(self.policy_decision_refs),
|
|
114
|
+
"subject_person_id": self.subject_person_id,
|
|
115
|
+
"viewer_scope": self.viewer_scope,
|
|
116
|
+
"shareability": self.shareability,
|
|
117
|
+
"capability_allowlist": json.dumps(self.capability_allowlist),
|
|
118
|
+
"goal_fingerprint": self.goal_fingerprint,
|
|
119
|
+
"created_at": self.created_at, "updated_at": self.updated_at,
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@classmethod
|
|
123
|
+
def from_row(cls, r: Dict[str, Any]) -> "Project":
|
|
124
|
+
def _list(name: str) -> List[str]:
|
|
125
|
+
try:
|
|
126
|
+
value = json.loads(r.get(name) or "[]")
|
|
127
|
+
return [str(item) for item in value] if isinstance(value, list) else []
|
|
128
|
+
except Exception:
|
|
129
|
+
return []
|
|
130
|
+
|
|
131
|
+
return cls(
|
|
132
|
+
id=r["id"], title=r.get("title", "") or "",
|
|
133
|
+
objective=r.get("objective", "") or "",
|
|
134
|
+
source=r.get("source", "owner") or "owner",
|
|
135
|
+
status=r.get("status", "planning") or "planning",
|
|
136
|
+
outcome=r.get("outcome", "pending") or "pending",
|
|
137
|
+
entity_ids=_list("entity_ids"),
|
|
138
|
+
reason=r.get("reason", "") or "",
|
|
139
|
+
replans=int(r.get("replans") or 0),
|
|
140
|
+
next_review_at=float(r.get("next_review_at") or 0.0),
|
|
141
|
+
concern_id=r.get("concern_id", "") or "",
|
|
142
|
+
source_event_refs=_list("source_event_refs"),
|
|
143
|
+
thought_job_id=r.get("thought_job_id", "") or "",
|
|
144
|
+
thought_result_ref=r.get("thought_result_ref", "") or "",
|
|
145
|
+
goal_proposal_id=r.get("goal_proposal_id", "") or "",
|
|
146
|
+
evidence_refs=_list("evidence_refs"),
|
|
147
|
+
policy_decision_refs=_list("policy_decision_refs"),
|
|
148
|
+
subject_person_id=r.get("subject_person_id", "") or "",
|
|
149
|
+
viewer_scope=r.get("viewer_scope", "owner") or "owner",
|
|
150
|
+
shareability=r.get("shareability", "owner_private") or "owner_private",
|
|
151
|
+
capability_allowlist=_list("capability_allowlist"),
|
|
152
|
+
goal_fingerprint=r.get("goal_fingerprint", "") or "",
|
|
153
|
+
created_at=float(r.get("created_at") or time.time()),
|
|
154
|
+
updated_at=float(r.get("updated_at") or time.time()),
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@dataclass
|
|
159
|
+
class Step:
|
|
160
|
+
project_id: str
|
|
161
|
+
ordinal: int
|
|
162
|
+
description: str
|
|
163
|
+
action_kind: str = "analyze"
|
|
164
|
+
depends_on: List[int] = field(default_factory=list) # ordinals within project
|
|
165
|
+
status: str = "pending"
|
|
166
|
+
attempts: int = 0
|
|
167
|
+
result: str = ""
|
|
168
|
+
work_order_ref: str = "" # immutable project-ledger WorkOrder row
|
|
169
|
+
work_order_digest: str = "" # exact authority envelope digest
|
|
170
|
+
work_order_issued_at: float = 0.0 # stable first-dispatch authority time
|
|
171
|
+
result_ref: str = "" # logical ExecutionResultV1 ledger row
|
|
172
|
+
boundary_subject: str = "" # extra subject text for the guard
|
|
173
|
+
confidence: float = 0.6 # planner-stated (charter contract)
|
|
174
|
+
id: str = field(default_factory=lambda: f"step-{uuid.uuid4().hex[:12]}")
|
|
175
|
+
created_at: float = field(default_factory=time.time)
|
|
176
|
+
updated_at: float = field(default_factory=time.time)
|
|
177
|
+
|
|
178
|
+
def to_row(self) -> Dict[str, Any]:
|
|
179
|
+
return {
|
|
180
|
+
"id": self.id, "project_id": self.project_id, "ordinal": self.ordinal,
|
|
181
|
+
"description": self.description, "action_kind": self.action_kind,
|
|
182
|
+
"depends_on": json.dumps(self.depends_on), "status": self.status,
|
|
183
|
+
"attempts": self.attempts, "result": self.result,
|
|
184
|
+
"work_order_ref": self.work_order_ref,
|
|
185
|
+
"work_order_digest": self.work_order_digest,
|
|
186
|
+
"work_order_issued_at": self.work_order_issued_at,
|
|
187
|
+
"result_ref": self.result_ref,
|
|
188
|
+
"boundary_subject": self.boundary_subject,
|
|
189
|
+
"confidence": self.confidence,
|
|
190
|
+
"created_at": self.created_at, "updated_at": self.updated_at,
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
@classmethod
|
|
194
|
+
def from_row(cls, r: Dict[str, Any]) -> "Step":
|
|
195
|
+
try:
|
|
196
|
+
deps = json.loads(r.get("depends_on") or "[]")
|
|
197
|
+
deps = [int(d) for d in deps] if isinstance(deps, list) else []
|
|
198
|
+
except Exception:
|
|
199
|
+
deps = []
|
|
200
|
+
return cls(
|
|
201
|
+
id=r["id"], project_id=r.get("project_id", "") or "",
|
|
202
|
+
ordinal=int(r.get("ordinal") or 0),
|
|
203
|
+
description=r.get("description", "") or "",
|
|
204
|
+
action_kind=r.get("action_kind", "analyze") or "analyze",
|
|
205
|
+
depends_on=deps, status=r.get("status", "pending") or "pending",
|
|
206
|
+
attempts=int(r.get("attempts") or 0),
|
|
207
|
+
result=r.get("result", "") or "",
|
|
208
|
+
work_order_ref=r.get("work_order_ref", "") or "",
|
|
209
|
+
work_order_digest=r.get("work_order_digest", "") or "",
|
|
210
|
+
work_order_issued_at=float(r.get("work_order_issued_at") or 0.0),
|
|
211
|
+
result_ref=r.get("result_ref", "") or "",
|
|
212
|
+
boundary_subject=r.get("boundary_subject", "") or "",
|
|
213
|
+
confidence=float(r.get("confidence", 0.6) or 0.6),
|
|
214
|
+
created_at=float(r.get("created_at") or time.time()),
|
|
215
|
+
updated_at=float(r.get("updated_at") or time.time()),
|
|
216
|
+
)
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""Project planner: objective -> validated, dependency-ordered Steps.
|
|
2
|
+
|
|
3
|
+
One LLM planning pass returns a STRICT JSON step list; deterministic code
|
|
4
|
+
re-validates everything (same discipline as directed intake): unknown
|
|
5
|
+
action_kinds are dropped, dependency cycles are broken, unknown/self deps are
|
|
6
|
+
stripped, and the step count is capped. The LLM proposes; code decides.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import logging
|
|
13
|
+
import re
|
|
14
|
+
from typing import Any, Dict, List, Optional
|
|
15
|
+
|
|
16
|
+
from apsimo.projects.models import ACTION_KINDS, Step, projects_max_steps
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
# The system prompt composes through the shared cognition charter
|
|
21
|
+
# (cognition/charter.py, role "planner"): shared doctrine + planner rules +
|
|
22
|
+
# injected self-model/boundaries/skills. This block only supplies the
|
|
23
|
+
# action-kind vocabulary the role rules reference.
|
|
24
|
+
_ACTION_KINDS_CONTEXT = """\
|
|
25
|
+
Allowed action kinds (choose the weakest kind that does the job):
|
|
26
|
+
- "analyze": reason over information the system already has (memory, world
|
|
27
|
+
model, configured read-only repo mirrors).
|
|
28
|
+
- "research": gather new information (web search, background research).
|
|
29
|
+
- "internal": internal bookkeeping (record findings, update memory).
|
|
30
|
+
- "directed": delegate scoped repo/code work to the directed-action pipeline
|
|
31
|
+
(it has its own approval gates). Use ONLY for work on the owner's
|
|
32
|
+
designated repositories.
|
|
33
|
+
- "deliver": report a finding/milestone to the owner."""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _parse_array(content: str) -> List[dict]:
|
|
37
|
+
text = (content or "").strip()
|
|
38
|
+
if "```" in text:
|
|
39
|
+
m = re.search(r"```(?:json)?\s*(.*?)```", text, re.DOTALL)
|
|
40
|
+
if m:
|
|
41
|
+
text = m.group(1).strip()
|
|
42
|
+
if not text.startswith("["):
|
|
43
|
+
m = re.search(r"\[.*\]", text, re.DOTALL)
|
|
44
|
+
if not m:
|
|
45
|
+
return []
|
|
46
|
+
text = m.group(0)
|
|
47
|
+
try:
|
|
48
|
+
data = json.loads(text)
|
|
49
|
+
except json.JSONDecodeError:
|
|
50
|
+
logger.warning("project planner output was not valid JSON; dropping")
|
|
51
|
+
return []
|
|
52
|
+
return [d for d in data if isinstance(d, dict)] if isinstance(data, list) else []
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def validate_steps(raw: List[dict], project_id: str = "",
|
|
56
|
+
max_steps: Optional[int] = None) -> List[Step]:
|
|
57
|
+
"""Deterministic re-validation of an LLM-proposed step list.
|
|
58
|
+
|
|
59
|
+
Drops steps with unknown action_kind, strips unknown/self dependencies,
|
|
60
|
+
breaks dependency cycles (a dep that is not strictly ordinal-decreasing
|
|
61
|
+
after topological sorting is dropped), caps the count, and renumbers
|
|
62
|
+
ordinals contiguously.
|
|
63
|
+
"""
|
|
64
|
+
cap = max_steps or projects_max_steps()
|
|
65
|
+
|
|
66
|
+
# 1. Shape + kind filter.
|
|
67
|
+
cleaned: List[Dict[str, Any]] = []
|
|
68
|
+
for d in raw or []:
|
|
69
|
+
desc = str(d.get("description", "")).strip()
|
|
70
|
+
kind = str(d.get("action_kind", "")).strip().lower()
|
|
71
|
+
if not desc or kind not in ACTION_KINDS:
|
|
72
|
+
continue
|
|
73
|
+
try:
|
|
74
|
+
ordinal = int(d.get("ordinal", len(cleaned) + 1))
|
|
75
|
+
except (TypeError, ValueError):
|
|
76
|
+
ordinal = len(cleaned) + 1
|
|
77
|
+
deps = []
|
|
78
|
+
for dep in (d.get("depends_on") or []):
|
|
79
|
+
try:
|
|
80
|
+
deps.append(int(dep))
|
|
81
|
+
except (TypeError, ValueError):
|
|
82
|
+
continue
|
|
83
|
+
try:
|
|
84
|
+
confidence = max(0.0, min(1.0, float(d.get("confidence", 0.6))))
|
|
85
|
+
except (TypeError, ValueError):
|
|
86
|
+
confidence = 0.6
|
|
87
|
+
cleaned.append({"ordinal": ordinal, "description": desc[:500],
|
|
88
|
+
"action_kind": kind, "depends_on": deps,
|
|
89
|
+
"confidence": confidence})
|
|
90
|
+
|
|
91
|
+
if not cleaned:
|
|
92
|
+
return []
|
|
93
|
+
|
|
94
|
+
# 2. Deduplicate ordinals (keep first) and drop unknown/self deps.
|
|
95
|
+
by_ord: Dict[int, Dict[str, Any]] = {}
|
|
96
|
+
for d in cleaned:
|
|
97
|
+
if d["ordinal"] not in by_ord:
|
|
98
|
+
by_ord[d["ordinal"]] = d
|
|
99
|
+
known = set(by_ord)
|
|
100
|
+
for d in by_ord.values():
|
|
101
|
+
d["depends_on"] = [x for x in d["depends_on"]
|
|
102
|
+
if x in known and x != d["ordinal"]]
|
|
103
|
+
|
|
104
|
+
# 3. Topological sort (Kahn); cycle-breaking: when no node is free, cut
|
|
105
|
+
# the remaining node with the fewest deps loose (drop its deps).
|
|
106
|
+
remaining = dict(by_ord)
|
|
107
|
+
ordered: List[Dict[str, Any]] = []
|
|
108
|
+
placed: set = set()
|
|
109
|
+
while remaining:
|
|
110
|
+
free = [o for o, d in remaining.items()
|
|
111
|
+
if all(dep in placed for dep in d["depends_on"])]
|
|
112
|
+
if not free:
|
|
113
|
+
victim = min(remaining.values(),
|
|
114
|
+
key=lambda d: (len(d["depends_on"]), d["ordinal"]))
|
|
115
|
+
logger.info("project planner: breaking dependency cycle at step %s",
|
|
116
|
+
victim["ordinal"])
|
|
117
|
+
victim["depends_on"] = [dep for dep in victim["depends_on"]
|
|
118
|
+
if dep in placed]
|
|
119
|
+
free = [victim["ordinal"]]
|
|
120
|
+
for o in sorted(free):
|
|
121
|
+
ordered.append(remaining.pop(o))
|
|
122
|
+
placed.add(o)
|
|
123
|
+
|
|
124
|
+
# 4. Cap, then drop deps pointing at capped-away steps.
|
|
125
|
+
ordered = ordered[:cap]
|
|
126
|
+
kept = {d["ordinal"] for d in ordered}
|
|
127
|
+
for d in ordered:
|
|
128
|
+
d["depends_on"] = [x for x in d["depends_on"] if x in kept]
|
|
129
|
+
|
|
130
|
+
# 5. Renumber contiguously (deps remapped).
|
|
131
|
+
remap = {d["ordinal"]: i + 1 for i, d in enumerate(ordered)}
|
|
132
|
+
steps: List[Step] = []
|
|
133
|
+
for d in ordered:
|
|
134
|
+
steps.append(Step(
|
|
135
|
+
project_id=project_id,
|
|
136
|
+
ordinal=remap[d["ordinal"]],
|
|
137
|
+
description=d["description"],
|
|
138
|
+
action_kind=d["action_kind"],
|
|
139
|
+
depends_on=sorted(remap[x] for x in d["depends_on"]),
|
|
140
|
+
confidence=d.get("confidence", 0.6),
|
|
141
|
+
))
|
|
142
|
+
return steps
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
async def plan_project(
|
|
146
|
+
router: Any,
|
|
147
|
+
objective: str,
|
|
148
|
+
*,
|
|
149
|
+
project_id: str = "",
|
|
150
|
+
context: str = "",
|
|
151
|
+
skills_block: str = "",
|
|
152
|
+
self_brief: str = "",
|
|
153
|
+
boundaries: str = "",
|
|
154
|
+
max_steps: Optional[int] = None,
|
|
155
|
+
) -> List[Step]:
|
|
156
|
+
"""One LLM planning pass -> validated Steps. Empty list on any failure."""
|
|
157
|
+
if router is None or not (objective or "").strip():
|
|
158
|
+
return []
|
|
159
|
+
cap = max_steps or projects_max_steps()
|
|
160
|
+
user_parts = [f"OBJECTIVE:\n{objective.strip()[:1500]}"]
|
|
161
|
+
if context:
|
|
162
|
+
user_parts.append(f"CONTEXT:\n{str(context)[:2000]}")
|
|
163
|
+
try:
|
|
164
|
+
from apsimo.cognition.charter import build_system_prompt
|
|
165
|
+
system = build_system_prompt(
|
|
166
|
+
"planner",
|
|
167
|
+
self_brief=(self_brief or None),
|
|
168
|
+
boundaries=(boundaries or None),
|
|
169
|
+
skills=(skills_block or None),
|
|
170
|
+
extra=_ACTION_KINDS_CONTEXT,
|
|
171
|
+
max_steps=cap)
|
|
172
|
+
response = await router.complete(
|
|
173
|
+
[{"role": "system", "content": system},
|
|
174
|
+
{"role": "user", "content": "\n\n".join(user_parts)}],
|
|
175
|
+
context={"task": "project_planning"})
|
|
176
|
+
content = getattr(response, "content", "") or ""
|
|
177
|
+
except Exception as exc:
|
|
178
|
+
logger.warning("project planning LLM call failed: %s", exc)
|
|
179
|
+
return []
|
|
180
|
+
return validate_steps(_parse_array(content), project_id=project_id,
|
|
181
|
+
max_steps=cap)
|