ltcai 11.5.2 → 11.6.0
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.
- package/README.md +91 -148
- package/bin/ltcai.js +234 -24
- package/docs/CHANGELOG.md +75 -0
- package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
- package/docs/DEVELOPMENT.md +8 -6
- package/docs/ENTERPRISE.md +2 -1
- package/docs/MULTI_AGENT_RUNTIME.md +12 -5
- package/docs/ONBOARDING.md +1 -1
- package/docs/OPERATIONS.md +6 -3
- package/docs/REALTIME_COLLABORATION.md +1 -1
- package/docs/TRUST_MODEL.md +1 -1
- package/docs/WHY_LATTICE.md +1 -1
- package/docs/WORKFLOW_DESIGNER.md +3 -2
- package/docs/kg-schema.md +1 -1
- package/docs/v11.6.0_ONE_DOOR_PLAN.md +170 -0
- package/lattice_brain/__init__.py +42 -79
- package/lattice_brain/graph/__init__.py +9 -24
- package/lattice_brain/graph/_kg_common/__init__.py +13 -24
- package/lattice_brain/ingestion/__init__.py +19 -58
- package/lattice_brain/ingestion/pipeline.py +20 -398
- package/lattice_brain/multimodal/__init__.py +7 -18
- package/lattice_brain/multimodal/images.py +6 -264
- package/lattice_brain/multimodal/video.py +8 -247
- package/lattice_brain/runtime/__init__.py +8 -79
- package/lattice_brain/runtime/hooks.py +22 -584
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/agent_worker_seam.py +7 -80
- package/latticeai/api/health.py +6 -20
- package/latticeai/api/local_files.py +16 -613
- package/latticeai/api/models.py +12 -87
- package/latticeai/api/search.py +17 -255
- package/latticeai/api/tools.py +69 -750
- package/latticeai/api/voice_capture.py +8 -70
- package/latticeai/api/worker_compute.py +842 -0
- package/latticeai/api/worker_seams.py +216 -0
- package/latticeai/app_factory.py +23 -232
- package/latticeai/cli/entrypoint.py +36 -249
- package/latticeai/core/agent_permission.py +16 -91
- package/latticeai/core/messages.py +89 -531
- package/latticeai/runtime/access_runtime.py +0 -14
- package/latticeai/runtime/bootstrap.py +10 -21
- package/latticeai/runtime/brain_runtime.py +35 -43
- package/latticeai/runtime/build_phases/__init__.py +21 -37
- package/latticeai/runtime/build_phases/features.py +99 -389
- package/latticeai/runtime/build_phases/foundation.py +82 -392
- package/latticeai/runtime/build_phases/web.py +91 -408
- package/latticeai/runtime/build_phases/worker_profile.py +244 -0
- package/latticeai/runtime/lifespan_runtime.py +7 -16
- package/latticeai/runtime/platform_services_runtime.py +1 -15
- package/latticeai/runtime/runtime_context.py +13 -130
- package/latticeai/runtime/security_runtime.py +10 -103
- package/latticeai/services/architecture_readiness.py +82 -43
- package/latticeai/services/model_runtime/__init__.py +2 -11
- package/latticeai/services/model_runtime/service.py +4 -28
- package/latticeai/services/p_reinforce.py +10 -261
- package/latticeai/services/product_readiness.py +31 -38
- package/latticeai/services/search_service.py +37 -795
- package/latticeai/services/tool_dispatch.py +30 -386
- package/latticeai/services/voice_capture.py +13 -107
- package/latticeai/tools/__init__.py +22 -49
- package/latticeai/tools/commands.py +0 -163
- package/latticeai/tools/computer.py +0 -39
- package/latticeai/tools/documents.py +1 -134
- package/latticeai/tools/filesystem.py +1 -247
- package/latticeai/tools/knowledge.py +1 -52
- package/latticeai/tools/local_files.py +0 -20
- package/latticeai/worker_app.py +75 -0
- package/package.json +2 -3
- package/requirements.txt +0 -5
- package/scripts/agent_eval.py +16 -28
- package/scripts/brain_quality_eval.py +20 -183
- package/scripts/bump_version.py +5 -5
- package/scripts/check_current_release_docs.mjs +7 -4
- package/scripts/check_openapi_drift.mjs +10 -0
- package/scripts/check_server_i18n.mjs +2 -18
- package/scripts/compose_openapi.py +377 -0
- package/scripts/export_openapi.py +32 -4
- package/scripts/gen_messages_catalog_fixture.py +302 -0
- package/scripts/gen_openapi_fragments.py +365 -0
- package/scripts/gen_redact_fixture.py +196 -0
- package/scripts/gen_worker_allowlist_fixture.py +115 -0
- package/scripts/generate_agent_parity_fixtures.py +33 -14
- package/scripts/openapi_route_families.json +2161 -0
- package/scripts/release_screen_claims.json +21 -0
- package/scripts/run_integration_tests.mjs +134 -29
- package/scripts/run_sidecar_e2e.mjs +104 -11
- package/scripts/wheel_smoke.py +32 -22
- package/src-tauri/Cargo.lock +375 -9
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/src/backend.rs +151 -45
- package/src-tauri/src/main.rs +18 -18
- package/src-tauri/src/topology.rs +58 -88
- package/src-tauri/tauri.conf.json +1 -2
- package/static/app/asset-manifest.json +41 -41
- package/static/app/assets/{Act-DcQizkl1.js → Act-CToZnOHz.js} +1 -1
- package/static/app/assets/{AdminConsole-cf4npybT.js → AdminConsole-D5kbrBu8.js} +1 -1
- package/static/app/assets/{Brain-3VCSHFcn.js → Brain-C6zCdv4S.js} +1 -1
- package/static/app/assets/{BrainHome-Qm8eaztx.js → BrainHome-D4n3EDUt.js} +1 -1
- package/static/app/assets/{BrainSignals-DS9BtKOW.js → BrainSignals-CzeZ-vQ9.js} +1 -1
- package/static/app/assets/{Capture-DiQ219jW.js → Capture-DPcyTczq.js} +1 -1
- package/static/app/assets/{Chronicle-BGvuAchH.js → Chronicle-D3Nx-oWS.js} +1 -1
- package/static/app/assets/{CommandPalette-Bqhm0Urn.js → CommandPalette-DPPvpp28.js} +1 -1
- package/static/app/assets/{Library-BV6NnF0a.js → Library-Co2qTu3I.js} +1 -1
- package/static/app/assets/{LivingBrain-GzenJchP.js → LivingBrain-Bsmzwy8_.js} +1 -1
- package/static/app/assets/{ProductFlow-DEP6-vML.js → ProductFlow-CBLdmesT.js} +1 -1
- package/static/app/assets/{ReviewCard-CNZ7XjWG.js → ReviewCard-CfR6DLs7.js} +1 -1
- package/static/app/assets/{System-CieofHQa.js → System-Ce-P2YL6.js} +1 -1
- package/static/app/assets/arrow-left-DD5jFGHV.js +1 -0
- package/static/app/assets/{bot-B_K1Tdmw.js → bot-C5-XIww2.js} +1 -1
- package/static/app/assets/{brain-DWyaV1L1.js → brain-CFRJUvdX.js} +1 -1
- package/static/app/assets/{button-aTn4s84A.js → button-DI1KcQP-.js} +1 -1
- package/static/app/assets/circle-check-CY20XMCh.js +1 -0
- package/static/app/assets/{circle-pause-xKgeGXkT.js → circle-pause-C8HHyood.js} +1 -1
- package/static/app/assets/{circle-play-DkT6tYPX.js → circle-play-Cufc235E.js} +1 -1
- package/static/app/assets/{cpu-85xYObUC.js → cpu-DW2_JVsv.js} +1 -1
- package/static/app/assets/{download-B5Fm7YXo.js → download-gbdKTBb8.js} +1 -1
- package/static/app/assets/{folder-open-kk2Xa52u.js → folder-open-C9ydyX33.js} +1 -1
- package/static/app/assets/{hard-drive-DkA3zBW_.js → hard-drive-BxNMzBM3.js} +1 -1
- package/static/app/assets/index-BovSWRmQ.css +2 -0
- package/static/app/assets/{index-BMPdTmlY.js → index-q9XyDY7A.js} +3 -3
- package/static/app/assets/{input-B0nRf2jO.js → input-GLD94Y_K.js} +1 -1
- package/static/app/assets/{link-2-Dwb4gnTc.js → link-2-B4Y0gz1S.js} +1 -1
- package/static/app/assets/{permissionCopy-CQDUBrOZ.js → permissionCopy-CxrV0fvU.js} +1 -1
- package/static/app/assets/{primitives-SNp0LRJz.js → primitives-2ANdAsu2.js} +1 -1
- package/static/app/assets/search-DTxtjQ07.js +1 -0
- package/static/app/assets/{share-2-BsrxFglO.js → share-2-DxArfiuI.js} +1 -1
- package/static/app/assets/{shield-alert-5BStfp2_.js → shield-alert-CgftuFFs.js} +1 -1
- package/static/app/assets/{textarea-Cg8IUA-k.js → textarea-BHVBAoCr.js} +1 -1
- package/static/app/assets/{useFocusTrap-CYKvE46M.js → useFocusTrap-Dzq2j_OG.js} +1 -1
- package/static/app/assets/{useMutation-CSn9t1op.js → useMutation-BcqfMLNq.js} +1 -1
- package/static/app/assets/{useQuery-CY2OI2uy.js → useQuery-DBzLKCOv.js} +1 -1
- package/static/app/assets/{utils-Ddol2RWD.js → utils-B-Aah1bO.js} +1 -1
- package/static/app/assets/{workspace-BqDwOz_p.js → workspace-B6fFhoko.js} +1 -1
- package/static/app/index.html +4 -4
- package/static/sw.js +1 -1
- package/lattice_brain/archive.py +0 -522
- package/lattice_brain/context.py +0 -325
- package/lattice_brain/conversations.py +0 -382
- package/lattice_brain/core.py +0 -82
- package/lattice_brain/graph/_kg_contract.py +0 -249
- package/lattice_brain/graph/curator.py +0 -676
- package/lattice_brain/graph/discovery.py +0 -595
- package/lattice_brain/graph/discovery_index/__init__.py +0 -35
- package/lattice_brain/graph/discovery_index/cleanup.py +0 -182
- package/lattice_brain/graph/discovery_index/extract.py +0 -137
- package/lattice_brain/graph/discovery_index/scan.py +0 -411
- package/lattice_brain/graph/discovery_index/upsert.py +0 -495
- package/lattice_brain/graph/documents.py +0 -380
- package/lattice_brain/graph/fusion.py +0 -395
- package/lattice_brain/graph/identity.py +0 -175
- package/lattice_brain/graph/image_vectors.py +0 -230
- package/lattice_brain/graph/ingest.py +0 -829
- package/lattice_brain/graph/network.py +0 -205
- package/lattice_brain/graph/proactive.py +0 -724
- package/lattice_brain/graph/projection/__init__.py +0 -42
- package/lattice_brain/graph/projection/curation.py +0 -500
- package/lattice_brain/graph/projection/v2_schema.py +0 -518
- package/lattice_brain/graph/provenance.py +0 -524
- package/lattice_brain/graph/rerank.py +0 -163
- package/lattice_brain/graph/retrieval/__init__.py +0 -54
- package/lattice_brain/graph/retrieval/context.py +0 -197
- package/lattice_brain/graph/retrieval/graph_view.py +0 -319
- package/lattice_brain/graph/retrieval/hybrid.py +0 -488
- package/lattice_brain/graph/retrieval/maintenance.py +0 -121
- package/lattice_brain/graph/retrieval/signals.py +0 -95
- package/lattice_brain/graph/retrieval_docgen.py +0 -253
- package/lattice_brain/graph/retrieval_policy.py +0 -180
- package/lattice_brain/graph/retrieval_reads.py +0 -769
- package/lattice_brain/graph/retrieval_vector/__init__.py +0 -42
- package/lattice_brain/graph/retrieval_vector/fingerprint.py +0 -97
- package/lattice_brain/graph/retrieval_vector/indexing.py +0 -347
- package/lattice_brain/graph/retrieval_vector/search.py +0 -560
- package/lattice_brain/graph/retrieval_vector/status.py +0 -374
- package/lattice_brain/graph/schema.py +0 -792
- package/lattice_brain/graph/store.py +0 -268
- package/lattice_brain/graph/vector_index/__init__.py +0 -85
- package/lattice_brain/graph/vector_index/base.py +0 -167
- package/lattice_brain/graph/vector_index/brute_force.py +0 -110
- package/lattice_brain/graph/vector_index/hnsw.py +0 -290
- package/lattice_brain/graph/vector_index/jobs.py +0 -287
- package/lattice_brain/graph/vector_index/quantized.py +0 -148
- package/lattice_brain/graph/vector_index/selector.py +0 -161
- package/lattice_brain/graph/write_master.py +0 -308
- package/lattice_brain/ingestion/_contract.py +0 -90
- package/lattice_brain/ingestion/folder_scan.py +0 -57
- package/lattice_brain/ingestion/folders.py +0 -258
- package/lattice_brain/ingestion/jobs_api.py +0 -107
- package/lattice_brain/ingestion/routing.py +0 -295
- package/lattice_brain/ingestion_jobs.py +0 -380
- package/lattice_brain/memory.py +0 -75
- package/lattice_brain/portability/__init__.py +0 -90
- package/lattice_brain/portability/_contract.py +0 -42
- package/lattice_brain/portability/backups.py +0 -338
- package/lattice_brain/portability/bundles.py +0 -136
- package/lattice_brain/portability/constants.py +0 -93
- package/lattice_brain/portability/fsops.py +0 -138
- package/lattice_brain/portability/service.py +0 -41
- package/lattice_brain/portability/sharing.py +0 -710
- package/lattice_brain/quality.py +0 -543
- package/lattice_brain/retrieval_benchmark_fixtures.py +0 -92
- package/lattice_brain/runtime/agent_runtime.py +0 -859
- package/lattice_brain/runtime/contracts.py +0 -460
- package/lattice_brain/runtime/multi_agent.py +0 -942
- package/lattice_brain/runtime/statuses.py +0 -10
- package/lattice_brain/sealed_box.py +0 -240
- package/lattice_brain/self_model.py +0 -675
- package/lattice_brain/sensitivity.py +0 -94
- package/lattice_brain/storage/__init__.py +0 -22
- package/lattice_brain/storage/base.py +0 -100
- package/lattice_brain/storage/docker.py +0 -105
- package/lattice_brain/storage/factory.py +0 -31
- package/lattice_brain/storage/migration.py +0 -191
- package/lattice_brain/storage/postgres.py +0 -123
- package/lattice_brain/storage/sqlite.py +0 -143
- package/lattice_brain/synthesis.py +0 -824
- package/lattice_brain/workflow.py +0 -497
- package/latticeai/api/admin.py +0 -471
- package/latticeai/api/agent_registry.py +0 -105
- package/latticeai/api/agents.py +0 -228
- package/latticeai/api/auth.py +0 -383
- package/latticeai/api/automation_intelligence.py +0 -401
- package/latticeai/api/brain_intelligence.py +0 -199
- package/latticeai/api/browser.py +0 -493
- package/latticeai/api/change_proposals.py +0 -89
- package/latticeai/api/chat.py +0 -572
- package/latticeai/api/chat_agent_http.py +0 -892
- package/latticeai/api/chat_contracts.py +0 -73
- package/latticeai/api/chat_documents.py +0 -276
- package/latticeai/api/chat_helpers.py +0 -460
- package/latticeai/api/chat_history.py +0 -101
- package/latticeai/api/chat_hybrid.py +0 -113
- package/latticeai/api/chat_intents.py +0 -646
- package/latticeai/api/chat_stream.py +0 -216
- package/latticeai/api/chronicle.py +0 -63
- package/latticeai/api/command_center.py +0 -51
- package/latticeai/api/computer_use.py +0 -474
- package/latticeai/api/evidence_actions.py +0 -48
- package/latticeai/api/features.py +0 -70
- package/latticeai/api/funnel_metrics.py +0 -31
- package/latticeai/api/garden.py +0 -34
- package/latticeai/api/hooks.py +0 -165
- package/latticeai/api/index_jobs.py +0 -145
- package/latticeai/api/invitations.py +0 -100
- package/latticeai/api/knowledge_graph.py +0 -536
- package/latticeai/api/marketplace.py +0 -105
- package/latticeai/api/mcp.py +0 -482
- package/latticeai/api/memory.py +0 -270
- package/latticeai/api/network.py +0 -81
- package/latticeai/api/network_boundary.py +0 -225
- package/latticeai/api/permission_mode.py +0 -61
- package/latticeai/api/permissions.py +0 -436
- package/latticeai/api/plugins.py +0 -126
- package/latticeai/api/portability.py +0 -391
- package/latticeai/api/project_sessions.py +0 -114
- package/latticeai/api/realtime.py +0 -118
- package/latticeai/api/review_queue.py +0 -364
- package/latticeai/api/security_dashboard.py +0 -604
- package/latticeai/api/setup.py +0 -319
- package/latticeai/api/static_routes.py +0 -354
- package/latticeai/api/ui_redirects.py +0 -26
- package/latticeai/api/workflow_designer.py +0 -394
- package/latticeai/api/workspace.py +0 -856
- package/latticeai/api/workspace_scope.py +0 -125
- package/latticeai/core/agent/__init__.py +0 -93
- package/latticeai/core/agent/_contract.py +0 -79
- package/latticeai/core/agent/context.py +0 -57
- package/latticeai/core/agent/deps.py +0 -125
- package/latticeai/core/agent/execution.py +0 -622
- package/latticeai/core/agent/planning.py +0 -145
- package/latticeai/core/agent/recovery.py +0 -157
- package/latticeai/core/agent/runtime.py +0 -210
- package/latticeai/core/agent/verification.py +0 -231
- package/latticeai/core/agent_eval.py +0 -739
- package/latticeai/core/agent_helpers.py +0 -493
- package/latticeai/core/agent_profiles.py +0 -110
- package/latticeai/core/agent_prompts.py +0 -171
- package/latticeai/core/agent_registry.py +0 -232
- package/latticeai/core/agent_state.py +0 -41
- package/latticeai/core/agent_trace.py +0 -104
- package/latticeai/core/artifact_ledger.py +0 -109
- package/latticeai/core/audit.py +0 -260
- package/latticeai/core/builtin_hooks.py +0 -105
- package/latticeai/core/context_builder.py +0 -394
- package/latticeai/core/document_generator.py +0 -103
- package/latticeai/core/enterprise.py +0 -154
- package/latticeai/core/enterprise_admin.py +0 -158
- package/latticeai/core/file_generation/__init__.py +0 -115
- package/latticeai/core/file_generation/bundles.py +0 -76
- package/latticeai/core/file_generation/extraction.py +0 -154
- package/latticeai/core/file_generation/inference.py +0 -235
- package/latticeai/core/file_generation/orchestration.py +0 -152
- package/latticeai/core/file_generation/prompting.py +0 -117
- package/latticeai/core/file_generation/repair.py +0 -114
- package/latticeai/core/file_generation/sanitize.py +0 -61
- package/latticeai/core/file_generation/validation.py +0 -201
- package/latticeai/core/invitations.py +0 -132
- package/latticeai/core/legacy_compatibility.py +0 -243
- package/latticeai/core/logging_safety.py +0 -46
- package/latticeai/core/marketplace.py +0 -293
- package/latticeai/core/mcp_catalog.py +0 -452
- package/latticeai/core/mcp_registry.py +0 -506
- package/latticeai/core/network_boundary.py +0 -168
- package/latticeai/core/oidc.py +0 -208
- package/latticeai/core/plugins.py +0 -432
- package/latticeai/core/product_hardening.py +0 -218
- package/latticeai/core/project_sessions.py +0 -337
- package/latticeai/core/realtime.py +0 -238
- package/latticeai/core/run_explain.py +0 -426
- package/latticeai/core/run_store.py +0 -252
- package/latticeai/core/timezones.py +0 -80
- package/latticeai/core/workspace_computer_memory.py +0 -84
- package/latticeai/core/workspace_graph_trace.py +0 -155
- package/latticeai/core/workspace_indexing.py +0 -102
- package/latticeai/core/workspace_memory.py +0 -77
- package/latticeai/core/workspace_onboarding.py +0 -104
- package/latticeai/core/workspace_os.py +0 -978
- package/latticeai/core/workspace_os_constants.py +0 -126
- package/latticeai/core/workspace_os_state.py +0 -180
- package/latticeai/core/workspace_os_utils.py +0 -103
- package/latticeai/core/workspace_permissions.py +0 -101
- package/latticeai/core/workspace_plugins.py +0 -97
- package/latticeai/core/workspace_relationships.py +0 -99
- package/latticeai/core/workspace_reorganization.py +0 -335
- package/latticeai/core/workspace_review_items.py +0 -112
- package/latticeai/core/workspace_runs.py +0 -726
- package/latticeai/core/workspace_skills.py +0 -109
- package/latticeai/core/workspace_snapshots.py +0 -198
- package/latticeai/core/workspace_timeline.py +0 -110
- package/latticeai/integrations/__init__.py +0 -0
- package/latticeai/integrations/telegram_bot/__init__.py +0 -123
- package/latticeai/integrations/telegram_bot/__main__.py +0 -17
- package/latticeai/integrations/telegram_bot/config.py +0 -86
- package/latticeai/integrations/telegram_bot/dispatch.py +0 -311
- package/latticeai/integrations/telegram_bot/flows.py +0 -478
- package/latticeai/integrations/telegram_bot/helpers.py +0 -322
- package/latticeai/integrations/telegram_bot/screens.py +0 -394
- package/latticeai/runtime/audit_runtime.py +0 -76
- package/latticeai/runtime/automation_runtime.py +0 -81
- package/latticeai/runtime/chat_wiring.py +0 -141
- package/latticeai/runtime/context_runtime.py +0 -66
- package/latticeai/runtime/feature_toggle_wiring.py +0 -157
- package/latticeai/runtime/history_runtime.py +0 -163
- package/latticeai/runtime/history_writer.py +0 -138
- package/latticeai/runtime/hooks_runtime.py +0 -77
- package/latticeai/runtime/model_wiring.py +0 -68
- package/latticeai/runtime/namespace_runtime.py +0 -163
- package/latticeai/runtime/network_boundary_wiring.py +0 -117
- package/latticeai/runtime/network_config_runtime.py +0 -56
- package/latticeai/runtime/permission_mode_wiring.py +0 -112
- package/latticeai/runtime/persistence_runtime.py +0 -159
- package/latticeai/runtime/platform_runtime_wiring.py +0 -89
- package/latticeai/runtime/review_wiring.py +0 -42
- package/latticeai/runtime/router_registration.py +0 -693
- package/latticeai/runtime/service_singletons.py +0 -55
- package/latticeai/runtime/sso_config_runtime.py +0 -128
- package/latticeai/runtime/user_key_runtime.py +0 -106
- package/latticeai/runtime/web_runtime.py +0 -92
- package/latticeai/server_app.py +0 -51
- package/latticeai/services/app_context.py +0 -130
- package/latticeai/services/automation_execution.py +0 -266
- package/latticeai/services/automation_intelligence.py +0 -614
- package/latticeai/services/brain_automation.py +0 -191
- package/latticeai/services/brain_intelligence/__init__.py +0 -58
- package/latticeai/services/brain_intelligence/_contract.py +0 -71
- package/latticeai/services/brain_intelligence/consistency.py +0 -193
- package/latticeai/services/brain_intelligence/constants.py +0 -47
- package/latticeai/services/brain_intelligence/digest.py +0 -258
- package/latticeai/services/brain_intelligence/health.py +0 -331
- package/latticeai/services/brain_intelligence/proposals.py +0 -259
- package/latticeai/services/brain_intelligence/sampling.py +0 -84
- package/latticeai/services/brain_intelligence/service.py +0 -48
- package/latticeai/services/change_proposals.py +0 -471
- package/latticeai/services/chat_service.py +0 -243
- package/latticeai/services/chronicle.py +0 -555
- package/latticeai/services/cloud_egress_audit.py +0 -85
- package/latticeai/services/cloud_extraction.py +0 -129
- package/latticeai/services/cloud_streaming.py +0 -268
- package/latticeai/services/cloud_token_guard.py +0 -84
- package/latticeai/services/command_center.py +0 -548
- package/latticeai/services/evidence_actions.py +0 -258
- package/latticeai/services/feature_toggles.py +0 -502
- package/latticeai/services/folder_watch.py +0 -520
- package/latticeai/services/funnel_metrics.py +0 -307
- package/latticeai/services/hybrid_chat.py +0 -316
- package/latticeai/services/hybrid_context.py +0 -228
- package/latticeai/services/hybrid_policy.py +0 -129
- package/latticeai/services/interop_bridges.py +0 -978
- package/latticeai/services/local_knowledge.py +0 -465
- package/latticeai/services/memory_service/__init__.py +0 -52
- package/latticeai/services/memory_service/_contract.py +0 -100
- package/latticeai/services/memory_service/brief.py +0 -431
- package/latticeai/services/memory_service/constants.py +0 -57
- package/latticeai/services/memory_service/maintenance.py +0 -138
- package/latticeai/services/memory_service/manager.py +0 -186
- package/latticeai/services/memory_service/proof.py +0 -136
- package/latticeai/services/memory_service/recall.py +0 -225
- package/latticeai/services/memory_service/service.py +0 -48
- package/latticeai/services/memory_service/stores.py +0 -110
- package/latticeai/services/mode_store.py +0 -132
- package/latticeai/services/model_recommendation.py +0 -224
- package/latticeai/services/model_runtime/cloud.py +0 -87
- package/latticeai/services/network_boundary_service.py +0 -117
- package/latticeai/services/obsidian_bridge.py +0 -609
- package/latticeai/services/openai_compatible_adapter.py +0 -101
- package/latticeai/services/permission_mode_service.py +0 -122
- package/latticeai/services/platform_runtime.py +0 -366
- package/latticeai/services/review_queue.py +0 -380
- package/latticeai/services/router_context.py +0 -59
- package/latticeai/services/run_executor.py +0 -387
- package/latticeai/services/self_model_service.py +0 -171
- package/latticeai/services/setup_detection.py +0 -147
- package/latticeai/services/triggers.py +0 -378
- package/latticeai/services/upload_service.py +0 -172
- package/latticeai/services/workspace_service.py +0 -165
- package/latticeai/setup/__init__.py +0 -25
- package/latticeai/setup/auto_setup.py +0 -846
- package/latticeai/setup/demo_corpus.py +0 -98
- package/latticeai/setup/wizard/__init__.py +0 -126
- package/latticeai/setup/wizard/catalog.py +0 -175
- package/latticeai/setup/wizard/detect.py +0 -302
- package/latticeai/setup/wizard/install.py +0 -348
- package/latticeai/setup/wizard/paths.py +0 -165
- package/latticeai/setup/wizard/plans.py +0 -74
- package/latticeai/setup/wizard/recommend.py +0 -320
- package/scripts/bench_agent_smoke.py +0 -409
- package/scripts/bench_models.py +0 -540
- package/scripts/bench_vector_index.py +0 -295
- package/scripts/funnel_soft_gate.py +0 -192
- package/scripts/generate_agent_loop_fixtures.py +0 -994
- package/scripts/generate_rust_parity_fixtures.py +0 -908
- package/scripts/migrate_brain_storage.py +0 -57
- package/scripts/parity_fixture_corpus_context.py +0 -162
- package/scripts/parity_fixture_corpus_docgen.py +0 -341
- package/scripts/profile_kg.py +0 -355
- package/server.py +0 -30
- package/static/app/assets/arrow-left-kfsrk0mv.js +0 -1
- package/static/app/assets/circle-check-qqLug9nU.js +0 -1
- package/static/app/assets/index-DxmOfNRi.css +0 -2
- package/static/app/assets/search-BcHqkjoy.js +0 -1
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
"""PLAN and APPROVAL — deciding what to do, and whether it may be done.
|
|
2
|
-
|
|
3
|
-
The planner turns the request into a structured plan; the approval gate then
|
|
4
|
-
answers one question about that plan under the run's autonomy dial: does a step
|
|
5
|
-
in it need a human to say yes? :meth:`approval_requirements` is the read-only
|
|
6
|
-
half of exactly the predicate :meth:`approve` enforces, so the HTTP layer can
|
|
7
|
-
pause a run instead of failing it closed without ever weakening the gate.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from __future__ import annotations
|
|
11
|
-
|
|
12
|
-
from typing import Any, Dict, Optional
|
|
13
|
-
|
|
14
|
-
from latticeai.core.agent_helpers import extract_action_details, normalize_plan
|
|
15
|
-
from latticeai.core.agent_permission import non_auto_plan_steps
|
|
16
|
-
from latticeai.core.agent_state import AgentState
|
|
17
|
-
from latticeai.core.permission_mode import plan_requires_approval
|
|
18
|
-
|
|
19
|
-
from ._contract import AgentCore as _Core
|
|
20
|
-
from .context import AgentRunContext
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class _PlanningMixin(_Core):
|
|
24
|
-
"""The PLAN and APPROVAL phases of :class:`SingleAgentRuntime`."""
|
|
25
|
-
|
|
26
|
-
# ── PLAN ─────────────────────────────────────────────────────────
|
|
27
|
-
async def plan(
|
|
28
|
-
self, ctx: AgentRunContext, req: Any, lang_hint: str, current_user: str,
|
|
29
|
-
model_id: Optional[str] = None,
|
|
30
|
-
) -> None:
|
|
31
|
-
"""PLAN: Planner role produces a structured plan JSON."""
|
|
32
|
-
d = self.deps
|
|
33
|
-
project_block = self._project_block(ctx)
|
|
34
|
-
context = (
|
|
35
|
-
f"{d.planner_prompt}\n\n"
|
|
36
|
-
f"[LANGUAGE HINT: {lang_hint}]\n"
|
|
37
|
-
f"Workspace root: {d.agent_root}{project_block}\n\n"
|
|
38
|
-
f"User request: {req.message}"
|
|
39
|
-
)
|
|
40
|
-
raw = await d.generate_as(
|
|
41
|
-
model_id,
|
|
42
|
-
message="Produce a JSON execution plan for this request.",
|
|
43
|
-
context=context, max_tokens=self.phase_budgets.plan_tokens, temperature=0.1,
|
|
44
|
-
)
|
|
45
|
-
ctx.trace.llm_call("plan", model=model_id)
|
|
46
|
-
try:
|
|
47
|
-
plan, plan_repairs = extract_action_details(str(raw))
|
|
48
|
-
ctx.trace.repair("plan", repairs=plan_repairs)
|
|
49
|
-
except ValueError as exc:
|
|
50
|
-
ctx.trace.parse_error("plan", error=str(exc), recovered=True)
|
|
51
|
-
plan = {
|
|
52
|
-
"action": "plan", "state": "PLAN",
|
|
53
|
-
"goal": req.message, "steps": [],
|
|
54
|
-
"requires_approval": False, "rollback_strategy": "none", "estimated_steps": 1,
|
|
55
|
-
}
|
|
56
|
-
plan, plan_fixes = normalize_plan(plan, req.message)
|
|
57
|
-
if plan_fixes:
|
|
58
|
-
ctx.trace.repair("plan", repairs=plan_fixes)
|
|
59
|
-
ctx.plan = plan
|
|
60
|
-
ctx.transcript.append({
|
|
61
|
-
"state": AgentState.PLANNING.value,
|
|
62
|
-
"goal": plan.get("goal", req.message),
|
|
63
|
-
"steps": plan.get("steps", []),
|
|
64
|
-
"requires_approval": plan.get("requires_approval", False),
|
|
65
|
-
"rollback_strategy": plan.get("rollback_strategy", "none"),
|
|
66
|
-
"estimated_steps": plan.get("estimated_steps", 1),
|
|
67
|
-
**({"plan_fixes": plan_fixes} if plan_fixes else {}),
|
|
68
|
-
})
|
|
69
|
-
self._emit_step(
|
|
70
|
-
ctx, "plan", "planned",
|
|
71
|
-
goal=str(plan.get("goal") or "")[:200],
|
|
72
|
-
steps=len(plan.get("steps") or []),
|
|
73
|
-
requires_approval=bool(plan.get("requires_approval", False)),
|
|
74
|
-
)
|
|
75
|
-
ctx.state = AgentState.WAITING_APPROVAL
|
|
76
|
-
|
|
77
|
-
# ── APPROVAL ─────────────────────────────────────────────────────
|
|
78
|
-
def approval_requirements(self, ctx: AgentRunContext) -> Dict[str, Any]:
|
|
79
|
-
"""Read-only preview of the approval gate for a planned run.
|
|
80
|
-
|
|
81
|
-
Shares the exact predicate :meth:`approve` enforces, so the HTTP
|
|
82
|
-
layer can pause a run as ``awaiting_approval`` (with a plan summary
|
|
83
|
-
for the user) instead of letting it fail closed — without ever
|
|
84
|
-
weakening the gate itself.
|
|
85
|
-
"""
|
|
86
|
-
d = self.deps
|
|
87
|
-
mode = self.resolve_permission_mode(ctx)
|
|
88
|
-
# Governor-managed tools never hard-block the plan: each call is
|
|
89
|
-
# classified at execution time — additive creates run, mutations and
|
|
90
|
-
# deletions of existing content become review proposals.
|
|
91
|
-
governed_tools = self._governed_tools()
|
|
92
|
-
steps = ctx.plan.get("steps", [])
|
|
93
|
-
non_auto = non_auto_plan_steps(
|
|
94
|
-
mode, steps, d.tool_governance or {}, governed_tools=governed_tools,
|
|
95
|
-
)
|
|
96
|
-
requires = plan_requires_approval(
|
|
97
|
-
mode,
|
|
98
|
-
non_auto_steps=non_auto,
|
|
99
|
-
plan_flag=bool(ctx.plan.get("requires_approval", False)),
|
|
100
|
-
)
|
|
101
|
-
lines = [
|
|
102
|
-
f"{index}. {step.get('description') or step.get('action') or '?'}"
|
|
103
|
-
for index, step in enumerate(steps, start=1)
|
|
104
|
-
]
|
|
105
|
-
summary = str(ctx.plan.get("goal") or "").strip()
|
|
106
|
-
if lines:
|
|
107
|
-
summary = (summary + "\n" if summary else "") + "\n".join(lines)
|
|
108
|
-
return {
|
|
109
|
-
"requires_approval": requires,
|
|
110
|
-
"non_auto_steps": non_auto,
|
|
111
|
-
"permission_mode": mode.value,
|
|
112
|
-
"plan_summary": summary,
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
def approve(self, ctx: AgentRunContext, current_user: str, *, approved_by_human: bool = False) -> None:
|
|
116
|
-
"""APPROVAL: Check governance, log decision, auto-approve (future: UI prompt)."""
|
|
117
|
-
d = self.deps
|
|
118
|
-
requirements = self.approval_requirements(ctx)
|
|
119
|
-
non_auto = requirements["non_auto_steps"]
|
|
120
|
-
requires = requirements["requires_approval"]
|
|
121
|
-
|
|
122
|
-
ctx.transcript.append({
|
|
123
|
-
"state": AgentState.WAITING_APPROVAL.value,
|
|
124
|
-
"requires_approval": requires,
|
|
125
|
-
"non_auto_approve_steps": non_auto,
|
|
126
|
-
"decision": "human_approved" if requires and approved_by_human else ("blocked_pending_approval" if requires else "auto_approved"),
|
|
127
|
-
})
|
|
128
|
-
decision = "human_approved" if requires and approved_by_human else ("blocked_pending_approval" if requires else "auto_approved")
|
|
129
|
-
ctx.trace.decision("approve", decision=decision, non_auto_steps=len(non_auto))
|
|
130
|
-
self._emit_step(ctx, "approval", "decision", decision=decision)
|
|
131
|
-
d.audit(
|
|
132
|
-
"agent_approval", user_email=current_user,
|
|
133
|
-
requires_approval=requires,
|
|
134
|
-
non_auto_steps=non_auto,
|
|
135
|
-
decision=decision,
|
|
136
|
-
)
|
|
137
|
-
if requires and not approved_by_human:
|
|
138
|
-
ctx.final_message = (
|
|
139
|
-
"이 작업에는 명시 승인이 필요한 도구가 포함되어 있어 자동 실행을 중단했습니다. "
|
|
140
|
-
"human_in_loop 승인 흐름으로 다시 실행해 주세요."
|
|
141
|
-
)
|
|
142
|
-
ctx.state = AgentState.FAILED
|
|
143
|
-
return
|
|
144
|
-
ctx.approved_by_human = bool(approved_by_human)
|
|
145
|
-
ctx.state = AgentState.EXECUTING
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
"""ROLLBACK and MEMORY — putting the workspace back, and keeping the lesson.
|
|
2
|
-
|
|
3
|
-
Recovery tries git first where the policy says the tool is git-rollbackable,
|
|
4
|
-
then the pre-write snapshot the executor captured, and otherwise reports
|
|
5
|
-
``mode="none"`` rather than claiming a recovery it did not perform. The memory
|
|
6
|
-
phase records what a *terminal* run taught: DONE runs record what worked,
|
|
7
|
-
FAILED and NEEDS_REVIEW runs record what went wrong, stored under the run's
|
|
8
|
-
actual terminal state and never a blanket "ok".
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
from __future__ import annotations
|
|
12
|
-
|
|
13
|
-
import json
|
|
14
|
-
import logging
|
|
15
|
-
from typing import Any, Dict, List, Optional
|
|
16
|
-
|
|
17
|
-
from latticeai.core.agent_helpers import extract_action, filter_learnings
|
|
18
|
-
from latticeai.core.agent_state import AGENT_TERMINAL_STATES, AgentState
|
|
19
|
-
|
|
20
|
-
from ._contract import AgentCore as _Core
|
|
21
|
-
from .context import AgentRunContext
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class _RecoveryMixin(_Core):
|
|
25
|
-
"""The ROLLBACK and MEMORY phases of :class:`SingleAgentRuntime`."""
|
|
26
|
-
|
|
27
|
-
# ── ROLLBACK ─────────────────────────────────────────────────────
|
|
28
|
-
def _snapshot_for(self, ctx: AgentRunContext, path: str) -> Optional[Dict[str, Any]]:
|
|
29
|
-
for entry in ctx.rollback_log:
|
|
30
|
-
if entry.get("path") == path:
|
|
31
|
-
return entry
|
|
32
|
-
return None
|
|
33
|
-
|
|
34
|
-
def _rollback_one(self, ctx: AgentRunContext, path: str, gov: Dict[str, Any]) -> Dict[str, Any]:
|
|
35
|
-
"""Recover one path: git when governed and available, else the
|
|
36
|
-
pre-write snapshot, else an honest ``mode="none"`` (review L7)."""
|
|
37
|
-
d = self.deps
|
|
38
|
-
if gov.get("rollback") == "git" and d.rollback_file is not None:
|
|
39
|
-
try:
|
|
40
|
-
result = dict(d.rollback_file(str(path)))
|
|
41
|
-
except Exception as exc: # noqa: BLE001
|
|
42
|
-
result = {"path": path, "ok": False, "error": str(exc)}
|
|
43
|
-
if result.get("ok"):
|
|
44
|
-
result["mode"] = "git"
|
|
45
|
-
return result
|
|
46
|
-
snapshot = self._snapshot_for(ctx, str(path))
|
|
47
|
-
if snapshot is not None and d.restore_snapshot is not None and not snapshot.get("too_large"):
|
|
48
|
-
content = snapshot.get("content") if snapshot.get("existed") else None
|
|
49
|
-
try:
|
|
50
|
-
restored = dict(d.restore_snapshot(str(path), content))
|
|
51
|
-
except Exception as exc: # noqa: BLE001
|
|
52
|
-
restored = {"path": path, "ok": False, "error": str(exc)}
|
|
53
|
-
restored.setdefault("path", path)
|
|
54
|
-
restored["mode"] = "snapshot"
|
|
55
|
-
return restored
|
|
56
|
-
return {
|
|
57
|
-
"path": path, "ok": False, "mode": "none",
|
|
58
|
-
"error": "no rollback available (git not applicable, no usable snapshot)",
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
def rollback(self, ctx: AgentRunContext, current_user: str) -> None:
|
|
62
|
-
"""ROLLBACK: recover written files (git → snapshot → none), then FAILED."""
|
|
63
|
-
d = self.deps
|
|
64
|
-
rolled: List[dict] = []
|
|
65
|
-
seen_paths: set = set()
|
|
66
|
-
for step in ctx.transcript:
|
|
67
|
-
if step.get("state") != AgentState.EXECUTING.value:
|
|
68
|
-
continue
|
|
69
|
-
if not isinstance(step.get("result"), dict):
|
|
70
|
-
continue
|
|
71
|
-
gov = step.get("governance", {}) or {}
|
|
72
|
-
path = step["result"].get("path") or (step.get("args") or {}).get("path", "")
|
|
73
|
-
if not path or str(path) in seen_paths:
|
|
74
|
-
continue
|
|
75
|
-
if gov.get("rollback") != "git" and step.get("action") not in d.file_create_actions:
|
|
76
|
-
continue
|
|
77
|
-
seen_paths.add(str(path))
|
|
78
|
-
rolled.append(self._rollback_one(ctx, str(path), gov))
|
|
79
|
-
|
|
80
|
-
ctx.transcript.append({"state": AgentState.ROLLBACK.value, "rolled_back": rolled})
|
|
81
|
-
ctx.trace.decision(
|
|
82
|
-
"rollback", decision="rolled_back",
|
|
83
|
-
attempted=len(rolled), recovered=sum(1 for r in rolled if r.get("ok")),
|
|
84
|
-
)
|
|
85
|
-
recovered = [f"{r['path']} ({r.get('mode')})" for r in rolled if r.get("ok")]
|
|
86
|
-
ctx.final_message = (
|
|
87
|
-
f"실행 실패로 롤백했습니다. 복구 파일: {recovered}"
|
|
88
|
-
if recovered
|
|
89
|
-
else "롤백을 시도했으나 복구할 파일이 없거나 git/스냅샷 복구 수단이 없습니다."
|
|
90
|
-
)
|
|
91
|
-
d.audit("agent_rollback", user_email=current_user, rolled_back=rolled)
|
|
92
|
-
self._emit_step(ctx, "rollback", "rolled_back", recovered=len(recovered))
|
|
93
|
-
# Rollback is a recovery from a failed verification — terminal state is FAILED
|
|
94
|
-
ctx.state = AgentState.FAILED
|
|
95
|
-
|
|
96
|
-
# ── MEMORY ───────────────────────────────────────────────────────
|
|
97
|
-
async def memory_update(self, ctx: AgentRunContext, req: Any, current_user: str) -> None:
|
|
98
|
-
"""Background: Memory Updater role extracts learnings from a terminal run.
|
|
99
|
-
|
|
100
|
-
Terminal-state learning policy (review §4.2 L6): DONE runs record what
|
|
101
|
-
worked; FAILED / NEEDS_REVIEW runs record what went wrong — failure is
|
|
102
|
-
exactly the experience worth remembering. The run status stored with
|
|
103
|
-
the experience is the *actual* terminal state, never a blanket "ok".
|
|
104
|
-
"""
|
|
105
|
-
d = self.deps
|
|
106
|
-
terminal = ctx.state.value if ctx.state in AGENT_TERMINAL_STATES else "UNKNOWN"
|
|
107
|
-
outcome_hint = (
|
|
108
|
-
"The task completed successfully."
|
|
109
|
-
if ctx.state == AgentState.DONE
|
|
110
|
-
else (
|
|
111
|
-
f"The task ended as {terminal} — extract what went wrong and "
|
|
112
|
-
"what to do differently next time, not a success story."
|
|
113
|
-
)
|
|
114
|
-
)
|
|
115
|
-
context = (
|
|
116
|
-
f"{d.memory_updater_prompt}\n\n"
|
|
117
|
-
f"Task: {req.message}\n"
|
|
118
|
-
f"Terminal status: {terminal}. {outcome_hint}\n\n"
|
|
119
|
-
f"Last 5 transcript steps:\n{json.dumps(ctx.transcript[-5:], ensure_ascii=False)}"
|
|
120
|
-
)
|
|
121
|
-
try:
|
|
122
|
-
raw = await d.generate(
|
|
123
|
-
message="Extract learnings from this completed task.",
|
|
124
|
-
context=context, max_tokens=self.phase_budgets.memory_tokens, temperature=0.1,
|
|
125
|
-
)
|
|
126
|
-
mem = extract_action(str(raw))
|
|
127
|
-
kept_learnings = filter_learnings(mem.get("learnings") or [])
|
|
128
|
-
if mem.get("save_to_knowledge") and kept_learnings:
|
|
129
|
-
learnings = "\n".join(kept_learnings)
|
|
130
|
-
status_label = {
|
|
131
|
-
AgentState.DONE: "ok",
|
|
132
|
-
AgentState.NEEDS_REVIEW: "needs_review",
|
|
133
|
-
AgentState.FAILED: "failed",
|
|
134
|
-
}.get(ctx.state, "unknown")
|
|
135
|
-
if d.brain_memory is not None:
|
|
136
|
-
# This runtime is LLM-driven — its learnings are real
|
|
137
|
-
# experiences and enter the brain with provenance.
|
|
138
|
-
d.brain_memory.record_experience(
|
|
139
|
-
f"Agent: {req.message[:60]}",
|
|
140
|
-
learnings,
|
|
141
|
-
run={
|
|
142
|
-
"mode": "llm",
|
|
143
|
-
"status": status_label,
|
|
144
|
-
"agent_id": "agent:executor",
|
|
145
|
-
"steps": len(ctx.transcript),
|
|
146
|
-
},
|
|
147
|
-
user_email=current_user or None,
|
|
148
|
-
)
|
|
149
|
-
else:
|
|
150
|
-
d.knowledge_save(
|
|
151
|
-
learnings,
|
|
152
|
-
folder="30_Projects",
|
|
153
|
-
title=f"Agent: {req.message[:60]}",
|
|
154
|
-
)
|
|
155
|
-
except Exception as exc:
|
|
156
|
-
# Never crash a completed run, but never swallow silently either.
|
|
157
|
-
logging.warning("agent memory update failed: %s", exc)
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
"""The runtime itself: shared state, the budgets, and the drive loop.
|
|
2
|
-
|
|
3
|
-
:class:`SingleAgentRuntime` is assembled from the four phase mixins. What lives
|
|
4
|
-
*here* is what all four need — the injected ports, the per-phase token and
|
|
5
|
-
transcript budgets, the autonomy dial, the step observer, the boundary/config
|
|
6
|
-
contracts — plus :meth:`run_to_completion`, which drives EXECUTING → VERIFYING
|
|
7
|
-
→ ROLLBACK until a terminal state.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from __future__ import annotations
|
|
11
|
-
|
|
12
|
-
import logging
|
|
13
|
-
from pathlib import Path
|
|
14
|
-
from typing import Any, Dict, FrozenSet, Optional
|
|
15
|
-
|
|
16
|
-
from lattice_brain.runtime.contracts import (
|
|
17
|
-
runtime_boundary_contract,
|
|
18
|
-
single_agent_contract,
|
|
19
|
-
)
|
|
20
|
-
from latticeai.core.agent_helpers import PhaseBudgets, TranscriptBudget
|
|
21
|
-
from latticeai.core.agent_permission import resolve_deps_mode
|
|
22
|
-
from latticeai.core.agent_profiles import AgentProfile, profile_for_model
|
|
23
|
-
from latticeai.core.agent_state import AGENT_TERMINAL_STATES, AgentState
|
|
24
|
-
from latticeai.core.permission_mode import PermissionMode
|
|
25
|
-
from latticeai.tools import document_output_target
|
|
26
|
-
|
|
27
|
-
from .context import AgentRunContext
|
|
28
|
-
from .deps import AgentDeps
|
|
29
|
-
from .execution import _ExecutionMixin
|
|
30
|
-
from .planning import _PlanningMixin
|
|
31
|
-
from .recovery import _RecoveryMixin
|
|
32
|
-
from .verification import _VerificationMixin
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class SingleAgentRuntime(
|
|
36
|
-
_PlanningMixin, _ExecutionMixin, _VerificationMixin, _RecoveryMixin
|
|
37
|
-
):
|
|
38
|
-
"""Drives the agent state machine over injected :class:`AgentDeps`.
|
|
39
|
-
|
|
40
|
-
The four mixins define disjoint method sets, so resolution order changes
|
|
41
|
-
nothing at runtime: this class exposes exactly the methods it exposed when
|
|
42
|
-
all four phases lived in one 1,465-line module.
|
|
43
|
-
"""
|
|
44
|
-
|
|
45
|
-
def __init__(self, deps: AgentDeps) -> None:
|
|
46
|
-
self.deps = deps
|
|
47
|
-
self._env_phase_budgets: Optional[PhaseBudgets] = None
|
|
48
|
-
self._env_transcript_budget: Optional[TranscriptBudget] = None
|
|
49
|
-
|
|
50
|
-
@property
|
|
51
|
-
def phase_budgets(self) -> PhaseBudgets:
|
|
52
|
-
# getattr twice: partially-constructed runtimes/deps (tests build them
|
|
53
|
-
# via __new__ or minimal fakes) still get working default budgets.
|
|
54
|
-
injected = getattr(self.deps, "phase_budgets", None)
|
|
55
|
-
if injected is not None:
|
|
56
|
-
return injected
|
|
57
|
-
cached = getattr(self, "_env_phase_budgets", None)
|
|
58
|
-
if cached is None:
|
|
59
|
-
cached = PhaseBudgets.from_env()
|
|
60
|
-
self._env_phase_budgets = cached
|
|
61
|
-
return cached
|
|
62
|
-
|
|
63
|
-
@property
|
|
64
|
-
def transcript_budget(self) -> TranscriptBudget:
|
|
65
|
-
injected = getattr(self.deps, "transcript_budget", None)
|
|
66
|
-
if injected is not None:
|
|
67
|
-
return injected
|
|
68
|
-
cached = getattr(self, "_env_transcript_budget", None)
|
|
69
|
-
if cached is None:
|
|
70
|
-
cached = TranscriptBudget.from_env()
|
|
71
|
-
self._env_transcript_budget = cached
|
|
72
|
-
return cached
|
|
73
|
-
|
|
74
|
-
# ── permission mode (v9.9.8) ─────────────────────────────────────
|
|
75
|
-
def resolve_permission_mode(
|
|
76
|
-
self,
|
|
77
|
-
ctx: Optional[AgentRunContext] = None,
|
|
78
|
-
*,
|
|
79
|
-
user_email: Optional[str] = None,
|
|
80
|
-
workspace_id: Optional[str] = None,
|
|
81
|
-
) -> PermissionMode:
|
|
82
|
-
"""Autonomy dial for this run.
|
|
83
|
-
|
|
84
|
-
A mode stamped on ``ctx`` wins, so the plan a user approved and every
|
|
85
|
-
tool step in the same run are judged by one dial even if the stored
|
|
86
|
-
preference changes mid-run. Otherwise the resolver on ``deps`` is
|
|
87
|
-
consulted with the caller's scope — resolving unscoped would collapse
|
|
88
|
-
every caller onto the process-wide default.
|
|
89
|
-
"""
|
|
90
|
-
return resolve_deps_mode(
|
|
91
|
-
self.deps, ctx, user_email=user_email, workspace_id=workspace_id,
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
def _governed_path_exists(self, name: str, path: str) -> bool:
|
|
95
|
-
"""Does this tool call's *real* target already exist?
|
|
96
|
-
|
|
97
|
-
The document creators sanitize ``filename`` into their own output
|
|
98
|
-
directory, so the raw argument is resolved through
|
|
99
|
-
:func:`document_output_target` first — checking it verbatim would
|
|
100
|
-
inspect a path nothing ever writes and the fail-closed overwrite guard
|
|
101
|
-
would never fire. Workspace-relative paths resolve under
|
|
102
|
-
``deps.agent_root``; absolute paths (home-sandbox writes) are honored
|
|
103
|
-
as-is. Never raises: governance must not be able to crash the loop, and
|
|
104
|
-
an unresolvable path degrades to "new file", which the remaining gates
|
|
105
|
-
still cover.
|
|
106
|
-
"""
|
|
107
|
-
try:
|
|
108
|
-
candidate = Path(document_output_target(name, path) or path)
|
|
109
|
-
if not candidate.is_absolute():
|
|
110
|
-
candidate = Path(self.deps.agent_root) / candidate
|
|
111
|
-
return candidate.exists()
|
|
112
|
-
except Exception: # noqa: BLE001 — classification is best-effort
|
|
113
|
-
return False
|
|
114
|
-
|
|
115
|
-
def _governed_tools(self) -> FrozenSet[str]:
|
|
116
|
-
governor = getattr(self.deps, "change_governor", None)
|
|
117
|
-
if governor is None:
|
|
118
|
-
return frozenset()
|
|
119
|
-
return frozenset(getattr(governor, "governed_tools", frozenset()))
|
|
120
|
-
|
|
121
|
-
def profile_for(self, model_id: Optional[str]) -> AgentProfile:
|
|
122
|
-
"""Loop profile for the model actually executing this run (v9.9.7).
|
|
123
|
-
|
|
124
|
-
An injected ``AgentDeps.agent_profile`` wins (tests, explicit config);
|
|
125
|
-
otherwise the profile is derived from the model id, so a small local
|
|
126
|
-
model gets the compact loop without any extra configuration.
|
|
127
|
-
"""
|
|
128
|
-
injected = getattr(self.deps, "agent_profile", None)
|
|
129
|
-
if injected is not None:
|
|
130
|
-
return injected
|
|
131
|
-
return profile_for_model(model_id)
|
|
132
|
-
|
|
133
|
-
def _emit_step(self, ctx: AgentRunContext, phase: str, event: str, **details: Any) -> None:
|
|
134
|
-
"""Fire the per-run / deps step observers (review Wave 1.1).
|
|
135
|
-
|
|
136
|
-
Observers power the live step timeline in the UI. They are pure
|
|
137
|
-
telemetry: any observer failure is logged and swallowed — the loop
|
|
138
|
-
itself must never notice.
|
|
139
|
-
"""
|
|
140
|
-
payload: Dict[str, Any] = {"phase": phase, "event": event}
|
|
141
|
-
for key, value in details.items():
|
|
142
|
-
if value is not None:
|
|
143
|
-
payload[key] = value
|
|
144
|
-
for observer in (getattr(ctx, "on_step", None), getattr(self.deps, "on_step", None)):
|
|
145
|
-
if observer is None:
|
|
146
|
-
continue
|
|
147
|
-
try:
|
|
148
|
-
observer(dict(payload))
|
|
149
|
-
except Exception as exc: # noqa: BLE001 — observers are advisory
|
|
150
|
-
logging.warning("agent step observer failed: %s", exc)
|
|
151
|
-
|
|
152
|
-
@staticmethod
|
|
153
|
-
def _project_block(ctx: AgentRunContext) -> str:
|
|
154
|
-
"""Project-session context for prompts, or "" for a standalone run.
|
|
155
|
-
|
|
156
|
-
Multi-turn project loop (v9.9.6): a later run must see the files the
|
|
157
|
-
project already produced and what is still open, instead of planning
|
|
158
|
-
from a blank workspace every time.
|
|
159
|
-
"""
|
|
160
|
-
summary = str(getattr(ctx, "project_context", "") or "").strip()
|
|
161
|
-
return f"\n\n[PROJECT SESSION]\n{summary}" if summary else ""
|
|
162
|
-
|
|
163
|
-
def boundary(self) -> Dict[str, Any]:
|
|
164
|
-
return runtime_boundary_contract(
|
|
165
|
-
name="SingleAgentRuntime",
|
|
166
|
-
runtime="single_agent",
|
|
167
|
-
entrypoint="latticeai.core.agent.SingleAgentRuntime",
|
|
168
|
-
surface="/agent",
|
|
169
|
-
owns="single-agent PLAN / EXECUTE / VERIFY state machine over injected ports",
|
|
170
|
-
compatibility_aliases=[],
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
def config(self) -> Dict[str, Any]:
|
|
174
|
-
return {
|
|
175
|
-
"boundary": self.boundary(),
|
|
176
|
-
"states": [state.value for state in AgentState],
|
|
177
|
-
"terminal_states": sorted(state.value for state in AGENT_TERMINAL_STATES),
|
|
178
|
-
"execution_mode": "injected_ports",
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
def contract(self, ctx: AgentRunContext, req: Any, *, run_id: Optional[str] = None) -> Dict[str, Any]:
|
|
182
|
-
"""Expose the shared agent-run contract for the single-agent loop."""
|
|
183
|
-
return single_agent_contract(ctx=ctx, goal=getattr(req, "message", ""), run_id=run_id)
|
|
184
|
-
|
|
185
|
-
# ── DRIVE LOOP ───────────────────────────────────────────────────
|
|
186
|
-
async def run_to_completion(
|
|
187
|
-
self, ctx: AgentRunContext, req: Any, lang_hint: str,
|
|
188
|
-
current_user: str, max_steps: int, max_retry: int,
|
|
189
|
-
) -> None:
|
|
190
|
-
"""Run EXECUTING → VERIFYING → ROLLBACK loop until a terminal state."""
|
|
191
|
-
while ctx.state not in AGENT_TERMINAL_STATES:
|
|
192
|
-
ctx.state_history.append(ctx.state.value)
|
|
193
|
-
if len(ctx.state_history) > 200:
|
|
194
|
-
ctx.final_message = "에이전트 상태 머신이 최대 반복(200)에 도달해 중단했습니다."
|
|
195
|
-
ctx.state = AgentState.FAILED
|
|
196
|
-
break
|
|
197
|
-
|
|
198
|
-
if ctx.state == AgentState.EXECUTING:
|
|
199
|
-
await self.execute(ctx, req, lang_hint, current_user, max_steps,
|
|
200
|
-
model_id=ctx.executing_model)
|
|
201
|
-
elif ctx.state == AgentState.VERIFYING:
|
|
202
|
-
await self.verify(ctx, req, lang_hint, current_user, max_retry,
|
|
203
|
-
model_id=ctx.reviewing_model)
|
|
204
|
-
elif ctx.state == AgentState.ROLLBACK:
|
|
205
|
-
self.rollback(ctx, current_user)
|
|
206
|
-
else:
|
|
207
|
-
ctx.state = AgentState.FAILED
|
|
208
|
-
|
|
209
|
-
ctx.state_history.append(ctx.state.value)
|
|
210
|
-
self._emit_step(ctx, "terminal", "state", state=ctx.state.value)
|