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,675 +0,0 @@
|
|
|
1
|
-
"""Personal ontology — the Brain's model of *its owner* (v11.1.0).
|
|
2
|
-
|
|
3
|
-
Everything else in the graph is knowledge the user collected. This subgraph is
|
|
4
|
-
knowledge about the user: what they prefer, what they decided, what they do
|
|
5
|
-
every day, who they work with. It is small, it is read on almost every prompt,
|
|
6
|
-
and it is the one part of the Brain where being wrong is not a retrieval miss
|
|
7
|
-
but an insult — so it is governed differently from the rest:
|
|
8
|
-
|
|
9
|
-
* **Extraction never writes.** :func:`propose_self_model` reads text and raises
|
|
10
|
-
one review proposal per candidate fact through the same
|
|
11
|
-
``ReviewQueueService`` door synthesis uses (:class:`ProposalDesk`, which also
|
|
12
|
-
suppresses a subject already waiting for a decision).
|
|
13
|
-
:func:`apply_self_model_proposal` is the only path from a proposal to a node,
|
|
14
|
-
and it writes nothing until ``review_queue.approve`` has returned.
|
|
15
|
-
* **The user writes directly.** :func:`upsert_self_model_fact` and
|
|
16
|
-
:func:`delete_self_model_fact` are user-initiated edits — ownership means the
|
|
17
|
-
person can add, correct, and remove their own profile without asking a
|
|
18
|
-
review queue for permission.
|
|
19
|
-
* **Deterministic by default.** The extractor is a table of regexes over
|
|
20
|
-
first-person phrasings (Korean and English); the same text always yields the
|
|
21
|
-
same candidates in the same order. An optional ``refiner`` callable may
|
|
22
|
-
improve the *wording* of a candidate — it can never add, drop, or reclassify
|
|
23
|
-
one, and a refiner that raises is ignored.
|
|
24
|
-
|
|
25
|
-
Shape of the subgraph: one ``Self`` root (``self:root``) with every fact node
|
|
26
|
-
(``self:<kind>:<digest>``) pointing at it through a ``PART_OF`` edge. The id
|
|
27
|
-
prefix is the membership test, so a fact can never be confused with an ordinary
|
|
28
|
-
memory that happens to be typed ``Decision``.
|
|
29
|
-
|
|
30
|
-
Brain Core isolation: ``review_queue`` and ``store`` are duck-typed; this
|
|
31
|
-
module never imports ``latticeai``.
|
|
32
|
-
"""
|
|
33
|
-
|
|
34
|
-
from __future__ import annotations
|
|
35
|
-
|
|
36
|
-
import hashlib
|
|
37
|
-
import json
|
|
38
|
-
import logging
|
|
39
|
-
import re
|
|
40
|
-
from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple
|
|
41
|
-
|
|
42
|
-
from .context import approx_tokens
|
|
43
|
-
from .synthesis import ProposalDesk
|
|
44
|
-
from .utils import now_iso
|
|
45
|
-
|
|
46
|
-
logger = logging.getLogger(__name__)
|
|
47
|
-
|
|
48
|
-
#: Review-queue kind for a Self-Model candidate. The source stays the existing
|
|
49
|
-
#: ``kg_change_digest`` (see :mod:`lattice_brain.synthesis`) — a new fact about
|
|
50
|
-
#: the user is a knowledge change, and the Review Center already renders it.
|
|
51
|
-
SELF_MODEL_KIND = "self_model_fact"
|
|
52
|
-
|
|
53
|
-
#: Node id prefix. Membership in the Self-Model is a fact about identity, not a
|
|
54
|
-
#: heuristic over node types, so it is encoded in the id.
|
|
55
|
-
SELF_ID_PREFIX = "self:"
|
|
56
|
-
SELF_ROOT_ID = "self:root"
|
|
57
|
-
|
|
58
|
-
#: Fact kind → the graph node type it becomes. ``DECISION`` is the existing
|
|
59
|
-
#: node type, deliberately reused (see ``graph/schema.py``).
|
|
60
|
-
FACT_NODE_TYPES: Dict[str, str] = {
|
|
61
|
-
"preference": "Preference",
|
|
62
|
-
"decision": "Decision",
|
|
63
|
-
"habit": "Habit",
|
|
64
|
-
"relationship": "Relationship",
|
|
65
|
-
"trait": "Self",
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
#: Rendering order for the summary — who the person is, then what they like,
|
|
69
|
-
#: then what they repeat, then what they settled, then who is around them.
|
|
70
|
-
KIND_ORDER: Tuple[str, ...] = (
|
|
71
|
-
"trait",
|
|
72
|
-
"preference",
|
|
73
|
-
"habit",
|
|
74
|
-
"decision",
|
|
75
|
-
"relationship",
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
#: Plain-language labels used in the injected summary (Korean product voice).
|
|
79
|
-
KIND_LABELS: Dict[str, str] = {
|
|
80
|
-
"trait": "나",
|
|
81
|
-
"preference": "선호",
|
|
82
|
-
"habit": "습관",
|
|
83
|
-
"decision": "결정",
|
|
84
|
-
"relationship": "관계",
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
#: Default token ceiling for :func:`self_model_summary`. Small on purpose: the
|
|
88
|
-
#: Self-Model rides along on every prompt, so it must never crowd out the
|
|
89
|
-
#: knowledge the question is actually about.
|
|
90
|
-
DEFAULT_SUMMARY_TOKENS = 200
|
|
91
|
-
|
|
92
|
-
_MAX_FACT_CHARS = 120
|
|
93
|
-
_WHITESPACE = re.compile(r"\s+")
|
|
94
|
-
_TRAILING = " .。!?!?,、;:\n\t\"'“”‘’()[]{}"
|
|
95
|
-
|
|
96
|
-
# Deterministic extraction table: (kind, pattern, capture group, confidence,
|
|
97
|
-
# signal). Order fixes the order candidates are produced in.
|
|
98
|
-
_PATTERNS: Tuple[Tuple[str, "re.Pattern[str]", int, float, str], ...] = (
|
|
99
|
-
(
|
|
100
|
-
"decision",
|
|
101
|
-
re.compile(r"결정\s*[::]\s*(?P<v>[^\n]+)"),
|
|
102
|
-
1,
|
|
103
|
-
0.9,
|
|
104
|
-
"ko_decision_marker",
|
|
105
|
-
),
|
|
106
|
-
(
|
|
107
|
-
"decision",
|
|
108
|
-
re.compile(r"(?P<v>[^\n.。]+?)\s*하기로\s*(?:했|결정했)"),
|
|
109
|
-
1,
|
|
110
|
-
0.75,
|
|
111
|
-
"ko_decided_to",
|
|
112
|
-
),
|
|
113
|
-
(
|
|
114
|
-
"decision",
|
|
115
|
-
re.compile(r"\bDecision\s*:\s*(?P<v>[^\n]+)", re.IGNORECASE),
|
|
116
|
-
1,
|
|
117
|
-
0.9,
|
|
118
|
-
"en_decision_marker",
|
|
119
|
-
),
|
|
120
|
-
(
|
|
121
|
-
"decision",
|
|
122
|
-
re.compile(r"\b(?:I|we)\s+decided\s+to\s+(?P<v>[^\n.!?]+)", re.IGNORECASE),
|
|
123
|
-
1,
|
|
124
|
-
0.75,
|
|
125
|
-
"en_decided_to",
|
|
126
|
-
),
|
|
127
|
-
(
|
|
128
|
-
"preference",
|
|
129
|
-
re.compile(
|
|
130
|
-
r"(?:나는|저는|내가|제가)\s*(?P<v>[^\n.。]+?)\s*(?:을|를|이|가)?"
|
|
131
|
-
r"\s*(?:좋아|선호|싫어)(?:합니다|한다|해요|해|하고)"
|
|
132
|
-
),
|
|
133
|
-
1,
|
|
134
|
-
0.7,
|
|
135
|
-
"ko_first_person_preference",
|
|
136
|
-
),
|
|
137
|
-
(
|
|
138
|
-
"preference",
|
|
139
|
-
re.compile(
|
|
140
|
-
r"\bI\s+(?:prefer|like|love|hate|dislike|avoid)\s+(?P<v>[^\n.!?]+)",
|
|
141
|
-
re.IGNORECASE,
|
|
142
|
-
),
|
|
143
|
-
1,
|
|
144
|
-
0.7,
|
|
145
|
-
"en_first_person_preference",
|
|
146
|
-
),
|
|
147
|
-
(
|
|
148
|
-
# The frequency word stays inside the capture: "회고를 씁니다" is a
|
|
149
|
-
# sentence, "매일 회고를 씁니다" is a habit.
|
|
150
|
-
"habit",
|
|
151
|
-
re.compile(r"(?P<v>(?:매일|매주|매달|아침마다|항상|늘)\s*[^\n.。!?]+)"),
|
|
152
|
-
1,
|
|
153
|
-
0.65,
|
|
154
|
-
"ko_routine",
|
|
155
|
-
),
|
|
156
|
-
(
|
|
157
|
-
"habit",
|
|
158
|
-
re.compile(
|
|
159
|
-
r"\bI\s+(?P<v>(?:always|usually|every\s+(?:morning|day|week))"
|
|
160
|
-
r"\s+[^\n.!?]+)",
|
|
161
|
-
re.IGNORECASE,
|
|
162
|
-
),
|
|
163
|
-
1,
|
|
164
|
-
0.65,
|
|
165
|
-
"en_routine",
|
|
166
|
-
),
|
|
167
|
-
(
|
|
168
|
-
"relationship",
|
|
169
|
-
re.compile(
|
|
170
|
-
r"(?:내|제)\s*(?P<v>(?:동료|팀장|매니저|친구|파트너|상사|멘토)\s+\S+)"
|
|
171
|
-
),
|
|
172
|
-
1,
|
|
173
|
-
0.6,
|
|
174
|
-
"ko_relationship",
|
|
175
|
-
),
|
|
176
|
-
(
|
|
177
|
-
"relationship",
|
|
178
|
-
re.compile(
|
|
179
|
-
r"\bmy\s+(?P<v>(?:colleague|manager|teammate|friend|partner|mentor|boss)"
|
|
180
|
-
r"\s+[^\n.!?,]+)",
|
|
181
|
-
re.IGNORECASE,
|
|
182
|
-
),
|
|
183
|
-
1,
|
|
184
|
-
0.6,
|
|
185
|
-
"en_relationship",
|
|
186
|
-
),
|
|
187
|
-
(
|
|
188
|
-
"trait",
|
|
189
|
-
re.compile(
|
|
190
|
-
r"(?:나는|저는)\s*(?P<v>[^\n.。]*?(?:개발자|디자이너|엔지니어|연구원|학생|기획자))"
|
|
191
|
-
r"\s*(?:입니다|이다|다|예요)"
|
|
192
|
-
),
|
|
193
|
-
1,
|
|
194
|
-
0.6,
|
|
195
|
-
"ko_role",
|
|
196
|
-
),
|
|
197
|
-
(
|
|
198
|
-
"trait",
|
|
199
|
-
re.compile(r"\bI\s+am\s+an?\s+(?P<v>[^\n.!?]+)", re.IGNORECASE),
|
|
200
|
-
1,
|
|
201
|
-
0.6,
|
|
202
|
-
"en_role",
|
|
203
|
-
),
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
class SelfModelError(ValueError):
|
|
208
|
-
"""Raised when a Self-Model operation cannot be carried out as asked.
|
|
209
|
-
|
|
210
|
-
Carries a machine-readable ``code`` alongside the developer message: Brain
|
|
211
|
-
Core cannot reach the server's message catalog (isolation), so the API
|
|
212
|
-
layer translates the code instead of forwarding an English sentence to a
|
|
213
|
-
Korean user.
|
|
214
|
-
"""
|
|
215
|
-
|
|
216
|
-
def __init__(self, message: str, *, code: str = "invalid") -> None:
|
|
217
|
-
self.code = code
|
|
218
|
-
super().__init__(message)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
# ── extraction (deterministic) ───────────────────────────────────────────────
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
def _normalize(text: str) -> str:
|
|
225
|
-
return _WHITESPACE.sub(" ", str(text or "")).strip().strip(_TRAILING)[
|
|
226
|
-
:_MAX_FACT_CHARS
|
|
227
|
-
]
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
def fact_id(kind: str, text: str) -> str:
|
|
231
|
-
"""Stable id for a fact — the same statement always lands on the same node."""
|
|
232
|
-
digest = hashlib.sha256(f"{kind}|{text.lower()}".encode()).hexdigest()[:12]
|
|
233
|
-
return f"{SELF_ID_PREFIX}{kind}:{digest}"
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
def extract_self_model(
|
|
237
|
-
text: str,
|
|
238
|
-
*,
|
|
239
|
-
source: Optional[str] = None,
|
|
240
|
-
refiner: Optional[Callable[[str], str]] = None,
|
|
241
|
-
) -> List[Dict[str, Any]]:
|
|
242
|
-
"""Candidate Self-Model facts found in ``text``.
|
|
243
|
-
|
|
244
|
-
Pure and deterministic: a table of first-person patterns, deduplicated on
|
|
245
|
-
(kind, lowercased text), ordered by (kind, text). ``refiner`` — a model,
|
|
246
|
-
when one is available — may only rewrite the wording of a candidate the
|
|
247
|
-
regexes already found; anything it returns that is empty or too long is
|
|
248
|
-
ignored, as is a refiner that raises.
|
|
249
|
-
"""
|
|
250
|
-
found: Dict[str, Dict[str, Any]] = {}
|
|
251
|
-
for kind, pattern, group, confidence, signal in _PATTERNS:
|
|
252
|
-
for match in pattern.finditer(str(text or "")):
|
|
253
|
-
value = _normalize(match.group(group))
|
|
254
|
-
if len(value) < 2:
|
|
255
|
-
continue
|
|
256
|
-
value = _refine(value, refiner)
|
|
257
|
-
identifier = fact_id(kind, value)
|
|
258
|
-
if identifier in found:
|
|
259
|
-
continue
|
|
260
|
-
found[identifier] = {
|
|
261
|
-
"id": identifier,
|
|
262
|
-
"kind": kind,
|
|
263
|
-
"text": value,
|
|
264
|
-
"confidence": confidence,
|
|
265
|
-
"signal": signal,
|
|
266
|
-
"source": source or "",
|
|
267
|
-
}
|
|
268
|
-
return sorted(found.values(), key=lambda fact: (fact["kind"], fact["text"]))
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
def _refine(value: str, refiner: Optional[Callable[[str], str]]) -> str:
|
|
272
|
-
"""Optional model pass over one candidate's wording (never its meaning)."""
|
|
273
|
-
if refiner is None:
|
|
274
|
-
return value
|
|
275
|
-
try:
|
|
276
|
-
written = _normalize(refiner(value))
|
|
277
|
-
except Exception: # noqa: BLE001 — a model may never break extraction
|
|
278
|
-
logger.exception("self-model refiner failed")
|
|
279
|
-
return value
|
|
280
|
-
return written or value
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
# ── proposals (the only agent/background write path) ─────────────────────────
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
def propose_self_model(
|
|
287
|
-
store: Any,
|
|
288
|
-
review_queue: Any,
|
|
289
|
-
*,
|
|
290
|
-
text: str = "",
|
|
291
|
-
texts: Optional[Sequence[str]] = None,
|
|
292
|
-
source: Optional[str] = None,
|
|
293
|
-
workspace_id: Optional[str] = None,
|
|
294
|
-
user_email: Optional[str] = None,
|
|
295
|
-
refiner: Optional[Callable[[str], str]] = None,
|
|
296
|
-
max_proposals: int = 5,
|
|
297
|
-
) -> Dict[str, Any]:
|
|
298
|
-
"""Raise one review proposal per newly-noticed fact about the user.
|
|
299
|
-
|
|
300
|
-
Facts already in the subgraph are skipped (nothing is proposed twice), and
|
|
301
|
-
:class:`ProposalDesk` suppresses subjects already waiting for a decision.
|
|
302
|
-
Nothing is written to the graph here.
|
|
303
|
-
"""
|
|
304
|
-
corpus = [text, *(texts or [])]
|
|
305
|
-
candidates: List[Dict[str, Any]] = []
|
|
306
|
-
seen: set = set()
|
|
307
|
-
for chunk in corpus:
|
|
308
|
-
for fact in extract_self_model(chunk, source=source, refiner=refiner):
|
|
309
|
-
if fact["id"] in seen:
|
|
310
|
-
continue
|
|
311
|
-
seen.add(fact["id"])
|
|
312
|
-
candidates.append(fact)
|
|
313
|
-
known = {row["id"] for row in _read_facts(store, workspace_id=workspace_id)}
|
|
314
|
-
fresh = [fact for fact in candidates if fact["id"] not in known]
|
|
315
|
-
desk = ProposalDesk(review_queue, user_email=user_email, workspace_id=workspace_id)
|
|
316
|
-
open_keys = desk.open_keys()
|
|
317
|
-
proposed: List[Dict[str, Any]] = []
|
|
318
|
-
for fact in fresh[: max(0, int(max_proposals))]:
|
|
319
|
-
item = desk.propose(
|
|
320
|
-
kind=SELF_MODEL_KIND,
|
|
321
|
-
key=fact["id"],
|
|
322
|
-
title=f"나에 대한 새 사실: {fact['text']}",
|
|
323
|
-
summary=(
|
|
324
|
-
f"대화에서 '{fact['text']}'를 읽었습니다. "
|
|
325
|
-
f"내 프로필({KIND_LABELS[fact['kind']]})에 추가할까요? "
|
|
326
|
-
"승인하기 전에는 저장되지 않습니다."
|
|
327
|
-
),
|
|
328
|
-
open_keys=open_keys,
|
|
329
|
-
payload={"fact": fact, "node_type": FACT_NODE_TYPES[fact["kind"]]},
|
|
330
|
-
)
|
|
331
|
-
if item is not None:
|
|
332
|
-
proposed.append(item)
|
|
333
|
-
return {
|
|
334
|
-
"available": True,
|
|
335
|
-
"candidates": candidates,
|
|
336
|
-
"candidate_count": len(candidates),
|
|
337
|
-
"already_known": len(candidates) - len(fresh),
|
|
338
|
-
"proposed": proposed,
|
|
339
|
-
"proposed_count": len(proposed),
|
|
340
|
-
"suppressed": desk.suppressed,
|
|
341
|
-
"generated_at": now_iso(),
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
def apply_self_model_proposal(
|
|
346
|
-
store: Any,
|
|
347
|
-
review_queue: Any,
|
|
348
|
-
item_id: str,
|
|
349
|
-
*,
|
|
350
|
-
workspace_id: Optional[str] = None,
|
|
351
|
-
) -> Dict[str, Any]:
|
|
352
|
-
"""Approve a Self-Model proposal and write the fact into the subgraph.
|
|
353
|
-
|
|
354
|
-
The single door from a proposal to a node, gated twice the way
|
|
355
|
-
``resolve_contradiction`` is: the item must be a Self-Model proposal, and
|
|
356
|
-
``review_queue.approve`` must return before anything is written.
|
|
357
|
-
"""
|
|
358
|
-
item = review_queue.get(item_id, workspace_id=workspace_id)
|
|
359
|
-
if str(item.get("kind")) != SELF_MODEL_KIND:
|
|
360
|
-
raise SelfModelError(
|
|
361
|
-
f"review item {item_id} is not a Self-Model proposal",
|
|
362
|
-
code="not_a_proposal",
|
|
363
|
-
)
|
|
364
|
-
fact = dict((item.get("payload") or {}).get("fact") or {})
|
|
365
|
-
kind = str(fact.get("kind") or "")
|
|
366
|
-
text = _normalize(str(fact.get("text") or ""))
|
|
367
|
-
if kind not in FACT_NODE_TYPES or not text:
|
|
368
|
-
raise SelfModelError(
|
|
369
|
-
f"review item {item_id} carries no Self-Model fact",
|
|
370
|
-
code="empty_proposal",
|
|
371
|
-
)
|
|
372
|
-
approved = review_queue.approve(item_id, workspace_id=workspace_id)
|
|
373
|
-
node = _write_fact(
|
|
374
|
-
store,
|
|
375
|
-
kind=kind,
|
|
376
|
-
text=text,
|
|
377
|
-
workspace_id=workspace_id or item.get("workspace_id"),
|
|
378
|
-
origin="proposal",
|
|
379
|
-
confidence=float(fact.get("confidence") or 0.0),
|
|
380
|
-
signal=str(fact.get("signal") or ""),
|
|
381
|
-
item_id=item_id,
|
|
382
|
-
)
|
|
383
|
-
return {
|
|
384
|
-
"item_id": item_id,
|
|
385
|
-
"status": str(approved.get("status") or ""),
|
|
386
|
-
"fact": node,
|
|
387
|
-
"applied_at": now_iso(),
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
# ── user-initiated edits (direct writes — ownership) ─────────────────────────
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
def upsert_self_model_fact(
|
|
395
|
-
store: Any,
|
|
396
|
-
*,
|
|
397
|
-
kind: str,
|
|
398
|
-
text: str,
|
|
399
|
-
workspace_id: Optional[str] = None,
|
|
400
|
-
) -> Dict[str, Any]:
|
|
401
|
-
"""Add or correct one fact directly. The user owns their own profile."""
|
|
402
|
-
kind = str(kind or "").strip().lower()
|
|
403
|
-
value = _normalize(text)
|
|
404
|
-
if kind not in FACT_NODE_TYPES:
|
|
405
|
-
raise SelfModelError(
|
|
406
|
-
f"kind must be one of {sorted(FACT_NODE_TYPES)}", code="invalid_kind"
|
|
407
|
-
)
|
|
408
|
-
if not value:
|
|
409
|
-
raise SelfModelError("a Self-Model fact needs text", code="text_required")
|
|
410
|
-
return _write_fact(
|
|
411
|
-
store,
|
|
412
|
-
kind=kind,
|
|
413
|
-
text=value,
|
|
414
|
-
workspace_id=workspace_id,
|
|
415
|
-
origin="user",
|
|
416
|
-
confidence=1.0,
|
|
417
|
-
signal="user_edit",
|
|
418
|
-
item_id=None,
|
|
419
|
-
)
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
def delete_self_model_fact(store: Any, node_id: str) -> Dict[str, Any]:
|
|
423
|
-
"""Remove one fact (or the whole root) from the subgraph, permanently."""
|
|
424
|
-
identifier = str(node_id or "").strip()
|
|
425
|
-
if not identifier.startswith(SELF_ID_PREFIX):
|
|
426
|
-
raise SelfModelError(f"not a Self-Model node: {identifier}", code="not_self_model")
|
|
427
|
-
with store._connect() as conn:
|
|
428
|
-
deleted = conn.execute(
|
|
429
|
-
"DELETE FROM nodes WHERE id=?", (identifier,)
|
|
430
|
-
).rowcount
|
|
431
|
-
conn.execute("DELETE FROM nodes_v2 WHERE id=?", (identifier,))
|
|
432
|
-
if not deleted:
|
|
433
|
-
raise SelfModelError(f"Self-Model node not found: {identifier}", code="not_found")
|
|
434
|
-
return {"status": "ok", "id": identifier, "deleted_at": now_iso()}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
# ── reads ────────────────────────────────────────────────────────────────────
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
def list_self_model(
|
|
441
|
-
store: Any,
|
|
442
|
-
*,
|
|
443
|
-
workspace_id: Optional[str] = None,
|
|
444
|
-
allowed_workspaces: Optional[Iterable[str]] = None,
|
|
445
|
-
) -> Dict[str, Any]:
|
|
446
|
-
"""Every fact the Brain holds about its owner, grouped for display."""
|
|
447
|
-
facts = _read_facts(
|
|
448
|
-
store, workspace_id=workspace_id, allowed_workspaces=allowed_workspaces
|
|
449
|
-
)
|
|
450
|
-
counts: Dict[str, int] = {kind: 0 for kind in KIND_ORDER}
|
|
451
|
-
for fact in facts:
|
|
452
|
-
counts[fact["kind"]] += 1
|
|
453
|
-
return {
|
|
454
|
-
"available": True,
|
|
455
|
-
"facts": facts,
|
|
456
|
-
"count": len(facts),
|
|
457
|
-
"counts": counts,
|
|
458
|
-
"kinds": list(KIND_ORDER),
|
|
459
|
-
"generated_at": now_iso(),
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
def self_model_summary(
|
|
464
|
-
store: Any,
|
|
465
|
-
*,
|
|
466
|
-
limit_tokens: int = DEFAULT_SUMMARY_TOKENS,
|
|
467
|
-
workspace_id: Optional[str] = None,
|
|
468
|
-
allowed_workspaces: Optional[Iterable[str]] = None,
|
|
469
|
-
) -> str:
|
|
470
|
-
"""Plain-text Self-Model summary for injection into a model's context.
|
|
471
|
-
|
|
472
|
-
Deterministic and bounded: facts are rendered in :data:`KIND_ORDER`, and
|
|
473
|
-
lines are added only while the whole block stays inside ``limit_tokens``.
|
|
474
|
-
An empty subgraph returns ``""`` — the caller injects nothing rather than a
|
|
475
|
-
header with nothing under it.
|
|
476
|
-
"""
|
|
477
|
-
facts = _read_facts(
|
|
478
|
-
store, workspace_id=workspace_id, allowed_workspaces=allowed_workspaces
|
|
479
|
-
)
|
|
480
|
-
if not facts or limit_tokens <= 0:
|
|
481
|
-
return ""
|
|
482
|
-
header = "사용자에 대해 확인된 사실:"
|
|
483
|
-
lines: List[str] = []
|
|
484
|
-
for kind in KIND_ORDER:
|
|
485
|
-
for fact in facts:
|
|
486
|
-
if fact["kind"] != kind:
|
|
487
|
-
continue
|
|
488
|
-
line = f"- {KIND_LABELS[kind]}: {fact['text']}"
|
|
489
|
-
if approx_tokens("\n".join([header, *lines, line])) > limit_tokens:
|
|
490
|
-
return "\n".join([header, *lines]) if lines else ""
|
|
491
|
-
lines.append(line)
|
|
492
|
-
return "\n".join([header, *lines])
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
# ── graph plumbing ───────────────────────────────────────────────────────────
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
def _write_fact(
|
|
499
|
-
store: Any,
|
|
500
|
-
*,
|
|
501
|
-
kind: str,
|
|
502
|
-
text: str,
|
|
503
|
-
workspace_id: Optional[str],
|
|
504
|
-
origin: str,
|
|
505
|
-
confidence: float,
|
|
506
|
-
signal: str,
|
|
507
|
-
item_id: Optional[str],
|
|
508
|
-
) -> Dict[str, Any]:
|
|
509
|
-
"""Write the root (if needed), the fact node, and the ``PART_OF`` edge."""
|
|
510
|
-
node_id = fact_id(kind, text)
|
|
511
|
-
metadata = {
|
|
512
|
-
"self_model": True,
|
|
513
|
-
"self_model_kind": kind,
|
|
514
|
-
"origin": origin,
|
|
515
|
-
"confidence": confidence,
|
|
516
|
-
"signal": signal,
|
|
517
|
-
"workspace_id": workspace_id,
|
|
518
|
-
"review_item_id": item_id,
|
|
519
|
-
}
|
|
520
|
-
with store._connect() as conn:
|
|
521
|
-
store._upsert_node(
|
|
522
|
-
conn,
|
|
523
|
-
SELF_ROOT_ID,
|
|
524
|
-
"Self",
|
|
525
|
-
"나",
|
|
526
|
-
"Brain이 사용자에 대해 알고 있는 사실의 뿌리입니다.",
|
|
527
|
-
metadata={"self_model": True, "self_model_kind": "root"},
|
|
528
|
-
workspace_id=workspace_id,
|
|
529
|
-
)
|
|
530
|
-
store._upsert_node(
|
|
531
|
-
conn,
|
|
532
|
-
node_id,
|
|
533
|
-
FACT_NODE_TYPES[kind],
|
|
534
|
-
text,
|
|
535
|
-
text,
|
|
536
|
-
metadata=metadata,
|
|
537
|
-
workspace_id=workspace_id,
|
|
538
|
-
)
|
|
539
|
-
store._upsert_edge(conn, node_id, SELF_ROOT_ID, "PART_OF")
|
|
540
|
-
return {
|
|
541
|
-
"id": node_id,
|
|
542
|
-
"kind": kind,
|
|
543
|
-
"type": FACT_NODE_TYPES[kind],
|
|
544
|
-
"text": text,
|
|
545
|
-
"origin": origin,
|
|
546
|
-
"confidence": confidence,
|
|
547
|
-
"signal": signal,
|
|
548
|
-
"workspace_id": workspace_id,
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
def _allowed_set(
|
|
553
|
-
workspace_id: Optional[str], allowed_workspaces: Optional[Iterable[str]]
|
|
554
|
-
) -> Optional[set]:
|
|
555
|
-
"""Normalize the two scoping spellings into one allow-set (``None`` = all).
|
|
556
|
-
|
|
557
|
-
``allowed_workspaces`` is the scoping vocabulary the graph reads already
|
|
558
|
-
use (``filter_scoped_nodes``); ``workspace_id`` is the single-scope sugar
|
|
559
|
-
the API surfaces speak. A fact with no workspace is personal-global and
|
|
560
|
-
stays visible in both.
|
|
561
|
-
"""
|
|
562
|
-
if allowed_workspaces is not None:
|
|
563
|
-
return {str(item) for item in allowed_workspaces if item}
|
|
564
|
-
if workspace_id is None:
|
|
565
|
-
return None
|
|
566
|
-
return {str(workspace_id)}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
def _read_facts(
|
|
570
|
-
store: Any,
|
|
571
|
-
*,
|
|
572
|
-
workspace_id: Optional[str] = None,
|
|
573
|
-
allowed_workspaces: Optional[Iterable[str]] = None,
|
|
574
|
-
) -> List[Dict[str, Any]]:
|
|
575
|
-
"""Fact rows for a workspace, ordered deterministically (kind, then text).
|
|
576
|
-
|
|
577
|
-
Reads the legacy ``nodes`` table, which the v4 write door maintains as the
|
|
578
|
-
compatibility projection of ``nodes_v2`` — so this answers the same rows in
|
|
579
|
-
both read modes. A store that cannot be read yields no facts rather than
|
|
580
|
-
raising into a prompt-assembly path.
|
|
581
|
-
"""
|
|
582
|
-
if not hasattr(store, "_connect"):
|
|
583
|
-
# Not a Brain store (a doc-generation seam may be handed a stand-in).
|
|
584
|
-
# There is no profile to read, and that is not an error worth logging.
|
|
585
|
-
return []
|
|
586
|
-
try:
|
|
587
|
-
with store._connect() as conn:
|
|
588
|
-
rows = conn.execute(
|
|
589
|
-
"SELECT id, type, title, summary, metadata_json, updated_at "
|
|
590
|
-
"FROM nodes WHERE id LIKE ? AND id != ? ORDER BY id ASC",
|
|
591
|
-
(f"{SELF_ID_PREFIX}%", SELF_ROOT_ID),
|
|
592
|
-
).fetchall()
|
|
593
|
-
except Exception: # noqa: BLE001 — an unreadable Brain injects nothing
|
|
594
|
-
logger.exception("self-model read failed")
|
|
595
|
-
return []
|
|
596
|
-
facts = [fact for fact in (_row_to_fact(row) for row in rows) if fact]
|
|
597
|
-
allowed = _allowed_set(workspace_id, allowed_workspaces)
|
|
598
|
-
if allowed is not None:
|
|
599
|
-
facts = [
|
|
600
|
-
fact
|
|
601
|
-
for fact in facts
|
|
602
|
-
if not fact["workspace_id"] or str(fact["workspace_id"]) in allowed
|
|
603
|
-
]
|
|
604
|
-
return sorted(facts, key=lambda fact: (KIND_ORDER.index(fact["kind"]), fact["text"]))
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
def _row_to_fact(row: Any) -> Optional[Dict[str, Any]]:
|
|
608
|
-
metadata = _loads(row["metadata_json"])
|
|
609
|
-
kind = str(metadata.get("self_model_kind") or "")
|
|
610
|
-
if kind not in FACT_NODE_TYPES:
|
|
611
|
-
return None
|
|
612
|
-
return {
|
|
613
|
-
"id": row["id"],
|
|
614
|
-
"kind": kind,
|
|
615
|
-
"type": row["type"],
|
|
616
|
-
"text": row["title"],
|
|
617
|
-
"origin": str(metadata.get("origin") or ""),
|
|
618
|
-
"confidence": metadata.get("confidence"),
|
|
619
|
-
"signal": str(metadata.get("signal") or ""),
|
|
620
|
-
"workspace_id": metadata.get("workspace_id"),
|
|
621
|
-
"updated_at": row["updated_at"],
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
def _loads(raw: Any) -> Dict[str, Any]:
|
|
626
|
-
try:
|
|
627
|
-
value = json.loads(raw or "{}")
|
|
628
|
-
except (TypeError, ValueError):
|
|
629
|
-
return {}
|
|
630
|
-
return value if isinstance(value, dict) else {}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
def summary_for_prompt(
|
|
634
|
-
store: Any,
|
|
635
|
-
*,
|
|
636
|
-
limit_tokens: int = DEFAULT_SUMMARY_TOKENS,
|
|
637
|
-
workspace_id: Optional[str] = None,
|
|
638
|
-
allowed_workspaces: Optional[Iterable[str]] = None,
|
|
639
|
-
) -> str:
|
|
640
|
-
""":func:`self_model_summary` that never raises — the injection seam.
|
|
641
|
-
|
|
642
|
-
Prompt assembly must not fail because a profile could not be read; an
|
|
643
|
-
unreadable Self-Model injects nothing, exactly like an empty one.
|
|
644
|
-
"""
|
|
645
|
-
try:
|
|
646
|
-
return self_model_summary(
|
|
647
|
-
store,
|
|
648
|
-
limit_tokens=limit_tokens,
|
|
649
|
-
workspace_id=workspace_id,
|
|
650
|
-
allowed_workspaces=allowed_workspaces,
|
|
651
|
-
)
|
|
652
|
-
except Exception: # noqa: BLE001 — see docstring
|
|
653
|
-
logger.exception("self-model summary failed")
|
|
654
|
-
return ""
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
__all__ = [
|
|
658
|
-
"DEFAULT_SUMMARY_TOKENS",
|
|
659
|
-
"FACT_NODE_TYPES",
|
|
660
|
-
"KIND_LABELS",
|
|
661
|
-
"KIND_ORDER",
|
|
662
|
-
"SELF_ID_PREFIX",
|
|
663
|
-
"SELF_MODEL_KIND",
|
|
664
|
-
"SELF_ROOT_ID",
|
|
665
|
-
"SelfModelError",
|
|
666
|
-
"apply_self_model_proposal",
|
|
667
|
-
"delete_self_model_fact",
|
|
668
|
-
"extract_self_model",
|
|
669
|
-
"fact_id",
|
|
670
|
-
"list_self_model",
|
|
671
|
-
"propose_self_model",
|
|
672
|
-
"self_model_summary",
|
|
673
|
-
"summary_for_prompt",
|
|
674
|
-
"upsert_self_model_fact",
|
|
675
|
-
]
|