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,493 +0,0 @@
|
|
|
1
|
-
"""Pure helpers for the single-agent runtime.
|
|
2
|
-
|
|
3
|
-
Extracted from :mod:`latticeai.core.agent` so the state machine module
|
|
4
|
-
owns only the loop. Every function here is deterministic and free of
|
|
5
|
-
I/O / side effects (aside from reading environment in the budget
|
|
6
|
-
``from_env`` constructors).
|
|
7
|
-
|
|
8
|
-
Public names are re-exported from :mod:`latticeai.core.agent` so callers
|
|
9
|
-
keep importing from the original location.
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
from __future__ import annotations
|
|
13
|
-
|
|
14
|
-
import ast
|
|
15
|
-
import json
|
|
16
|
-
import re
|
|
17
|
-
from dataclasses import dataclass
|
|
18
|
-
from pathlib import Path
|
|
19
|
-
from typing import Any, Dict, FrozenSet, List, Mapping, Optional, Tuple
|
|
20
|
-
|
|
21
|
-
from latticeai.core.agent_state import AgentState
|
|
22
|
-
from latticeai.core.file_generation import infer_file_target, infer_project_manifest
|
|
23
|
-
|
|
24
|
-
# ── action parsing ─────────────────────────────────────────────────────
|
|
25
|
-
|
|
26
|
-
_THINK_BLOCK_RE = re.compile(
|
|
27
|
-
r"<(think|thinking|reasoning)>.*?</\1>", flags=re.DOTALL | re.IGNORECASE
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def extract_action_details(raw: str) -> Tuple[Dict, List[str]]:
|
|
32
|
-
"""Parse one JSON action object out of an LLM response (tolerant of fences/prose).
|
|
33
|
-
|
|
34
|
-
Returns ``(action, repairs)`` where ``repairs`` names every tolerance that
|
|
35
|
-
was needed — the loop trace and the weak-model robustness harness consume
|
|
36
|
-
it to measure how much help a given model needs.
|
|
37
|
-
"""
|
|
38
|
-
repairs: List[str] = []
|
|
39
|
-
# Small local models often prepend <think>...</think> reasoning that can
|
|
40
|
-
# itself contain braces — drop it before locating the action object.
|
|
41
|
-
text = _THINK_BLOCK_RE.sub("", raw).strip()
|
|
42
|
-
if text != str(raw).strip():
|
|
43
|
-
repairs.append("think_strip")
|
|
44
|
-
fenced = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", text, flags=re.DOTALL)
|
|
45
|
-
if fenced:
|
|
46
|
-
text = fenced.group(1).strip()
|
|
47
|
-
repairs.append("fence")
|
|
48
|
-
elif not text.startswith("{"):
|
|
49
|
-
start = text.find("{")
|
|
50
|
-
end = text.rfind("}")
|
|
51
|
-
if start >= 0 and end > start:
|
|
52
|
-
text = text[start : end + 1]
|
|
53
|
-
repairs.append("slice")
|
|
54
|
-
|
|
55
|
-
action: Any = None
|
|
56
|
-
try:
|
|
57
|
-
action = json.loads(text)
|
|
58
|
-
except json.JSONDecodeError:
|
|
59
|
-
# Second chance for the most common small-model JSON slips: trailing
|
|
60
|
-
# commas before a closing brace/bracket.
|
|
61
|
-
repaired = re.sub(r",\s*([}\]])", r"\1", text)
|
|
62
|
-
try:
|
|
63
|
-
action = json.loads(repaired)
|
|
64
|
-
repairs.append("trailing_comma")
|
|
65
|
-
except json.JSONDecodeError as exc:
|
|
66
|
-
# Last chance: weak models sometimes emit a Python dict literal
|
|
67
|
-
# (single quotes, True/False/None). ast.literal_eval parses that
|
|
68
|
-
# deterministically without evaluating code.
|
|
69
|
-
try:
|
|
70
|
-
literal = ast.literal_eval(text)
|
|
71
|
-
except (ValueError, SyntaxError):
|
|
72
|
-
raise ValueError(f"Agent did not return valid JSON: {exc}") from exc
|
|
73
|
-
if not isinstance(literal, dict):
|
|
74
|
-
raise ValueError(f"Agent did not return valid JSON: {exc}") from exc
|
|
75
|
-
action = literal
|
|
76
|
-
repairs.append("python_literal")
|
|
77
|
-
|
|
78
|
-
if not isinstance(action, dict) or "action" not in action:
|
|
79
|
-
raise ValueError("Agent JSON must include an action field.")
|
|
80
|
-
return action, repairs
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def extract_action(raw: str) -> Dict:
|
|
84
|
-
"""Back-compat wrapper over :func:`extract_action_details`."""
|
|
85
|
-
action, _ = extract_action_details(raw)
|
|
86
|
-
return action
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
# ── plan normalization ─────────────────────────────────────────────────
|
|
90
|
-
|
|
91
|
-
_FILE_CREATE_PLAN_ACTIONS = frozenset({"write_file", "generate_file"})
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def _plan_misses_manifest(steps: List[Dict[str, Any]], manifest: Dict[str, Any]) -> bool:
|
|
95
|
-
"""True when a pure file-writing plan fails to cover the manifest's file types.
|
|
96
|
-
|
|
97
|
-
Only pure file-creation plans are candidates for rewriting — a plan with
|
|
98
|
-
read/search steps reflects real planner intent and stays untouched.
|
|
99
|
-
"""
|
|
100
|
-
if any(s.get("action") not in _FILE_CREATE_PLAN_ACTIONS for s in steps):
|
|
101
|
-
return False
|
|
102
|
-
|
|
103
|
-
def _ext(path: Any) -> str:
|
|
104
|
-
text = str(path or "")
|
|
105
|
-
dot = text.rfind(".")
|
|
106
|
-
return text[dot:].lower() if dot >= 0 else ""
|
|
107
|
-
|
|
108
|
-
planned_exts = {
|
|
109
|
-
_ext((s.get("args") or {}).get("path")) for s in steps
|
|
110
|
-
}
|
|
111
|
-
manifest_exts = {_ext(spec.get("path")) for spec in manifest.get("files", [])}
|
|
112
|
-
return not manifest_exts.issubset(planned_exts)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def normalize_plan(plan: Any, user_message: str) -> Tuple[Dict[str, Any], List[str]]:
|
|
116
|
-
"""Enforce the minimal plan schema so execution never starts adrift.
|
|
117
|
-
|
|
118
|
-
A weak planner that returns junk steps / a missing goal previously flowed
|
|
119
|
-
straight into the executor, which then had to reconstruct intent from the
|
|
120
|
-
raw request. Normalization keeps the loop honest: ``goal`` is always a
|
|
121
|
-
non-empty string, ``steps`` only contains dicts with an ``action``, and an
|
|
122
|
-
empty plan for an obvious file-creation request gets a deterministic
|
|
123
|
-
single ``write_file`` step instead of leaving the executor to improvise.
|
|
124
|
-
|
|
125
|
-
Returns ``(plan, fixes)`` where ``fixes`` names every applied repair —
|
|
126
|
-
the loop trace records them so plan quality is observable per model.
|
|
127
|
-
"""
|
|
128
|
-
fixes: List[str] = []
|
|
129
|
-
if not isinstance(plan, dict):
|
|
130
|
-
plan = {}
|
|
131
|
-
fixes.append("plan_not_object")
|
|
132
|
-
plan = dict(plan)
|
|
133
|
-
|
|
134
|
-
goal = str(plan.get("goal") or "").strip()
|
|
135
|
-
if not goal:
|
|
136
|
-
plan["goal"] = user_message
|
|
137
|
-
fixes.append("goal_defaulted")
|
|
138
|
-
|
|
139
|
-
raw_steps = plan.get("steps")
|
|
140
|
-
steps = [
|
|
141
|
-
s for s in (raw_steps if isinstance(raw_steps, list) else [])
|
|
142
|
-
if isinstance(s, dict) and s.get("action")
|
|
143
|
-
]
|
|
144
|
-
if raw_steps and steps != raw_steps:
|
|
145
|
-
fixes.append("steps_filtered")
|
|
146
|
-
|
|
147
|
-
# Manifest-aware planning (review Wave 0.4): when the request is a
|
|
148
|
-
# recognized multi-file project, the deterministic manifest — not the
|
|
149
|
-
# planner's improvisation — decides the file set, exactly like the direct
|
|
150
|
-
# chat path. Rewrites apply only when the plan is empty or is a pure
|
|
151
|
-
# file-writing plan that misses part of the manifest, so a planner that
|
|
152
|
-
# already covered every requested file type is left untouched.
|
|
153
|
-
manifest = infer_project_manifest(user_message)
|
|
154
|
-
if manifest:
|
|
155
|
-
manifest_steps = [{
|
|
156
|
-
"action": "write_file",
|
|
157
|
-
"args": {"path": spec["path"]},
|
|
158
|
-
"description": spec["brief"],
|
|
159
|
-
} for spec in manifest["files"]]
|
|
160
|
-
if not steps:
|
|
161
|
-
steps = manifest_steps
|
|
162
|
-
fixes.append("manifest_steps")
|
|
163
|
-
elif _plan_misses_manifest(steps, manifest):
|
|
164
|
-
steps = manifest_steps
|
|
165
|
-
fixes.append("manifest_rewrite")
|
|
166
|
-
|
|
167
|
-
if not steps:
|
|
168
|
-
inferred = infer_file_target(user_message)
|
|
169
|
-
if inferred:
|
|
170
|
-
steps = [{
|
|
171
|
-
"action": "write_file",
|
|
172
|
-
"args": {"path": inferred},
|
|
173
|
-
"description": f"Create {inferred} for: {user_message[:120]}",
|
|
174
|
-
}]
|
|
175
|
-
fixes.append("heuristic_file_step")
|
|
176
|
-
plan["steps"] = steps
|
|
177
|
-
|
|
178
|
-
try:
|
|
179
|
-
estimated = int(plan.get("estimated_steps") or 0)
|
|
180
|
-
except (TypeError, ValueError):
|
|
181
|
-
estimated = 0
|
|
182
|
-
fixes.append("estimated_steps_invalid")
|
|
183
|
-
plan["estimated_steps"] = max(1, estimated, len(steps))
|
|
184
|
-
plan["requires_approval"] = bool(plan.get("requires_approval", False))
|
|
185
|
-
if not isinstance(plan.get("rollback_strategy"), str):
|
|
186
|
-
plan["rollback_strategy"] = "none"
|
|
187
|
-
return plan, fixes
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
# ── learnings filter ───────────────────────────────────────────────────
|
|
191
|
-
|
|
192
|
-
_TRIVIAL_LEARNING_RE = re.compile(
|
|
193
|
-
r"^(파일(을|이)?\s*(만들|생성|작성|저장)|작업(을|이)?\s*(완료|성공)|성공적으로"
|
|
194
|
-
r"|task\s+(was\s+)?complet|file\s+(was\s+)?(creat|written|saved)"
|
|
195
|
-
r"|(successfully\s+)?(created|completed|finished|done)\b)",
|
|
196
|
-
re.IGNORECASE,
|
|
197
|
-
)
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
def filter_learnings(learnings: List[Any]) -> List[str]:
|
|
201
|
-
"""Drop trivial/duplicate learnings before they enter the brain.
|
|
202
|
-
|
|
203
|
-
"파일을 만들었다"-class statements restate what the transcript already
|
|
204
|
-
records and pollute recall. A learning survives when it is long enough to
|
|
205
|
-
carry information and is not a bare completion announcement.
|
|
206
|
-
"""
|
|
207
|
-
kept: List[str] = []
|
|
208
|
-
seen: set = set()
|
|
209
|
-
for raw in learnings or []:
|
|
210
|
-
text = str(raw or "").strip()
|
|
211
|
-
if len(text) < 12:
|
|
212
|
-
continue
|
|
213
|
-
if _TRIVIAL_LEARNING_RE.match(text) and len(text) < 48:
|
|
214
|
-
continue
|
|
215
|
-
key = text.lower()
|
|
216
|
-
if key in seen:
|
|
217
|
-
continue
|
|
218
|
-
seen.add(key)
|
|
219
|
-
kept.append(text)
|
|
220
|
-
return kept
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
# ── transcript shaping ─────────────────────────────────────────────────
|
|
224
|
-
|
|
225
|
-
def _truncate_strings(value: Any, limit: int) -> Any:
|
|
226
|
-
"""Deep-copy ``value`` with every string capped at ``limit`` chars.
|
|
227
|
-
|
|
228
|
-
Long tool outputs (file bodies, command output) dominate executor prompt
|
|
229
|
-
size without adding decision-relevant signal. The cap keeps the head of
|
|
230
|
-
each string and names how much was dropped, so the model still sees what
|
|
231
|
-
the value was — never a silent hole.
|
|
232
|
-
"""
|
|
233
|
-
if isinstance(value, str):
|
|
234
|
-
if len(value) <= limit:
|
|
235
|
-
return value
|
|
236
|
-
return value[:limit] + f"…[+{len(value) - limit} chars]"
|
|
237
|
-
if isinstance(value, dict):
|
|
238
|
-
return {k: _truncate_strings(v, limit) for k, v in value.items()}
|
|
239
|
-
if isinstance(value, list):
|
|
240
|
-
return [_truncate_strings(v, limit) for v in value]
|
|
241
|
-
return value
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
def compact_transcript(
|
|
245
|
-
transcript: List[Dict[str, Any]],
|
|
246
|
-
*,
|
|
247
|
-
window: int = 8,
|
|
248
|
-
result_chars: int = 700,
|
|
249
|
-
) -> List[Dict[str, Any]]:
|
|
250
|
-
"""Bounded executor view of a transcript (review Wave 0.3).
|
|
251
|
-
|
|
252
|
-
The executor prompt previously embedded the *entire* transcript JSON every
|
|
253
|
-
step — O(steps²) token growth that starved :class:`PhaseBudgets` on long
|
|
254
|
-
runs and buried weak models in stale detail. This view keeps the most
|
|
255
|
-
recent ``window`` steps in full (with string values capped at
|
|
256
|
-
``result_chars``) and reduces every older step to a one-line summary, so
|
|
257
|
-
the prompt stays bounded while no step disappears entirely.
|
|
258
|
-
"""
|
|
259
|
-
steps = list(transcript or [])
|
|
260
|
-
if len(steps) <= window:
|
|
261
|
-
return [_truncate_strings(step, result_chars) for step in steps]
|
|
262
|
-
older, recent = steps[:-window], steps[-window:]
|
|
263
|
-
summarized: List[Dict[str, Any]] = [{
|
|
264
|
-
"summarized_older_steps": len(older),
|
|
265
|
-
"note": "older steps compacted — full detail retained in the run record",
|
|
266
|
-
}]
|
|
267
|
-
for step in older:
|
|
268
|
-
entry: Dict[str, Any] = {"state": step.get("state")}
|
|
269
|
-
for key in ("action", "verdict", "retry_attempt"):
|
|
270
|
-
if step.get(key) is not None:
|
|
271
|
-
entry[key] = step.get(key)
|
|
272
|
-
if step.get("error"):
|
|
273
|
-
entry["error"] = str(step["error"])[:160]
|
|
274
|
-
elif isinstance(step.get("result"), dict):
|
|
275
|
-
entry["ok"] = True
|
|
276
|
-
path = step["result"].get("path") or (step.get("args") or {}).get("path")
|
|
277
|
-
if path:
|
|
278
|
-
entry["path"] = str(path)
|
|
279
|
-
summarized.append(entry)
|
|
280
|
-
summarized.extend(_truncate_strings(step, result_chars) for step in recent)
|
|
281
|
-
return summarized
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
def files_written(
|
|
285
|
-
transcript: List[Dict[str, Any]],
|
|
286
|
-
file_create_actions: FrozenSet[str],
|
|
287
|
-
) -> List[str]:
|
|
288
|
-
"""Ordered unique paths of files this run successfully wrote (review L5).
|
|
289
|
-
|
|
290
|
-
Later executor steps get this as explicit context, so "만들고 이어서
|
|
291
|
-
설명해" multi-step work sees its own output instead of a stale
|
|
292
|
-
workspace picture.
|
|
293
|
-
"""
|
|
294
|
-
seen: List[str] = []
|
|
295
|
-
for step in transcript:
|
|
296
|
-
if step.get("state") != AgentState.EXECUTING.value:
|
|
297
|
-
continue
|
|
298
|
-
if step.get("action") not in file_create_actions:
|
|
299
|
-
continue
|
|
300
|
-
if not isinstance(step.get("result"), dict):
|
|
301
|
-
continue
|
|
302
|
-
path = step["result"].get("path") or (step.get("args") or {}).get("path")
|
|
303
|
-
if path and str(path) not in seen:
|
|
304
|
-
seen.append(str(path))
|
|
305
|
-
return seen
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
def artifact_checklist(
|
|
309
|
-
transcript: List[Dict[str, Any]],
|
|
310
|
-
file_create_actions: FrozenSet[str],
|
|
311
|
-
) -> List[Dict[str, Any]]:
|
|
312
|
-
"""Deterministic artifact facts for the critic (review L4).
|
|
313
|
-
|
|
314
|
-
The critic previously judged file work from prose alone; this surfaces
|
|
315
|
-
the sanitize/repair honesty flags per written file so a repaired
|
|
316
|
-
placeholder can never pass as a fulfilled request unchecked.
|
|
317
|
-
"""
|
|
318
|
-
checklist: List[Dict[str, Any]] = []
|
|
319
|
-
for step in transcript:
|
|
320
|
-
if step.get("state") != AgentState.EXECUTING.value:
|
|
321
|
-
continue
|
|
322
|
-
if step.get("action") not in file_create_actions:
|
|
323
|
-
continue
|
|
324
|
-
if not isinstance(step.get("result"), dict):
|
|
325
|
-
continue
|
|
326
|
-
path = step["result"].get("path") or (step.get("args") or {}).get("path")
|
|
327
|
-
if not path:
|
|
328
|
-
continue
|
|
329
|
-
sanitize_meta = step.get("content_sanitize") or {}
|
|
330
|
-
checklist.append({
|
|
331
|
-
"path": str(path),
|
|
332
|
-
"sanitized": bool(sanitize_meta.get("sanitized")),
|
|
333
|
-
"repaired": bool(sanitize_meta.get("repaired")),
|
|
334
|
-
})
|
|
335
|
-
return checklist
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
# Explicit requirement lines a user writes out: "- 다크모드", "1. 검색 기능",
|
|
339
|
-
# "* dark mode". Free prose is deliberately NOT parsed — a wrong requirement
|
|
340
|
-
# is worse than no requirement.
|
|
341
|
-
_REQUIREMENT_LINE_RE = re.compile(r"^\s*(?:[-*•]|\d+[.)])\s+(.{3,120})$", re.MULTILINE)
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
def requirement_coverage(
|
|
345
|
-
user_message: str,
|
|
346
|
-
transcript: List[Dict[str, Any]],
|
|
347
|
-
file_create_actions: FrozenSet[str],
|
|
348
|
-
) -> Dict[str, Any]:
|
|
349
|
-
"""Did the run produce what the request actually asked for? (review 루프 §2)
|
|
350
|
-
|
|
351
|
-
The critic judged prose against prose, so "make an HTML+CSS+JS todo app"
|
|
352
|
-
could pass with one file written. This is the deterministic half of the
|
|
353
|
-
answer, built from two sources that are honest about their own limits:
|
|
354
|
-
|
|
355
|
-
* **manifest files** — when the request maps to a known multi-file project
|
|
356
|
-
(:func:`infer_project_manifest`), every declared path must have been
|
|
357
|
-
written. A missing manifest file is a *hard* miss: it is not a matter of
|
|
358
|
-
taste whether ``style.css`` exists.
|
|
359
|
-
* **explicit requirement lines** — bullet/numbered lines the user wrote
|
|
360
|
-
out. These are reported to the critic as a checklist but never block on
|
|
361
|
-
their own: matching a feature to a transcript is a judgement call, and
|
|
362
|
-
guessing it wrong would either fake completion or block real work.
|
|
363
|
-
|
|
364
|
-
Returns ``{"files": {...}, "requirements": [...], "missing_files": [...],
|
|
365
|
-
"complete": bool}`` where ``complete`` is false only when a declared
|
|
366
|
-
manifest file is missing.
|
|
367
|
-
"""
|
|
368
|
-
written = files_written(transcript, file_create_actions)
|
|
369
|
-
written_names = {Path(path).name.lower() for path in written}
|
|
370
|
-
manifest = infer_project_manifest(user_message) or {}
|
|
371
|
-
declared = [str(spec.get("path") or "") for spec in manifest.get("files", [])]
|
|
372
|
-
missing = [
|
|
373
|
-
path for path in declared
|
|
374
|
-
if path and Path(path).name.lower() not in written_names
|
|
375
|
-
]
|
|
376
|
-
requirements = [
|
|
377
|
-
line.strip()
|
|
378
|
-
for line in _REQUIREMENT_LINE_RE.findall(str(user_message or ""))
|
|
379
|
-
][:10]
|
|
380
|
-
return {
|
|
381
|
-
"files": {"declared": declared, "written": written},
|
|
382
|
-
"missing_files": missing,
|
|
383
|
-
"requirements": requirements,
|
|
384
|
-
"complete": not missing,
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
def format_requirement_coverage(coverage: Dict[str, Any]) -> str:
|
|
389
|
-
"""Requirement facts for the critic prompt, or "" when there is nothing."""
|
|
390
|
-
lines: List[str] = []
|
|
391
|
-
declared = coverage["files"]["declared"]
|
|
392
|
-
if declared:
|
|
393
|
-
written = set(coverage["files"]["written"])
|
|
394
|
-
lines.append("Requested files (deterministic, from the request):")
|
|
395
|
-
for path in declared:
|
|
396
|
-
got = any(Path(item).name.lower() == Path(path).name.lower() for item in written)
|
|
397
|
-
lines.append(f"- {path}: {'written' if got else 'MISSING'}")
|
|
398
|
-
if coverage["requirements"]:
|
|
399
|
-
lines.append(
|
|
400
|
-
"Requirements the user listed explicitly — check each one against "
|
|
401
|
-
"the artifacts, not against the plan:"
|
|
402
|
-
)
|
|
403
|
-
lines.extend(f"- {item}" for item in coverage["requirements"])
|
|
404
|
-
return "\n\n" + "\n".join(lines) if lines else ""
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
def format_artifact_checklist(checklist: List[Dict[str, Any]]) -> str:
|
|
408
|
-
lines = []
|
|
409
|
-
for item in checklist:
|
|
410
|
-
state = (
|
|
411
|
-
"auto-REPAIRED scaffold" if item["repaired"]
|
|
412
|
-
else ("sanitized model output" if item["sanitized"] else "written as produced")
|
|
413
|
-
)
|
|
414
|
-
lines.append(f"- {item['path']}: {state}")
|
|
415
|
-
return (
|
|
416
|
-
"Artifact checklist (deterministic, from the transcript):\n"
|
|
417
|
-
+ "\n".join(lines)
|
|
418
|
-
+ "\nVerify each artifact actually fulfills the user's request. An "
|
|
419
|
-
"auto-repaired scaffold is NOT completion unless its content "
|
|
420
|
-
"satisfies what was asked."
|
|
421
|
-
)
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
# ── budgets ────────────────────────────────────────────────────────────
|
|
425
|
-
|
|
426
|
-
@dataclass(frozen=True)
|
|
427
|
-
class TranscriptBudget:
|
|
428
|
-
"""Executor/critic prompt shaping caps (review Wave 0.3).
|
|
429
|
-
|
|
430
|
-
``window`` full recent steps for the executor; per-string caps keep tool
|
|
431
|
-
output bodies from dominating either prompt. Overridable through the same
|
|
432
|
-
``Config.from_env`` pattern as :class:`PhaseBudgets`.
|
|
433
|
-
"""
|
|
434
|
-
|
|
435
|
-
window: int = 8
|
|
436
|
-
result_chars: int = 700
|
|
437
|
-
verify_chars: int = 1200
|
|
438
|
-
|
|
439
|
-
@classmethod
|
|
440
|
-
def from_env(cls, env: Optional[Mapping[str, str]] = None) -> "TranscriptBudget":
|
|
441
|
-
from latticeai.core.config import _int
|
|
442
|
-
|
|
443
|
-
if env is None:
|
|
444
|
-
import os
|
|
445
|
-
|
|
446
|
-
env = os.environ
|
|
447
|
-
|
|
448
|
-
def cap(key: str, default: int, floor: int) -> int:
|
|
449
|
-
return max(floor, _int(env, key, default))
|
|
450
|
-
|
|
451
|
-
return cls(
|
|
452
|
-
window=cap("LATTICEAI_AGENT_TRANSCRIPT_WINDOW", cls.window, 2),
|
|
453
|
-
result_chars=cap("LATTICEAI_AGENT_TRANSCRIPT_CHARS", cls.result_chars, 120),
|
|
454
|
-
verify_chars=cap("LATTICEAI_AGENT_VERIFY_CHARS", cls.verify_chars, 200),
|
|
455
|
-
)
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
@dataclass(frozen=True)
|
|
459
|
-
class PhaseBudgets:
|
|
460
|
-
"""Per-phase token budgets for the agent loop.
|
|
461
|
-
|
|
462
|
-
One shared budget let a weak model burn everything on planning prose and
|
|
463
|
-
reach EXECUTE with nothing left. Each role phase now has its own cap, so
|
|
464
|
-
a verbose planner can never starve execution or verification. Defaults
|
|
465
|
-
match the historical hardcoded values; every cap is overridable through
|
|
466
|
-
the ``Config.from_env`` environment pattern.
|
|
467
|
-
"""
|
|
468
|
-
|
|
469
|
-
plan_tokens: int = 1024
|
|
470
|
-
execute_tokens: int = 4096
|
|
471
|
-
verify_tokens: int = 512
|
|
472
|
-
memory_tokens: int = 256
|
|
473
|
-
|
|
474
|
-
@classmethod
|
|
475
|
-
def from_env(cls, env: Optional[Mapping[str, str]] = None) -> "PhaseBudgets":
|
|
476
|
-
from latticeai.core.config import _int
|
|
477
|
-
|
|
478
|
-
if env is None:
|
|
479
|
-
import os
|
|
480
|
-
|
|
481
|
-
env = os.environ
|
|
482
|
-
|
|
483
|
-
def cap(key: str, default: int) -> int:
|
|
484
|
-
# A misconfigured/absurd value must not brick the loop: floor at a
|
|
485
|
-
# budget that still fits one JSON action object.
|
|
486
|
-
return max(128, _int(env, key, default))
|
|
487
|
-
|
|
488
|
-
return cls(
|
|
489
|
-
plan_tokens=cap("LATTICEAI_AGENT_PLAN_TOKENS", cls.plan_tokens),
|
|
490
|
-
execute_tokens=cap("LATTICEAI_AGENT_EXECUTE_TOKENS", cls.execute_tokens),
|
|
491
|
-
verify_tokens=cap("LATTICEAI_AGENT_VERIFY_TOKENS", cls.verify_tokens),
|
|
492
|
-
memory_tokens=cap("LATTICEAI_AGENT_MEMORY_TOKENS", cls.memory_tokens),
|
|
493
|
-
)
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
"""Agent profiles — matching the loop to the model driving it (v9.9.7).
|
|
2
|
-
|
|
3
|
-
The single-agent loop asks a model to emit one strict JSON tool call per turn.
|
|
4
|
-
Large models do that reliably; a 1–4B local model routinely does not, and the
|
|
5
|
-
loop's answer was always the same: repair, correct, retry, and eventually give
|
|
6
|
-
up with nothing written. The counters showed the strain (``model_strain`` in
|
|
7
|
-
the run explanation), but every model still ran the same loop.
|
|
8
|
-
|
|
9
|
-
A profile is that missing dial:
|
|
10
|
-
|
|
11
|
-
* **standard** — today's behaviour, unchanged, for models that can hold a
|
|
12
|
-
tool-call contract.
|
|
13
|
-
* **compact** — for small local models: a shorter transcript window, an
|
|
14
|
-
earlier escalation to naming the valid tools, and — the important one — a
|
|
15
|
-
**direct-path fallback**. When JSON tool calls keep failing, the loop stops
|
|
16
|
-
asking for JSON at all and executes the plan's own file steps, requesting
|
|
17
|
-
only *file content* in plain text. Weak models are bad at tool protocols and
|
|
18
|
-
fine at writing a file; the profile plays to that.
|
|
19
|
-
|
|
20
|
-
Selection is deterministic and inspectable: a model id names its size, so the
|
|
21
|
-
profile is a pure function of the id plus an explicit environment override.
|
|
22
|
-
Nothing here guesses at quality — a model absent from the size heuristic gets
|
|
23
|
-
``standard``, which is the conservative choice (no behaviour change).
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
from __future__ import annotations
|
|
27
|
-
|
|
28
|
-
import re
|
|
29
|
-
from dataclasses import dataclass
|
|
30
|
-
from typing import Mapping, Optional
|
|
31
|
-
|
|
32
|
-
__all__ = ["AgentProfile", "COMPACT", "STANDARD", "profile_for_model", "model_size_b"]
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
@dataclass(frozen=True)
|
|
36
|
-
class AgentProfile:
|
|
37
|
-
"""How hard the loop should work to keep a given model on contract."""
|
|
38
|
-
|
|
39
|
-
name: str
|
|
40
|
-
# Executor transcript window (steps kept in full). Small models drown in
|
|
41
|
-
# long transcripts far sooner than large ones.
|
|
42
|
-
transcript_window: int
|
|
43
|
-
# Format slips tolerated before the run stops retrying.
|
|
44
|
-
parse_failure_budget: int
|
|
45
|
-
# Slip count at which the correction hint starts naming valid tool names.
|
|
46
|
-
escalate_after: int
|
|
47
|
-
# When JSON tool calls are exhausted, execute the plan's file steps
|
|
48
|
-
# directly and ask only for file content in plain text.
|
|
49
|
-
direct_path_fallback: bool
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
STANDARD = AgentProfile(
|
|
53
|
-
name="standard",
|
|
54
|
-
transcript_window=8,
|
|
55
|
-
parse_failure_budget=3,
|
|
56
|
-
escalate_after=2,
|
|
57
|
-
direct_path_fallback=False,
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
COMPACT = AgentProfile(
|
|
61
|
-
name="compact",
|
|
62
|
-
transcript_window=4,
|
|
63
|
-
parse_failure_budget=4,
|
|
64
|
-
escalate_after=1,
|
|
65
|
-
direct_path_fallback=True,
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
_PROFILES = {profile.name: profile for profile in (STANDARD, COMPACT)}
|
|
69
|
-
|
|
70
|
-
# "gemma-3-4b-it-4bit", "qwen2.5-1.5b", "llama-3.2-3B" → parameter count in B.
|
|
71
|
-
# The quantization suffix ("4bit", "8bit") is deliberately excluded: it is not
|
|
72
|
-
# a parameter count, and reading it as one would mislabel every quantized model.
|
|
73
|
-
_SIZE_RE = re.compile(r"(?<![a-z0-9.])(\d+(?:\.\d+)?)\s*b(?![a-z0-9])", re.IGNORECASE)
|
|
74
|
-
_QUANT_RE = re.compile(r"\d+\s*bit", re.IGNORECASE)
|
|
75
|
-
|
|
76
|
-
# At or below this parameter count, the compact profile applies.
|
|
77
|
-
COMPACT_MAX_PARAMS_B = 4.0
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def model_size_b(model_id: str) -> Optional[float]:
|
|
81
|
-
"""Parameter count in billions parsed from a model id, or None.
|
|
82
|
-
|
|
83
|
-
Returns None rather than guessing when the id names no size — an unknown
|
|
84
|
-
model must not be silently downgraded to the compact loop.
|
|
85
|
-
"""
|
|
86
|
-
text = _QUANT_RE.sub(" ", str(model_id or ""))
|
|
87
|
-
sizes = [float(match) for match in _SIZE_RE.findall(text)]
|
|
88
|
-
return min(sizes) if sizes else None
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def profile_for_model(
|
|
92
|
-
model_id: Optional[str], *, env: Optional[Mapping[str, str]] = None
|
|
93
|
-
) -> AgentProfile:
|
|
94
|
-
"""Pick the loop profile for a model.
|
|
95
|
-
|
|
96
|
-
Order: explicit ``LATTICEAI_AGENT_PROFILE`` override → size heuristic →
|
|
97
|
-
``standard``. An unrecognized override name falls through to the
|
|
98
|
-
heuristic rather than failing the run.
|
|
99
|
-
"""
|
|
100
|
-
if env is None:
|
|
101
|
-
import os
|
|
102
|
-
|
|
103
|
-
env = os.environ
|
|
104
|
-
override = str(env.get("LATTICEAI_AGENT_PROFILE") or "").strip().lower()
|
|
105
|
-
if override in _PROFILES:
|
|
106
|
-
return _PROFILES[override]
|
|
107
|
-
size = model_size_b(model_id or "")
|
|
108
|
-
if size is not None and size <= COMPACT_MAX_PARAMS_B:
|
|
109
|
-
return COMPACT
|
|
110
|
-
return STANDARD
|