ltcai 11.5.2 → 11.7.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 +93 -148
- package/bin/ltcai.js +234 -24
- package/docs/CHANGELOG.md +119 -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 +54 -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-BPcVAbOL.js} +1 -1
- package/static/app/assets/AdminConsole-Bw1ATQL0.js +1 -0
- package/static/app/assets/{Brain-3VCSHFcn.js → Brain-CT92Kos0.js} +2 -2
- package/static/app/assets/{BrainHome-Qm8eaztx.js → BrainHome-CFBkt1K_.js} +1 -1
- package/static/app/assets/{BrainSignals-DS9BtKOW.js → BrainSignals-ReLWF2H8.js} +1 -1
- package/static/app/assets/Capture-BsTokYkk.js +1 -0
- package/static/app/assets/{Chronicle-BGvuAchH.js → Chronicle-B6f0T9id.js} +1 -1
- package/static/app/assets/{CommandPalette-Bqhm0Urn.js → CommandPalette-CuvjTv1u.js} +1 -1
- package/static/app/assets/{Library-BV6NnF0a.js → Library-BGJbG9Hd.js} +1 -1
- package/static/app/assets/{LivingBrain-GzenJchP.js → LivingBrain-DGYK_Jsa.js} +1 -1
- package/static/app/assets/{ProductFlow-DEP6-vML.js → ProductFlow-DXBC6brE.js} +1 -1
- package/static/app/assets/{ReviewCard-CNZ7XjWG.js → ReviewCard-HXRle3qq.js} +2 -2
- package/static/app/assets/System-CMHSO9qM.js +1 -0
- package/static/app/assets/arrow-left-BfmkskWx.js +1 -0
- package/static/app/assets/{bot-B_K1Tdmw.js → bot-Cn8bWRuq.js} +1 -1
- package/static/app/assets/{brain-DWyaV1L1.js → brain-CQJberbE.js} +1 -1
- package/static/app/assets/{button-aTn4s84A.js → button-Ct9f2_oT.js} +1 -1
- package/static/app/assets/circle-check-DruOxB-4.js +1 -0
- package/static/app/assets/{circle-pause-xKgeGXkT.js → circle-pause-CmzC_apg.js} +1 -1
- package/static/app/assets/{circle-play-DkT6tYPX.js → circle-play-D8mW2aQ7.js} +1 -1
- package/static/app/assets/{cpu-85xYObUC.js → cpu-DZcdd0PZ.js} +1 -1
- package/static/app/assets/{download-B5Fm7YXo.js → download-bv1KEPGQ.js} +1 -1
- package/static/app/assets/{folder-open-kk2Xa52u.js → folder-open-d-Pip5gr.js} +1 -1
- package/static/app/assets/{hard-drive-DkA3zBW_.js → hard-drive-D20iavUb.js} +1 -1
- package/static/app/assets/index-D9x-kSNy.css +2 -0
- package/static/app/assets/{index-BMPdTmlY.js → index-Do83hDzJ.js} +3 -3
- package/static/app/assets/{input-B0nRf2jO.js → input-BLXVNmj1.js} +1 -1
- package/static/app/assets/{link-2-Dwb4gnTc.js → link-2-BPJOFlAy.js} +1 -1
- package/static/app/assets/{permissionCopy-CQDUBrOZ.js → permissionCopy-ChdJd493.js} +1 -1
- package/static/app/assets/primitives-Cv5tbZBY.js +1 -0
- package/static/app/assets/search-CT9aho2j.js +1 -0
- package/static/app/assets/{share-2-BsrxFglO.js → share-2-YNX_NtMU.js} +1 -1
- package/static/app/assets/{shield-alert-5BStfp2_.js → shield-alert-DuQ3zrVL.js} +1 -1
- package/static/app/assets/{textarea-Cg8IUA-k.js → textarea-DqwLnli4.js} +1 -1
- package/static/app/assets/{useFocusTrap-CYKvE46M.js → useFocusTrap-ZVI98jaW.js} +1 -1
- package/static/app/assets/{useMutation-CSn9t1op.js → useMutation-CVC4qv_D.js} +1 -1
- package/static/app/assets/{useQuery-CY2OI2uy.js → useQuery-C7BeG4HU.js} +1 -1
- package/static/app/assets/{utils-Ddol2RWD.js → utils-CiFtIdZq.js} +1 -1
- package/static/app/assets/{workspace-BqDwOz_p.js → workspace-DQz9vIId.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/AdminConsole-cf4npybT.js +0 -1
- package/static/app/assets/Capture-DiQ219jW.js +0 -1
- package/static/app/assets/System-CieofHQa.js +0 -1
- 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/primitives-SNp0LRJz.js +0 -1
- package/static/app/assets/search-BcHqkjoy.js +0 -1
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
"""Role prompts for the Lattice multi-role agent runtime."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing import Any, Optional
|
|
6
|
-
|
|
7
|
-
from latticeai.core.tool_registry import TOOL_CATALOG_BRIEF
|
|
8
|
-
|
|
9
|
-
PLANNER_PROMPT = """You are the PLANNER role in Lattice AI's multi-role agent harness.
|
|
10
|
-
Your ONLY job: analyze the request and produce a structured execution plan.
|
|
11
|
-
You do NOT call tools or write code.
|
|
12
|
-
|
|
13
|
-
Respond with exactly ONE JSON object (no markdown, no fences):
|
|
14
|
-
{
|
|
15
|
-
"action": "plan",
|
|
16
|
-
"state": "PLANNING",
|
|
17
|
-
"goal": "one-sentence goal in the user's language",
|
|
18
|
-
"steps": [
|
|
19
|
-
{"id": 1, "description": "what this step does", "action": "expected_tool", "purpose": "why needed"}
|
|
20
|
-
],
|
|
21
|
-
"requires_approval": true,
|
|
22
|
-
"rollback_strategy": "git",
|
|
23
|
-
"estimated_steps": 3
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
Rules:
|
|
27
|
-
- requires_approval = true if ANY step uses write/exec tools (edit_file, write_file, run_command, etc.)
|
|
28
|
-
- rollback_strategy = "git" if steps modify existing files; "none" otherwise
|
|
29
|
-
- Keep steps realistic: 2-4 for simple tasks, up to 10 for complex ones
|
|
30
|
-
- Do NOT specify full tool args -- that is the Executor's job
|
|
31
|
-
|
|
32
|
-
Available tools:""" + TOOL_CATALOG_BRIEF
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
EXECUTOR_PROMPT = """You are the EXECUTOR role in Lattice AI's multi-role agent harness.
|
|
36
|
-
You have a plan from the Planner. Execute it step by step using exactly one tool per response.
|
|
37
|
-
|
|
38
|
-
You think and act like a senior software engineer:
|
|
39
|
-
- Read (read_file, grep) BEFORE editing -- never guess at file contents
|
|
40
|
-
- Prefer edit_file over write_file for existing files
|
|
41
|
-
- Keep changes small and precise
|
|
42
|
-
- Verify after changes with build_project or run_command
|
|
43
|
-
|
|
44
|
-
Respond with exactly ONE JSON object per step:
|
|
45
|
-
{"thoughts": "what you learned / why this next action", "action": "tool_name", "args": {...}}
|
|
46
|
-
|
|
47
|
-
When writing a file (write_file), args.content must be the COMPLETE raw file
|
|
48
|
-
content: no Markdown fences, no commentary, valid for the file's extension
|
|
49
|
-
(an .html file starts with <!DOCTYPE html> and ends with </html>; a .json
|
|
50
|
-
file must parse as strict JSON).
|
|
51
|
-
|
|
52
|
-
When the task is fully done AND a tool result in this run confirms it:
|
|
53
|
-
{"thoughts": "verified", "action": "final", "message": "한국어로 무엇을 했고 어디서 검증했는지 요약"}
|
|
54
|
-
|
|
55
|
-
ANTI-PATTERNS (will halt the loop):
|
|
56
|
-
- Editing without reading first -> read_file + grep BEFORE edit_file
|
|
57
|
-
- Repeating the same action+args -> check the transcript
|
|
58
|
-
- Claiming done without a verification tool result in transcript
|
|
59
|
-
- Hallucinating imports or file paths that were never confirmed by a tool result
|
|
60
|
-
|
|
61
|
-
Available tools:""" + TOOL_CATALOG_BRIEF
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
CRITIC_PROMPT = """You are the CRITIC / REVIEWER role in Lattice AI's multi-role agent harness.
|
|
65
|
-
Review the execution transcript and determine whether the goal was achieved.
|
|
66
|
-
|
|
67
|
-
Respond with exactly ONE JSON object:
|
|
68
|
-
{
|
|
69
|
-
"action": "verdict",
|
|
70
|
-
"state": "VERIFYING",
|
|
71
|
-
"verdict": "PASS",
|
|
72
|
-
"reason": "why you think it passed or failed (cite specific tool results)",
|
|
73
|
-
"corrections": [],
|
|
74
|
-
"confidence": 0.95,
|
|
75
|
-
"next_state": "DONE"
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
verdict: "PASS" | "FAIL"
|
|
79
|
-
next_state:
|
|
80
|
-
"DONE" -- task succeeded; finish
|
|
81
|
-
"EXECUTING" -- task failed but corrections can fix it (use corrections field for retry)
|
|
82
|
-
"ROLLBACK" -- task failed AND file changes should be undone
|
|
83
|
-
|
|
84
|
-
Criteria for PASS: a tool result in the transcript explicitly confirms success.
|
|
85
|
-
Be strict. Claiming done without evidence = FAIL."""
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
MEMORY_UPDATER_PROMPT = """You are the MEMORY UPDATER role in Lattice AI's multi-role agent harness.
|
|
89
|
-
After a completed task, extract reusable learnings.
|
|
90
|
-
|
|
91
|
-
Respond with exactly ONE JSON object:
|
|
92
|
-
{
|
|
93
|
-
"action": "memory",
|
|
94
|
-
"state": "DONE",
|
|
95
|
-
"learnings": ["one concise fact about this codebase or task"],
|
|
96
|
-
"artifacts": ["relative/path/to/created_or_modified_file"],
|
|
97
|
-
"save_to_knowledge": false
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
Rules:
|
|
101
|
-
- max 5 learnings, one sentence each
|
|
102
|
-
- save_to_knowledge = true only if learnings are genuinely useful across future sessions
|
|
103
|
-
- artifacts = files the Executor actually created or modified (from transcript)
|
|
104
|
-
"""
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
AGENT_SYSTEM_PROMPT = EXECUTOR_PROMPT
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
# ── file-writing hints (v11.1.0) ────────────────────────────────────────────
|
|
111
|
-
#
|
|
112
|
-
# A 1–4B local model does not fail at *writing a file*; it fails at deciding
|
|
113
|
-
# it is allowed to write one yet. The compact profile already gives that model
|
|
114
|
-
# a shorter transcript and an earlier escalation; these hints give it the two
|
|
115
|
-
# things the transcripts showed it missing most: a concrete order of
|
|
116
|
-
# operations, and permission to write the file on the very next turn.
|
|
117
|
-
#
|
|
118
|
-
# The strings are deliberately additive — ``EXECUTOR_PROMPT`` is untouched, so
|
|
119
|
-
# every caller that wants today's prompt keeps getting exactly today's prompt.
|
|
120
|
-
|
|
121
|
-
FILE_TASK_HINTS = """
|
|
122
|
-
FILE TASKS — how to actually finish one:
|
|
123
|
-
- If the request names a file to create, your FIRST action is write_file with
|
|
124
|
-
the complete content. Do not read, list, or plan around it first.
|
|
125
|
-
- args.path is a workspace-relative path (e.g. "report.md"), never absolute.
|
|
126
|
-
- args.content is the WHOLE file, not a fragment and not a description of it.
|
|
127
|
-
- After the write result appears in the transcript, verify once (read_file),
|
|
128
|
-
then finish with {"action": "final", ...}.
|
|
129
|
-
"""
|
|
130
|
-
|
|
131
|
-
COMPACT_FILE_TASK_HINTS = """
|
|
132
|
-
FILE TASKS (short form):
|
|
133
|
-
1. write_file {"path": "<name>", "content": "<the whole file>"}
|
|
134
|
-
2. read_file to confirm it exists
|
|
135
|
-
3. {"action": "final", "message": "..."}
|
|
136
|
-
Write the file on your next turn. Do not explain first.
|
|
137
|
-
"""
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
def file_task_hints(profile: Any = None) -> str:
|
|
141
|
-
"""The file-writing hints for a loop profile.
|
|
142
|
-
|
|
143
|
-
``profile`` is an :class:`~latticeai.core.agent_profiles.AgentProfile`, its
|
|
144
|
-
name, or ``None``. The compact profile gets the numbered short form (small
|
|
145
|
-
models follow a list better than prose); everything else gets the standard
|
|
146
|
-
hints, so a strong model's prompt grows by four sentences and no rules.
|
|
147
|
-
"""
|
|
148
|
-
name = getattr(profile, "name", profile)
|
|
149
|
-
if str(name or "").strip().lower() == "compact":
|
|
150
|
-
return COMPACT_FILE_TASK_HINTS.strip()
|
|
151
|
-
return FILE_TASK_HINTS.strip()
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
def executor_prompt_for(
|
|
155
|
-
base: str = EXECUTOR_PROMPT,
|
|
156
|
-
*,
|
|
157
|
-
profile: Any = None,
|
|
158
|
-
self_model_summary: Optional[str] = None,
|
|
159
|
-
) -> str:
|
|
160
|
-
"""``base`` plus the file hints, and the Self-Model summary when there is one.
|
|
161
|
-
|
|
162
|
-
The injection rule is the same one the context builder follows: an empty
|
|
163
|
-
(or missing) Self-Model adds nothing at all — no header, no placeholder —
|
|
164
|
-
so a Brain that knows nothing about its owner produces the prompt it
|
|
165
|
-
always produced.
|
|
166
|
-
"""
|
|
167
|
-
parts = [str(base or "").rstrip(), file_task_hints(profile)]
|
|
168
|
-
summary = str(self_model_summary or "").strip()
|
|
169
|
-
if summary:
|
|
170
|
-
parts.append(f"ABOUT THE USER (from their Brain — honour it, do not restate it):\n{summary}")
|
|
171
|
-
return "\n\n".join(parts)
|
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
"""Agent Registry — registration, discovery, metadata, versioning, capabilities.
|
|
2
|
-
|
|
3
|
-
Part 2 of the v3.2.0 platform. Before this module the agent roster was derived
|
|
4
|
-
ad-hoc from the hardcoded ``AGENT_ROLES`` tuple wherever it was needed. The
|
|
5
|
-
registry makes every agent — built-in role and user-registered custom agent — a
|
|
6
|
-
first-class entry with stable metadata: ``id``, ``type``, ``version``,
|
|
7
|
-
``capabilities``, ``description`` and a mutable ``config``. The HTTP surface and
|
|
8
|
-
the /app views read this registry instead of any hardcoded list.
|
|
9
|
-
|
|
10
|
-
Built-in role agents are projected from the single source of truth in
|
|
11
|
-
:mod:`lattice_brain.runtime.multi_agent` (so adding a role there flows through here).
|
|
12
|
-
Custom agents and config overrides are persisted to
|
|
13
|
-
``data_dir/agent_registry.json``.
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
from __future__ import annotations
|
|
17
|
-
|
|
18
|
-
import json
|
|
19
|
-
import os
|
|
20
|
-
import tempfile
|
|
21
|
-
from pathlib import Path
|
|
22
|
-
from typing import Any, Dict, List, Optional
|
|
23
|
-
|
|
24
|
-
from lattice_brain.runtime.multi_agent import (
|
|
25
|
-
AGENT_ROLES,
|
|
26
|
-
CORE_PIPELINE,
|
|
27
|
-
MULTI_AGENT_VERSION,
|
|
28
|
-
ROLE_AGENT_IDS,
|
|
29
|
-
)
|
|
30
|
-
from latticeai.core.quiet import quiet
|
|
31
|
-
|
|
32
|
-
from .timeutil import now_iso as _now
|
|
33
|
-
|
|
34
|
-
AGENT_TYPES = ("planner", "researcher", "executor", "reviewer", "release", "custom")
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
# Capabilities + descriptions for the built-in role agents. Kept here as the
|
|
38
|
-
# registry's metadata projection of the roles defined in multi_agent.py.
|
|
39
|
-
ROLE_META: Dict[str, Dict[str, Any]] = {
|
|
40
|
-
"researcher": {
|
|
41
|
-
"description": "Gathers workspace context, memory, and graph signal for the goal.",
|
|
42
|
-
"capabilities": ["context-retrieval", "memory-recall", "graph-read", "hybrid-search"],
|
|
43
|
-
},
|
|
44
|
-
"planner": {
|
|
45
|
-
"description": "Decomposes the goal into an ordered, bounded, reviewable plan.",
|
|
46
|
-
"capabilities": ["task-decomposition", "plan-review", "delegation"],
|
|
47
|
-
},
|
|
48
|
-
"executor": {
|
|
49
|
-
"description": "Executes each planned step, invoking tools, workflows, and plugins.",
|
|
50
|
-
"capabilities": ["tool-use", "workflow-run", "plugin-run", "file-write"],
|
|
51
|
-
},
|
|
52
|
-
"reviewer": {
|
|
53
|
-
"description": "Reviews executed work and approves, rejects, or requests a retry.",
|
|
54
|
-
"capabilities": ["verification", "retry-control", "approval"],
|
|
55
|
-
},
|
|
56
|
-
"release": {
|
|
57
|
-
"description": "Finalizes and summarizes the approved outcome.",
|
|
58
|
-
"capabilities": ["summarize", "finalize"],
|
|
59
|
-
},
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
class AgentRegistry:
|
|
64
|
-
"""Persisted registry of built-in role agents + user-registered agents."""
|
|
65
|
-
|
|
66
|
-
def __init__(self, path: Path):
|
|
67
|
-
self.path = Path(path)
|
|
68
|
-
self._state: Dict[str, Any] = self._load()
|
|
69
|
-
|
|
70
|
-
# ── persistence ───────────────────────────────────────────────────────
|
|
71
|
-
def _load(self) -> Dict[str, Any]:
|
|
72
|
-
if self.path.exists():
|
|
73
|
-
try:
|
|
74
|
-
with open(self.path, "r", encoding="utf-8") as fh:
|
|
75
|
-
data = json.load(fh)
|
|
76
|
-
if isinstance(data, dict):
|
|
77
|
-
data.setdefault("custom", [])
|
|
78
|
-
data.setdefault("config_overrides", {})
|
|
79
|
-
return data
|
|
80
|
-
except Exception:
|
|
81
|
-
quiet()
|
|
82
|
-
return {"custom": [], "config_overrides": {}}
|
|
83
|
-
|
|
84
|
-
def _save(self) -> None:
|
|
85
|
-
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
86
|
-
fd, tmp = tempfile.mkstemp(dir=str(self.path.parent), suffix=".tmp")
|
|
87
|
-
try:
|
|
88
|
-
with os.fdopen(fd, "w", encoding="utf-8") as fh:
|
|
89
|
-
json.dump(self._state, fh, ensure_ascii=False, indent=2)
|
|
90
|
-
os.replace(tmp, self.path)
|
|
91
|
-
finally:
|
|
92
|
-
if os.path.exists(tmp):
|
|
93
|
-
os.unlink(tmp)
|
|
94
|
-
|
|
95
|
-
# ── projection ────────────────────────────────────────────────────────
|
|
96
|
-
def _builtin_agents(self) -> List[Dict[str, Any]]:
|
|
97
|
-
overrides = self._state.get("config_overrides", {})
|
|
98
|
-
agents: List[Dict[str, Any]] = []
|
|
99
|
-
for role in AGENT_ROLES:
|
|
100
|
-
meta = ROLE_META.get(role, {"description": "", "capabilities": []})
|
|
101
|
-
agent_id = ROLE_AGENT_IDS.get(role, f"agent:{role}")
|
|
102
|
-
handoffs: List[str] = []
|
|
103
|
-
if role == "planner":
|
|
104
|
-
handoffs = [ROLE_AGENT_IDS["executor"]]
|
|
105
|
-
elif role == "executor":
|
|
106
|
-
handoffs = [ROLE_AGENT_IDS["reviewer"]]
|
|
107
|
-
ov = overrides.get(agent_id, {})
|
|
108
|
-
agents.append({
|
|
109
|
-
"id": agent_id,
|
|
110
|
-
"name": role.capitalize(),
|
|
111
|
-
"type": role,
|
|
112
|
-
"version": MULTI_AGENT_VERSION,
|
|
113
|
-
"description": meta["description"],
|
|
114
|
-
"capabilities": list(meta["capabilities"]),
|
|
115
|
-
"handoffs": handoffs,
|
|
116
|
-
"in_default_pipeline": role in CORE_PIPELINE,
|
|
117
|
-
"source": "builtin",
|
|
118
|
-
"removable": False,
|
|
119
|
-
"enabled": bool(ov.get("enabled", True)),
|
|
120
|
-
"config": ov.get("config", {}),
|
|
121
|
-
})
|
|
122
|
-
return agents
|
|
123
|
-
|
|
124
|
-
def _custom_agents(self) -> List[Dict[str, Any]]:
|
|
125
|
-
out: List[Dict[str, Any]] = []
|
|
126
|
-
for entry in self._state.get("custom", []):
|
|
127
|
-
agent = dict(entry)
|
|
128
|
-
agent["source"] = "user"
|
|
129
|
-
agent["removable"] = True
|
|
130
|
-
agent.setdefault("enabled", True)
|
|
131
|
-
agent.setdefault("handoffs", [])
|
|
132
|
-
out.append(agent)
|
|
133
|
-
return out
|
|
134
|
-
|
|
135
|
-
def all(self) -> List[Dict[str, Any]]:
|
|
136
|
-
return self._builtin_agents() + self._custom_agents()
|
|
137
|
-
|
|
138
|
-
# ── reads ─────────────────────────────────────────────────────────────
|
|
139
|
-
def list(self, agent_type: Optional[str] = None) -> Dict[str, Any]:
|
|
140
|
-
agents = self.all()
|
|
141
|
-
if agent_type:
|
|
142
|
-
agents = [a for a in agents if a["type"] == agent_type]
|
|
143
|
-
counts: Dict[str, int] = {}
|
|
144
|
-
for a in self.all():
|
|
145
|
-
counts[a["type"]] = counts.get(a["type"], 0) + 1
|
|
146
|
-
return {
|
|
147
|
-
"agents": agents,
|
|
148
|
-
"types": list(AGENT_TYPES),
|
|
149
|
-
"counts": counts,
|
|
150
|
-
"total": len(agents),
|
|
151
|
-
"version": MULTI_AGENT_VERSION,
|
|
152
|
-
"default_pipeline": list(CORE_PIPELINE),
|
|
153
|
-
"generated_at": _now(),
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
def get(self, agent_id: str) -> Optional[Dict[str, Any]]:
|
|
157
|
-
return next((a for a in self.all() if a["id"] == agent_id), None)
|
|
158
|
-
|
|
159
|
-
def capabilities(self) -> Dict[str, List[str]]:
|
|
160
|
-
"""Inverted index: capability -> [agent_id, …]."""
|
|
161
|
-
index: Dict[str, List[str]] = {}
|
|
162
|
-
for a in self.all():
|
|
163
|
-
for cap in a.get("capabilities", []):
|
|
164
|
-
index.setdefault(cap, []).append(a["id"])
|
|
165
|
-
return index
|
|
166
|
-
|
|
167
|
-
def discover(self, capability: str) -> List[Dict[str, Any]]:
|
|
168
|
-
cap = str(capability or "").lower().strip()
|
|
169
|
-
return [a for a in self.all() if any(cap == c.lower() for c in a.get("capabilities", []))]
|
|
170
|
-
|
|
171
|
-
# ── mutations ─────────────────────────────────────────────────────────
|
|
172
|
-
def register(
|
|
173
|
-
self,
|
|
174
|
-
*,
|
|
175
|
-
name: str,
|
|
176
|
-
agent_type: str = "custom",
|
|
177
|
-
description: str = "",
|
|
178
|
-
capabilities: Optional[List[str]] = None,
|
|
179
|
-
config: Optional[Dict[str, Any]] = None,
|
|
180
|
-
version: str = "1.0.0",
|
|
181
|
-
) -> Dict[str, Any]:
|
|
182
|
-
if not str(name).strip():
|
|
183
|
-
raise ValueError("name is required")
|
|
184
|
-
if agent_type not in AGENT_TYPES:
|
|
185
|
-
raise ValueError(f"type must be one of {', '.join(AGENT_TYPES)}")
|
|
186
|
-
slug = str(name).strip().lower().replace(" ", "-")
|
|
187
|
-
agent_id = f"agent:custom:{slug}"
|
|
188
|
-
existing = {c["id"] for c in self._state.get("custom", [])}
|
|
189
|
-
if agent_id in existing:
|
|
190
|
-
agent_id = f"agent:custom:{slug}-{len(existing) + 1}"
|
|
191
|
-
entry = {
|
|
192
|
-
"id": agent_id,
|
|
193
|
-
"name": str(name).strip(),
|
|
194
|
-
"type": agent_type,
|
|
195
|
-
"version": str(version or "1.0.0"),
|
|
196
|
-
"description": str(description or "").strip(),
|
|
197
|
-
"capabilities": list(capabilities or []),
|
|
198
|
-
"config": dict(config or {}),
|
|
199
|
-
"enabled": True,
|
|
200
|
-
"created_at": _now(),
|
|
201
|
-
}
|
|
202
|
-
self._state.setdefault("custom", []).append(entry)
|
|
203
|
-
self._save()
|
|
204
|
-
return entry
|
|
205
|
-
|
|
206
|
-
def update_config(self, agent_id: str, config: Dict[str, Any], *, enabled: Optional[bool] = None) -> Dict[str, Any]:
|
|
207
|
-
if self.get(agent_id) is None:
|
|
208
|
-
raise KeyError(agent_id)
|
|
209
|
-
if agent_id.startswith("agent:custom:"):
|
|
210
|
-
for entry in self._state.get("custom", []):
|
|
211
|
-
if entry["id"] == agent_id:
|
|
212
|
-
entry["config"] = dict(config or {})
|
|
213
|
-
if enabled is not None:
|
|
214
|
-
entry["enabled"] = bool(enabled)
|
|
215
|
-
entry["updated_at"] = _now()
|
|
216
|
-
else:
|
|
217
|
-
ov = self._state.setdefault("config_overrides", {}).setdefault(agent_id, {})
|
|
218
|
-
ov["config"] = dict(config or {})
|
|
219
|
-
if enabled is not None:
|
|
220
|
-
ov["enabled"] = bool(enabled)
|
|
221
|
-
self._save()
|
|
222
|
-
return self.get(agent_id) # type: ignore[return-value]
|
|
223
|
-
|
|
224
|
-
def remove(self, agent_id: str) -> Dict[str, Any]:
|
|
225
|
-
if not agent_id.startswith("agent:custom:"):
|
|
226
|
-
raise ValueError("Built-in role agents cannot be removed; disable them via config instead.")
|
|
227
|
-
before = len(self._state.get("custom", []))
|
|
228
|
-
self._state["custom"] = [c for c in self._state.get("custom", []) if c["id"] != agent_id]
|
|
229
|
-
if len(self._state["custom"]) == before:
|
|
230
|
-
raise KeyError(agent_id)
|
|
231
|
-
self._save()
|
|
232
|
-
return {"removed": agent_id}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
"""The agent loop's state vocabulary.
|
|
2
|
-
|
|
3
|
-
Its own module because both sides of the runtime need it and neither can own
|
|
4
|
-
it: :mod:`latticeai.core.agent` holds the state machine, and
|
|
5
|
-
:mod:`latticeai.core.agent_helpers` holds the pure functions the machine calls.
|
|
6
|
-
If the enum lived in ``agent``, the helpers would have to import the module
|
|
7
|
-
that imports them — so they would fall back to writing ``"EXECUTING"`` as a
|
|
8
|
-
bare string, and a rename of the enum value would silently stop matching.
|
|
9
|
-
|
|
10
|
-
Both names stay importable from :mod:`latticeai.core.agent`, which is where
|
|
11
|
-
every existing caller expects them.
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
from __future__ import annotations
|
|
15
|
-
|
|
16
|
-
from enum import Enum
|
|
17
|
-
from typing import FrozenSet
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class AgentState(str, Enum):
|
|
21
|
-
IDLE = "IDLE"
|
|
22
|
-
PLANNING = "PLANNING"
|
|
23
|
-
WAITING_APPROVAL = "WAITING_APPROVAL"
|
|
24
|
-
EXECUTING = "EXECUTING"
|
|
25
|
-
VERIFYING = "VERIFYING"
|
|
26
|
-
FAILED = "FAILED"
|
|
27
|
-
ROLLBACK = "ROLLBACK"
|
|
28
|
-
# Terminal, non-success: the run ended but completion could not be
|
|
29
|
-
# verified (critic unavailable/unparseable, or a PASS with no execution
|
|
30
|
-
# evidence). Never presented as success — the user must check the result.
|
|
31
|
-
NEEDS_REVIEW = "NEEDS_REVIEW"
|
|
32
|
-
DONE = "DONE"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
# Terminal states — the agent loop exits when reaching one of these
|
|
36
|
-
AGENT_TERMINAL_STATES: FrozenSet[AgentState] = frozenset(
|
|
37
|
-
{AgentState.DONE, AgentState.FAILED, AgentState.NEEDS_REVIEW}
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
__all__ = ["AgentState", "AGENT_TERMINAL_STATES"]
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
"""Structured observability for the single-agent reasoning loop (v9.6.0).
|
|
2
|
-
|
|
3
|
-
The PLAN → EXECUTE → VERIFY state machine in :mod:`latticeai.core.agent`
|
|
4
|
-
already keeps a human-readable transcript, but the transcript mixes model
|
|
5
|
-
output, tool results, and control decisions into one list — you cannot ask
|
|
6
|
-
"how many parse failures were recovered?" or "which repairs did the weak
|
|
7
|
-
model need?" without re-parsing it.
|
|
8
|
-
|
|
9
|
-
:class:`LoopTrace` is the machine-readable side channel: every phase records
|
|
10
|
-
typed events (llm_call, parse_error, repair, correction, tool call outcome,
|
|
11
|
-
blocked action, retry, rollback), and :meth:`LoopTrace.summary` reduces them
|
|
12
|
-
to the counters an evaluation harness or the API response can consume
|
|
13
|
-
directly. The trace is pure data — no I/O, no clock dependency beyond an
|
|
14
|
-
injectable timestamp function — so unit tests and the deterministic agent
|
|
15
|
-
evaluation harness can assert on it exactly.
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
from __future__ import annotations
|
|
19
|
-
|
|
20
|
-
from typing import Any, Callable, Dict, List, Optional
|
|
21
|
-
|
|
22
|
-
from latticeai.core.timeutil import now_iso
|
|
23
|
-
|
|
24
|
-
_MAX_EVENTS = 500
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class LoopTrace:
|
|
28
|
-
"""Typed event stream + summary counters for one agent run."""
|
|
29
|
-
|
|
30
|
-
def __init__(self, clock: Optional[Callable[[], str]] = None) -> None:
|
|
31
|
-
self._clock = clock or now_iso
|
|
32
|
-
self.events: List[Dict[str, Any]] = []
|
|
33
|
-
self.truncated = 0
|
|
34
|
-
|
|
35
|
-
def record(self, phase: str, kind: str, **details: Any) -> None:
|
|
36
|
-
if len(self.events) >= _MAX_EVENTS:
|
|
37
|
-
self.truncated += 1
|
|
38
|
-
return
|
|
39
|
-
event: Dict[str, Any] = {"phase": phase, "kind": kind, "at": self._clock()}
|
|
40
|
-
for key, value in details.items():
|
|
41
|
-
if value is not None:
|
|
42
|
-
event[key] = value
|
|
43
|
-
self.events.append(event)
|
|
44
|
-
|
|
45
|
-
# ── typed helpers keep call sites terse and the vocabulary closed ────
|
|
46
|
-
|
|
47
|
-
def llm_call(self, phase: str, *, model: Optional[str] = None) -> None:
|
|
48
|
-
self.record(phase, "llm_call", model=model)
|
|
49
|
-
|
|
50
|
-
def parse_error(self, phase: str, *, error: str, recovered: bool) -> None:
|
|
51
|
-
self.record(phase, "parse_error", error=error[:200], recovered=recovered)
|
|
52
|
-
|
|
53
|
-
def repair(self, phase: str, *, repairs: List[str]) -> None:
|
|
54
|
-
if repairs:
|
|
55
|
-
self.record(phase, "repair", repairs=list(repairs))
|
|
56
|
-
|
|
57
|
-
def correction(self, phase: str, *, hint: str) -> None:
|
|
58
|
-
self.record(phase, "correction", hint=hint[:200])
|
|
59
|
-
|
|
60
|
-
def tool(self, phase: str, *, name: str, outcome: str, risk: Optional[str] = None) -> None:
|
|
61
|
-
self.record(phase, "tool", name=name, outcome=outcome, risk=risk)
|
|
62
|
-
|
|
63
|
-
def decision(self, phase: str, *, decision: str, **details: Any) -> None:
|
|
64
|
-
self.record(phase, "decision", decision=decision, **details)
|
|
65
|
-
|
|
66
|
-
def retry(self, phase: str, *, attempt: int) -> None:
|
|
67
|
-
self.record(phase, "retry", attempt=attempt)
|
|
68
|
-
|
|
69
|
-
# ── reduction ────────────────────────────────────────────────────────
|
|
70
|
-
|
|
71
|
-
def summary(self) -> Dict[str, Any]:
|
|
72
|
-
counts: Dict[str, int] = {}
|
|
73
|
-
tool_outcomes: Dict[str, int] = {}
|
|
74
|
-
repairs: Dict[str, int] = {}
|
|
75
|
-
parse_errors = 0
|
|
76
|
-
parse_recovered = 0
|
|
77
|
-
for event in self.events:
|
|
78
|
-
kind = event["kind"]
|
|
79
|
-
counts[kind] = counts.get(kind, 0) + 1
|
|
80
|
-
if kind == "tool":
|
|
81
|
-
outcome = str(event.get("outcome") or "unknown")
|
|
82
|
-
tool_outcomes[outcome] = tool_outcomes.get(outcome, 0) + 1
|
|
83
|
-
elif kind == "parse_error":
|
|
84
|
-
parse_errors += 1
|
|
85
|
-
if event.get("recovered"):
|
|
86
|
-
parse_recovered += 1
|
|
87
|
-
elif kind == "repair":
|
|
88
|
-
for name in event.get("repairs") or []:
|
|
89
|
-
repairs[name] = repairs.get(name, 0) + 1
|
|
90
|
-
return {
|
|
91
|
-
"events": len(self.events),
|
|
92
|
-
"truncated_events": self.truncated,
|
|
93
|
-
"kind_counts": counts,
|
|
94
|
-
"llm_calls": counts.get("llm_call", 0),
|
|
95
|
-
"parse_errors": parse_errors,
|
|
96
|
-
"parse_recovered": parse_recovered,
|
|
97
|
-
"corrections": counts.get("correction", 0),
|
|
98
|
-
"retries": counts.get("retry", 0),
|
|
99
|
-
"tool_outcomes": tool_outcomes,
|
|
100
|
-
"repairs": repairs,
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
__all__ = ["LoopTrace"]
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
"""Conversation artifact ledger — closing the re-search loop (v9.9.6).
|
|
2
|
-
|
|
3
|
-
A run writes ``index.html``, the file is queued for ingestion, and the user
|
|
4
|
-
immediately asks "이제 그 파일에 다크모드 넣어줘". Indexing (chunking, embedding,
|
|
5
|
-
graph writes) is asynchronous, so retrieval may not know about that file yet —
|
|
6
|
-
the next turn could plan as if it had never been created.
|
|
7
|
-
|
|
8
|
-
This ledger is the deterministic bridge: the agent path records what it just
|
|
9
|
-
wrote, and the context assembler injects it as a high-priority section. It is
|
|
10
|
-
intentionally **process-local and bounded**:
|
|
11
|
-
|
|
12
|
-
* it answers "what did *this conversation* just make?", a question whose
|
|
13
|
-
useful lifetime is minutes;
|
|
14
|
-
* by the time a restart loses it, the ingestion pipeline has indexed those
|
|
15
|
-
files and normal retrieval answers better than a ledger would;
|
|
16
|
-
* nothing downstream may treat it as durable storage — the Brain remains the
|
|
17
|
-
single durable home for knowledge.
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
from __future__ import annotations
|
|
21
|
-
|
|
22
|
-
import threading
|
|
23
|
-
from collections import OrderedDict
|
|
24
|
-
from typing import Any, Dict, List, Optional, Sequence
|
|
25
|
-
|
|
26
|
-
from latticeai.core.timeutil import now_iso
|
|
27
|
-
|
|
28
|
-
__all__ = ["ArtifactLedger"]
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class ArtifactLedger:
|
|
32
|
-
"""Bounded, thread-safe map of conversation → recently written artifacts."""
|
|
33
|
-
|
|
34
|
-
def __init__(
|
|
35
|
-
self,
|
|
36
|
-
*,
|
|
37
|
-
max_conversations: int = 200,
|
|
38
|
-
max_per_conversation: int = 25,
|
|
39
|
-
) -> None:
|
|
40
|
-
self._lock = threading.Lock()
|
|
41
|
-
self._entries: "OrderedDict[tuple, List[Dict[str, Any]]]" = OrderedDict()
|
|
42
|
-
self._max_conversations = max(1, int(max_conversations))
|
|
43
|
-
self._max_per_conversation = max(1, int(max_per_conversation))
|
|
44
|
-
|
|
45
|
-
@staticmethod
|
|
46
|
-
def _key(
|
|
47
|
-
user_email: Optional[str],
|
|
48
|
-
conversation_id: Optional[str],
|
|
49
|
-
workspace_id: Optional[str],
|
|
50
|
-
) -> tuple:
|
|
51
|
-
return (
|
|
52
|
-
str(user_email or ""),
|
|
53
|
-
str(conversation_id or "default"),
|
|
54
|
-
str(workspace_id or ""),
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
def record(
|
|
58
|
-
self,
|
|
59
|
-
paths: Sequence[Any],
|
|
60
|
-
*,
|
|
61
|
-
user_email: Optional[str] = None,
|
|
62
|
-
conversation_id: Optional[str] = None,
|
|
63
|
-
workspace_id: Optional[str] = None,
|
|
64
|
-
run_id: str = "",
|
|
65
|
-
) -> List[Dict[str, Any]]:
|
|
66
|
-
"""Note artifacts this conversation just produced. Never raises."""
|
|
67
|
-
cleaned: List[str] = []
|
|
68
|
-
for entry in list(paths or []):
|
|
69
|
-
path = entry if isinstance(entry, str) else (entry or {}).get("path")
|
|
70
|
-
path = str(path or "").strip()
|
|
71
|
-
if path and path not in cleaned:
|
|
72
|
-
cleaned.append(path)
|
|
73
|
-
if not cleaned:
|
|
74
|
-
return []
|
|
75
|
-
stamp = now_iso()
|
|
76
|
-
key = self._key(user_email, conversation_id, workspace_id)
|
|
77
|
-
with self._lock:
|
|
78
|
-
existing = self._entries.pop(key, [])
|
|
79
|
-
by_path = {item["path"]: item for item in existing}
|
|
80
|
-
for path in cleaned:
|
|
81
|
-
by_path[path] = {"path": path, "at": stamp, "run_id": str(run_id or "")}
|
|
82
|
-
merged = sorted(by_path.values(), key=lambda item: item["at"])
|
|
83
|
-
merged = merged[-self._max_per_conversation :]
|
|
84
|
-
self._entries[key] = merged
|
|
85
|
-
while len(self._entries) > self._max_conversations:
|
|
86
|
-
self._entries.popitem(last=False)
|
|
87
|
-
return list(merged)
|
|
88
|
-
|
|
89
|
-
def recent(
|
|
90
|
-
self,
|
|
91
|
-
*,
|
|
92
|
-
user_email: Optional[str] = None,
|
|
93
|
-
conversation_id: Optional[str] = None,
|
|
94
|
-
workspace_id: Optional[str] = None,
|
|
95
|
-
limit: int = 10,
|
|
96
|
-
) -> List[Dict[str, Any]]:
|
|
97
|
-
"""Most recent artifacts for one conversation, newest last.
|
|
98
|
-
|
|
99
|
-
Returns ``[]`` for an unknown conversation — honest absence, never a
|
|
100
|
-
cross-conversation leak.
|
|
101
|
-
"""
|
|
102
|
-
key = self._key(user_email, conversation_id, workspace_id)
|
|
103
|
-
with self._lock:
|
|
104
|
-
items = list(self._entries.get(key, []))
|
|
105
|
-
return items[-max(1, int(limit)) :]
|
|
106
|
-
|
|
107
|
-
def clear(self) -> None:
|
|
108
|
-
with self._lock:
|
|
109
|
-
self._entries.clear()
|