diff --git a/DISTRIBUTED-DEPLOYMENT-BUNDLE.md b/DISTRIBUTED-DEPLOYMENT-BUNDLE.md new file mode 100644 index 0000000..e76aab7 --- /dev/null +++ b/DISTRIBUTED-DEPLOYMENT-BUNDLE.md @@ -0,0 +1,5 @@ +# v1.6.0 Distributed Deployment Bundle + +This is the unchanged v1.6.0 application release plus ready-to-edit deployment kits under `deployments/`. No application code or persisted data format was changed. + +See `deployments/README.md`. diff --git a/MANIFEST.sha256 b/MANIFEST.sha256 index 9914d30..f5fab8e 100644 --- a/MANIFEST.sha256 +++ b/MANIFEST.sha256 @@ -1,9 +1,11 @@ 27dc46be5cbb1b171deff7fbd2f28bff1be802dff403797535fd8968bb98c8eb ./.cbmignore -34b4ce51cae7719746736c38776f3f961b93d24b68f54da95582f285a1b7530c ./.env.example +94765e7c36c2e99c38af0b0d1193583b472fe49df014206b1f4c41b6e4f343ee ./.env.example +948661c795abfc3ed92726b43406ad2ca51cb4a2cd32d22dbc4093db75f74b62 ./.env.subagent.example ed22fda7661db8203563611dc144998161cd024e161b0471d615eaf0defeb7db ./.gitea/workflows/release-tag.yml e1ff71187cc3411a85067b964264011db7bd109a585ef7b9ea5b08bda039d813 ./.gitignore +9c4258c6f68d01688ca1ea6959ed423601fab6b1523217c1d643f7d99acdd0c2 ./DISTRIBUTED-DEPLOYMENT-BUNDLE.md ccfc4c139345a69d05cd2b809b0d09b4332d98e5b4f79aace4db0b648e3ea814 ./Makefile -f391b2925cff4f407504b441179029a33b8c435a2dddf2f6296e5cd13768be4a ./README.md +339c16bbeacd70b1ab21bd1e482a31999594b32ed3ea865591f345e843e99c0e ./README.md 4858caa52c0fb6cf302a1c581d07d448e5e90e0daa797e5819610fd6223bd348 ./RELEASE-NOTES-v1.1.0.md 01163462f46314f57660677fdef407c6c2884412ea850aab13a4f650e8c29f50 ./RELEASE-NOTES-v1.2.0.md 4da388ce660aa3b7a0d8075ec066a025b5437960973397360fcb9a5d4cb58c96 ./RELEASE-NOTES-v1.3.0.md @@ -14,21 +16,48 @@ f391b2925cff4f407504b441179029a33b8c435a2dddf2f6296e5cd13768be4a ./README.md 61ceebe5a891388336795fda2fd0d1ad10373b4ebaae7d2e670f35766115c604 ./RELEASE-NOTES-v1.4.4.md 15a3defdd5bbf07ebaec1a2e8626347a027a3622f10afb508f7a21c9b0bdfa71 ./RELEASE-NOTES-v1.4.5.md d10b1dbd87585d72144d816449800dc17b6d7f8faba2dfb4eca93ae3def2c631 ./RELEASE-NOTES-v1.5.0.md -55c4132c9883c31a2a0d9a5a58d72420a0c98d632907b1fd05565ac2815d5272 ./VERSION +d069259c99322966b525ab451defd6386ea182bdbdba58122e287df395e2396e ./RELEASE-NOTES-v1.6.0.md +cb1d9a30bc4ab8a397f39b7d786bb82aca0b816077eefc35d7c822017d75f6a5 ./VERSION e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ./backups/.gitkeep 8127e9db5e5e0af1d88770dc8fa60b381de45dbcc843262698cf9501409b4d58 ./deploy/searxng/settings.yml -3e153ad540d6cb1b20275775f2d7c129ec0216764cc9d0d27b963c634f347b43 ./docker-compose.dev.yml -d528a2cde5c3fc936e50da45c752041cbed99c8756d6ea823cf01ebf7c55bce9 ./docker-compose.yml -0f2adaa0765ff00d9c3a1133840c7a768a011f9d8f820f8a340b43600c7649d8 ./docs/ARCHITECTURE.md +db75f62ad947313abd1f0b9018a8bb3f7c05dadca2e97ffd429cf64ddaf72d31 ./deployments/README.md +433d44ed0ca5f2133246ad73818c6e48bb676cfcb3f635a51aec0c38380b19c3 ./deployments/cpu-subagent/.env +c14fe9d60e1e9c216fef80d3b596b670c56231dae72f94f9461c809ac4f24504 ./deployments/cpu-subagent/README.md +fa1f4ebcf753f7d3ba7b49a1bf9b9738d2f9f3cf1b88b3ed4403a819de196b4a ./deployments/cpu-subagent/docker-compose.yml +5ebe674fff45df57eb2ade09565d732c39f094d3bc4168b3ac8db5504b86094b ./deployments/cpu-subagent/preflight.sh +433a91b265b4f9daf1b964b3cca859905b58de7f846771ddfdd8b5e7c112b876 ./deployments/gpu-subagent/.env +8bd16c76f2da34f50e827e131eefb260b1c56b7263f8b92b42795fdb719e9f40 ./deployments/gpu-subagent/README.md +89dbaa1fa401a02ac9242450fa3119caf3d4f63e66d5b25af4cb100f1a8be351 ./deployments/gpu-subagent/docker-compose.yml +82803ed2628259b8f0cb8df5236099ef21dbd2bae299d2b0664dc8822b01b222 ./deployments/gpu-subagent/preflight.sh +77b97a11eb962a5e855a80b63b4598b2f35a5110cfba552d6cd509f3ca49bfd6 ./deployments/master/.env +16f8e8224c0c46e5c79f35b322695046edb13e608d176cb0b9f25590f11b6b01 ./deployments/master/README.md +e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ./deployments/master/backups/.gitkeep +8127e9db5e5e0af1d88770dc8fa60b381de45dbcc843262698cf9501409b4d58 ./deployments/master/deploy/searxng/settings.yml +bb00045a607dab67892030674567d3e3ab9c1a9e9f1b0a7177f127edc1747059 ./deployments/master/docker-compose.yml +e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ./deployments/master/knowledge/.gitkeep +6cd2e5bdf4c2a213f548e4f7a139faeeb56ff74f54e6da72e430fbd71342d64d ./deployments/master/monitoring/README.md +161c46febb7c00b7e8d3f71c231d673eb9ca20ad39d03a22de6f5f00a70ab322 ./deployments/master/monitoring/grafana/dashboards/neuroforge-master-subagents.json +2c7c42913333222438dc3dd9baaab21aa28bd9aff033a75f85c6981da00325be ./deployments/master/monitoring/grafana/provisioning/dashboards/dashboards.yml +725d7ae42e32230bc315c45b09d250332d8617b5c65b03e4da906c669fe20905 ./deployments/master/monitoring/grafana/provisioning/datasources/prometheus.yml +f2aea5d7ba2c22e4de4aef4d1c1dca7f4d5998a4ceae16eff0ab1a05e79bd8f7 ./deployments/master/monitoring/prometheus/alerts.yml +aac90d7e75a79256f77d730ca80fc1824e86f4299192526910b6f0226cedcbb9 ./deployments/master/monitoring/prometheus/prometheus.yml.template +5e3459ce74ef9f754e4a8439977f7da21563dc6863f760f49ef8699fe56ff603 ./deployments/master/monitoring/validate.sh +a59ce38c9b9d3d9a6abf22111530335a1e23a9b45665091655530b86d8ba2ae1 ./deployments/master/preflight.sh +e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ./deployments/master/staging/.gitkeep +58db2a87b703a5bbddc20b140907d187f8388e7db87b079ae9f6ffaac9c121fc ./docker-compose.dev.yml +3764108384c468f4f90f45458f554db7194e7d25cee2e20cdbfe8cf257acc620 ./docker-compose.subagent.yml +3155a644e4e3019959b6e20b4bcf3eef4e5e9bb78362b0e42a63ef8e704422d3 ./docker-compose.yml +e4c516375e450c43b5193b1257880c02f2132b05a16e19ab1c96d57182fa070c ./docs/ARCHITECTURE.md 34e2c7ac5992389cc51b40f740c1e206f20bffc67bdf19cb59d94b5df67a7082 ./docs/BACKUP-RESTORE-v1.5.0.md 9467a3c0796a87bada0eab6ad191913e628b4abc05520cbb4b73fac76d334481 ./docs/CODEBASE-MEMORY-MCP.md -765c39f0db69165a70d8175d1fe7686ff6dd512669db088a6fdf6a93de5404f6 ./docs/CONTROL-CENTER.md +74cab9b3ce3f6ed2bb5f1de99e1fe5c6f311e90550d078e10e140b726674957e ./docs/CONTROL-CENTER.md 88955b0ea289ccc63cbd3404f5b00bb446b38270408dcee23a6103a698f1f76b ./docs/CONTROL-MATRIX.md 323402668da4d8e1d97a29c45db753834f6c3a16739eebebf7e016f06af160bf ./docs/CONTROLLED-AUTONOMY.md 51b4a8d7daff787f099b77bd5a33ebf8038c0812bd3056f0ee6afe17e103681d ./docs/ENV-MIGRATION-FROM-LEGACY.md -e243300d59e4654de8d5b5110cc1cc2e00185a20c7d7b8c3d56848195e66b148 ./docs/ENVIRONMENT.md +a19522149d097ede5751dd6b9db515146ac47d14aaa7a7b49515e495388d0e0e ./docs/ENVIRONMENT.md 9c49332c6d0e23cd7f82adf23f61bcbd795ad30401990ae99b23295b2ab4cd09 ./docs/GO-LIVE-v1.5.0.md -2465c837c243ef03856ad1297b540d6df90005c74b9f4a9bba223066a612009f ./docs/IMPLEMENTED.md +6a9862c2ffa95420a3f8d2c3901a46f00c77a38826913280bd0e367e3133498f ./docs/IMPLEMENTED.md +65ab928c3a0c283131e48322685d240507a085c1fb059d4c114465774473c335 ./docs/MASTER-SUBAGENT-ORCHESTRATOR.md 9c38807cc12fba6f94cadc4694996de58e6d2d5396e55fc0c4bd404bb13f53ad ./docs/MIGRATION-CUTOVER.md a71d28353529906cee08cf90f35e0b0cf97bbc94ccfcef830145a2d561b21a9c ./docs/MIGRATION-MANIFEST.md 4330b3adefd8c40d174e1e878ff7cf075d99a44ec51251953d1a58d3a0fa8ca3 ./docs/MIGRATION-v1.1.0-to-v1.2.0.md @@ -45,6 +74,7 @@ b38aaf9d352ad70a9701566fc24d73d26c50c407291fe1c38b08b1afad9435bc ./docs/MIGRATI b57e66a589cff7331d547afc0e031accd1d576c46c013143f76311a84eacbcfe ./docs/MIGRATION-v1.5.6-to-v1.5.7.md 6c234e8bfcfa63f879f0748ccf9ca006595f5ee905b97d316f685154a4a5a2b9 ./docs/MIGRATION-v1.5.7-to-v1.5.8.md 91e8541a86f3b6b2080439b3177309b584f5d6c637baa286d7ad9d40afbbdba9 ./docs/MIGRATION-v1.5.8-to-v1.5.9.md +1d114256ef855284dd5a11b40682ce67ad27e17b41045625cae251a16531d0bd ./docs/MIGRATION-v1.5.9-to-v1.6.0.md 2a01fb10a3e04eae1800a7c7e0aafc31e9bbb23584cea54d849b004716ef81b4 ./docs/OBSIDIAN-EXPORT.md 040010a807178d33797106e04716822f8d147d7d5833c1ba70865a1827f8484a ./docs/OPERATIONS.md 69ea49bc76690ac489aea908f4e784a14293e92d62478a240eabea53c5f90820 ./docs/QUALITY-REPLAY-example.json @@ -58,6 +88,7 @@ bc9067063b13788393fc3389ac60c0db50e35cc9d7d733311fca2fc6923834f1 ./docs/RELEASE f7bbf78e76592762fe6e2a08eff9110a273962ae3145da287c5875355672db7f ./docs/RELEASE-v1.5.7.md 5757dd833f1f8b7b0a7b90b09324cb8989496ea6326d652d659a59f215a3172d ./docs/RELEASE-v1.5.8.md 245f942b72da17cb6c953720bd9c6742f71723be06bdb842c512851aca351e3e ./docs/RELEASE-v1.5.9.md +85160313c5dd89faea2e7404f090a6cef13a9b33758124dfc4d748a119375c32 ./docs/RELEASE-v1.6.0.md be749a09ddbcd4cf427316c6fe531f138e9bfaa6c104f4c11a08f7555231e8b2 ./docs/UNIFIED-GRAPH.md 77d91565660789c621b05bd69fecfa3c2ef736f93855eca1f58b017e61c76f90 ./docs/VALIDATION-v1.5.0.md 3ea1953b2b4eb8f966310a65a12a681fb44e4eb3857b843312e235759aacfce5 ./docs/VALIDATION.md @@ -168,7 +199,7 @@ cb3cde795233c0d4842dc7451c5855d1c071e459efbde3be0cabac32a837ba1b ./knowledge/14 f1eab883370e0a40ef52a6b6d785a510d8ed48a19d25e0a2bc95f4f2cc8e329e ./knowledge/17_serienbriefe-und-dokumentfunktionen.json 5a0b3d5d5bc712e30a67a4de3432f69070f7e43dd99931f675e72c213006b363 ./knowledge/example-vpn.json 49c48fb45c8387b020bb6a4bde2c81e544ac82b74403e453e28a85f987052978 ./mega-project.json -a6369b13936281483ee3b7cad2c380301b2fd9bdec0f7e817d821b9cef65227b ./patches/SHA256SUMS +634bef89fe88e3b578b197095cb424ecbda9354211a69e86cd9964251eda047b ./patches/SHA256SUMS 47a6fa2c79bbba0c04af86dfa65d58529f492c698060fe586456c22a4eadb877 ./patches/glpi-agent-mega.diff 9b411c90d96a86c80f088ee4637046eeefaf31c62059d11aa1a999d6eb08b5b4 ./patches/glpi-knowledge-mega.diff f0491de3cb6201f98ca6be8e865237adbba4772471f7fc7165d030e0c045fdb6 ./patches/neuroforge-mega.diff @@ -190,6 +221,7 @@ b031ea42356a022d7bb03e7e2bda23c4ceddb66ccc59a5d1f0ebe787a790b829 ./patches/v1.5 257a78b8f6613f92086a04bf53455f12e9bd1ab94775add31819ada465582163 ./patches/v1.5.6-to-v1.5.7.diff 53ab438b31a31590edbfe00eea6ba9d9ebff2b4c6255d7ac2056c86757f65c3a ./patches/v1.5.7-to-v1.5.8.diff 24bb0da9bf77a0e8b692989b05c00b623ed9a558b9b5050c24e396e1da41d0f8 ./patches/v1.5.8-to-v1.5.9.diff +e579d88ed2846cf5bc306cad45502af3edc61bd1c5bb5a2272979feff23dca62 ./patches/v1.5.9-to-v1.6.0.diff 564817f8edabde0c4e4a1a427a3aa5418aae7bf12e9463044a7e6e0f13973657 ./platform/neuroforge/.env.example 39319b6f2058e4c8d6656a9cf01675374f81a04075b956b093a2befb5e05ada4 ./platform/neuroforge/.gitignore 189486a885c7fb78e0eb878d93cda0c70ca6d7ff9bfdfb3f5f32487cf03a9688 ./platform/neuroforge/BENCHMARK-v0.5.0.md @@ -217,20 +249,23 @@ c3d34504b1b8a4ef73382df061c7b272a55c2a3a7a720435c6050254da3f22e0 ./platform/neu b4896439112f9ca0f0a43d55d43cb3a2c4c802c568b65a96d04a3ca9d841c358 ./platform/neuroforge/VALIDATION-v0.8.2.txt ceb9b2c03afb769df3b1e518520c4e1798e9a3313a1c5ffffcb054fef5d6d6fb ./platform/neuroforge/VERSION 0c5308f5a3d37ac23dce162fd5fab78ce598e41671db5dd50c9c4ae7f215d49f ./platform/neuroforge/cmd/bench/main.go -e61e226a1778caa92d8fd9c674282f47d025b2dc18f579cd822f68f568700c83 ./platform/neuroforge/cmd/server/main.go -ea9b7a78ff7e4987d4f3e8a48799ed2c109632eeb8c38284fe123dd21d7566bb ./platform/neuroforge/cmd/worker/main.go +4916d533b1b9474f7ddf99c034778466b07a39a5aa61732660456156916a41c6 ./platform/neuroforge/cmd/server/main.go +9884e323599a328e16cfe08b52473cc9346b17c3f5969fce4425d20fd0f42a93 ./platform/neuroforge/cmd/worker/main.go e3dacdaec3c629dd432f24218a50fa4de5e1698ac7f27e84b14f776b50beeb83 ./platform/neuroforge/deploy/learning-policy.example.json 3765a5faea1faeb72aad7878ff0159a56fa1b0ca1fc94a348a8c599e460b61dd ./platform/neuroforge/deploy/model-routing.example.json dcb7f80239cf955941ede9bcdc3bda8f39d81db5e5b0e0962b373036b40a0df5 ./platform/neuroforge/deploy/neuroforge.service.example 72443642fd4c498554ad6e46db2a096c97530fba1ba20b842a478345fe54c46c ./platform/neuroforge/deploy/prometheus-alerts.yml c30e5b2fd39e72894db22499259b6f97d225c5929a7f3472277853a348cab9be ./platform/neuroforge/deploy/prometheus.yml.example dbbaa7fd4430b9cdb5302144b6d40f7ac4ab9f73e81da4ddb51e788dd373d95e ./platform/neuroforge/deploy/searxng/settings.yml.example -71a16d9f64154aff65838c8afe4698dfc52a95a831b95a08e654f92aef09730a ./platform/neuroforge/docker-compose.yml +a8962d0a1adca3a80b91d1eeddeab36b91c1cf7d84f39c84bfcedb85ccf55a52 ./platform/neuroforge/docker-compose.yml fc993dc95fa49802ecb62994e4140dff18a27438e8a4f3c6352229c79b041710 ./platform/neuroforge/go.mod -585f5d02cbd11a2ebdc8e9fff536b5bd2dbd46237fa3ee23414dadda4f55a73a ./platform/neuroforge/internal/brain/brain.go +1dc1d58bf4761f2aac3f2436f78c5942bf3a5968ef88840693dd6042960f7b7f ./platform/neuroforge/internal/brain/brain.go 976288422c0c4116d8c98af8a9b164ac670f9caf03eddc2d5e2e48747456d3b4 ./platform/neuroforge/internal/brain/consolidation_test.go +cbf267375414612ce2d4a697606576f756ce3611df543ca8cb63b6c2fcc64e44 ./platform/neuroforge/internal/brain/distributed.go 0eb3cb86d1c235ac21527aae79de7c26ce204412814c5ea84dd9389e0b0fca2b ./platform/neuroforge/internal/brain/goal_progress.go 6a1e33b71d88f963f244a5a00ea45ee65bcff4882a562148d8ba925b8692e083 ./platform/neuroforge/internal/brain/goal_progress_test.go +bb4cec696bc7e5fc2b1f2fab0b5c01552f523a7dc2bfa1cb5be5e5374b304162 ./platform/neuroforge/internal/brain/orchestrator.go +03ba39168194068f59e4be837de1c152e0d10e316461b235c61704571cfc0e08 ./platform/neuroforge/internal/brain/orchestrator_test.go 359955653c647125559afd6dc3ebe69aa5ca19ff7e825ce801b7bc24e5fbcfcb ./platform/neuroforge/internal/brain/policy.go 27e87af473d2d71ba94ffb9bf7a70934776f8c23ce45496ca0998ad3000fc156 ./platform/neuroforge/internal/brain/policy_test.go 9022a797aa1d49af20ced5d8c3e0b903be740fbaa7251e8a80d43cd04d2f68fe ./platform/neuroforge/internal/brain/research_quality.go @@ -246,21 +281,21 @@ a9619d9571f6ab6363b36af9fdf1690f9f88e9333eb6f2773ae68580b19666d8 ./platform/neu cd8f1281e25ce42ae8918abbed16d4bf57ada9c3a3cc1212edd28f77ccc9328f ./platform/neuroforge/internal/brain/v6.go 312e7578b5800ccb06755c19d7ef7026a362443804ff78cb7a11a7443af2aab5 ./platform/neuroforge/internal/brain/v8.go 31420af7145686a2c7eb42fc18ca1299604afcb5942cfdfd132bef5c5f05d980 ./platform/neuroforge/internal/brain/v8_test.go -4e0f6a74fbbd54666cea2733542fbb0ddd19fad024e3fc37487f7808eb63b069 ./platform/neuroforge/internal/core/types.go +4345241b0108fb9f720df831f4878149689b8fb692d48641d3d4606ac7e89e30 ./platform/neuroforge/internal/core/types.go 65a8b8196343e7cfd9444ca314da4a83c81bc046d9478b8b93217d9cc68ba562 ./platform/neuroforge/internal/cost/cost.go b9bb2934e01ed2bfb6b16e4139e10e51f1387112eb8654f7f692779a2fd2d273 ./platform/neuroforge/internal/cost/cost_test.go fb66ce4ab760b979eacd4f7f17dedc41916f93c53ba582be572d250f99654695 ./platform/neuroforge/internal/httpapi/admin_app_auth_test.go eb2b8c599e4e0838d920296391ecaacdba1578c4f3a6a0a9e8469fdba31217ed ./platform/neuroforge/internal/httpapi/goal_duplicate_test.go -1e2cbeecce139a32d4a12137dea59d5071ed9dbebd26a2d9933c1737ae6a851a ./platform/neuroforge/internal/httpapi/httpapi.go +d7e6d3cb1705da9a174f993d60927b91d89415a7135157629113e25476fccd57 ./platform/neuroforge/internal/httpapi/httpapi.go 680b7fdcb20ac76c9305dfb94e4ec7474526be7a44b56afb3db7e4a66c8e2c38 ./platform/neuroforge/internal/httpapi/index.html 20f4a6cc30d5ce84ceed4fbdfc9f5c57ab5f82274e0c54464817d571e0e881b0 ./platform/neuroforge/internal/httpapi/integration.go c346dd0205548f7cdb00ea026f1afd82d59f87824eb983ebe84abfa7cb078974 ./platform/neuroforge/internal/httpapi/integration_api_test.go f8cfcc7a2bc781394231c25e36e75c41563f5250a1387e5687880d2591efd93b ./platform/neuroforge/internal/httpapi/integration_graph.go c289fe8748d28d4c1207b22924f7d1a7b2df25703d6df5bd1869acb325211b2e ./platform/neuroforge/internal/httpapi/integration_graph_test.go -b1a9df15a4d0263dd90b47672866f3e4e308498ae99002ba63ce128b759c8ad9 ./platform/neuroforge/internal/httpapi/knowledge.go +29c365a797f1f162f4d7a5d32833123f7930b73ca3b649fe31e117453dcb9197 ./platform/neuroforge/internal/httpapi/knowledge.go 409dd8d7c5ca93cc82ce2b7bf17a12655c97dd27ad9767aa40619502830a0ab2 ./platform/neuroforge/internal/httpapi/knowledge_integration_test.go cf285170ff175ce2bb223b4ffd87a35da604ec73b5fe386ac35402af1f9c3531 ./platform/neuroforge/internal/httpapi/knowledge_policy_test.go -cf8d0808d3f37de187c4acb5e50e094f8b43d2b7e731eecf99368ab8a1325d41 ./platform/neuroforge/internal/httpapi/metrics.go +b7ac0b25751ed312b0a39cc731396c0073d81a22dd4fbd6f4d75de18805618b4 ./platform/neuroforge/internal/httpapi/metrics.go c40412c77dd8635bbd4bf9c6d7eb11b0d7902aee896bdd1ee99e5cdfb72c7453 ./platform/neuroforge/internal/httpapi/metrics_test.go 83033a18a319ffb162c7cdefb2efe108a47dba81095ac34662349e232f8c22da ./platform/neuroforge/internal/httpapi/model_routing_test.go 38a9388b6907975aec933697ad87ddeb9e7016a279523f2526db3810df70b006 ./platform/neuroforge/internal/httpapi/outcomes.go @@ -282,19 +317,23 @@ f9cfbd0bab443faf39973e6fe1777d7137ae5dcf58fc1b0d6295099e1b3fadc9 ./platform/neu baace266205a584a53463af76038e97771f088a38e42fe333bb548e43856e719 ./platform/neuroforge/internal/provider/runtime_test.go 024d4983420ac32fb0e29ff1cf145c45756af28076e937b07c272fcec28749dc ./platform/neuroforge/internal/research/searxng.go e890897c662b070997c3b18134bafbee5bf9b3fa5220045ac09dc6e3dfd4ac41 ./platform/neuroforge/internal/research/searxng_test.go -49f89a674ab1a4387c67efd6f22406f3ab9067befc75114fe12c4801dfd1f005 ./platform/neuroforge/internal/store/batch.go +fe35f46c0b6dee7283142dc818f484006fdde6d71906d5f5fb3bc05c424ddd4f ./platform/neuroforge/internal/store/batch.go 8e7a029a904ca1c22821c5e4871c72958f0d2645786871d75f3e00c186b00778 ./platform/neuroforge/internal/store/cluster.go 6637e541f61a58f624baca31d972095ea01a95491ddb30492d9ac055740213c5 ./platform/neuroforge/internal/store/diskann.go 1858ed8045b16282de815a6668ff6027c7a3a3795e38659c753c504d0f2a8383 ./platform/neuroforge/internal/store/diskann_test.go 366e3b2f29b4abb1997aceb9c2b934ebe8f4ee44605b54167c70b8ddad683c7c ./platform/neuroforge/internal/store/goal_duplicate_test.go +38f24189c76652f12da9eef7a617455dc923499e2f21cd3c29d94231dd0d7460 ./platform/neuroforge/internal/store/graph_index.go +bda98567153542e9119757ce5f8f6014334ac9f80ad79a26ff83fdfaccc1ca8e ./platform/neuroforge/internal/store/graph_multihop_test.go d49925fda3cbe3ceeffde3369073bce5a5bc67a23defb6827e27cd4a7f4c59f5 ./platform/neuroforge/internal/store/index_segments.go 478efaeb260cb5e970af8b3bb99f232ef846f54c6e4ff9deb5499df6e008e1c1 ./platform/neuroforge/internal/store/index_segments_test.go -bea84ff9bd7812249ada186b97be58766a37d319f4d986252a0fd43e399a3dc2 ./platform/neuroforge/internal/store/knowledge.go +eef4aad176d7f6e740dff4ffd81f3dbacbcbe6646e724331ae010ab50db04b30 ./platform/neuroforge/internal/store/knowledge.go e9aaedbf4d5e9a43f4a4a5dbe2e9e0f0257b952fb59e82bf729aa5e761955c1e ./platform/neuroforge/internal/store/knowledge_test.go 43de0f541fb47633b02b25686544d44887c17b47e8d01cb23f8e57567cebdcd7 ./platform/neuroforge/internal/store/mmap_linux.go 9ce033d691157037fc6df719b5ecd8fb29d6d881d1862e2d50dc0c6c573b5f66 ./platform/neuroforge/internal/store/mmap_linux_test.go ef6135a9039fb45361e57107d3968272b4b365ac833e9e2e58b0875566bf5a07 ./platform/neuroforge/internal/store/mmap_other.go fe98dca9f919cb52140a916de08e76b88bd0d08b6b83fc8b4e5220411998b0fc ./platform/neuroforge/internal/store/observability.go +781e3b00d4acd0679adf235a6addcf44992d5039adc4f6c6e3b071c7593cc968 ./platform/neuroforge/internal/store/orchestrator.go +f830512498457ccaade5bf1c55f1caea1504bb432e95c99f5b0437794fd1d2a4 ./platform/neuroforge/internal/store/orchestrator_test.go 66d612efe2462d76d79cc51b8b6835b5990d4a79a2dc036c26b040554357ae29 ./platform/neuroforge/internal/store/pagecache.go 5df97ac71adcea7627756a52d295d682900d3b812a977dbe9a4911e4c4118809 ./platform/neuroforge/internal/store/raftlog.go fd6769bdcf1f7d22ced9ea426fd4dbc52b50b5483b4be42ac3b6adf637c926ab ./platform/neuroforge/internal/store/raftstate.go @@ -305,14 +344,14 @@ ea968456538367ed536c212707209a173098ec65823152a0813422bc8071c952 ./platform/neu 4d41586c15bfdd13139e19bfa102613f18ddc6120e4214a1f45dd9e2eaa195af ./platform/neuroforge/internal/store/sources.go 0ff464ef7fab01324d77ee4e406a8c15451b3333b75d4afa444be3c3c94f016b ./platform/neuroforge/internal/store/sources_test.go 61cd82b106e74f2aa0a7591d757a0872491a635dc1fccc78c66d41c052d0c9d2 ./platform/neuroforge/internal/store/sqar_vector.go -d8f9d45c6675fdd6dbc04b8c1ca5173c2e013024676ccab3a6594572f30a51d7 ./platform/neuroforge/internal/store/store.go +e17ceefd9d084102ca080a2978ef759eccee8e7973b2884ca1938d05f2faa6d0 ./platform/neuroforge/internal/store/store.go f86b648e8b4845ab83e672ae3fcfb0bcb0dc1bf891abf4bd30d380304f0b259a ./platform/neuroforge/internal/store/tiering.go 2138e467981a3c814d1e88842dfbd470b76333898614543cafb4f93ea8b9beaa ./platform/neuroforge/internal/store/v3.go 091e4c9cacfc76786a76f6afc4eb2b697e5a641e611475b6dab97e99371360ea ./platform/neuroforge/internal/store/v3_test.go 7f0bca5419a895258b7921e940c3fd69cce04dedcb0b9d8f1127cd24b9c9e201 ./platform/neuroforge/internal/store/v5_test.go b3a0115907719a8a7aa9e6b244eec55be71a2535ba20db65907987d5fb78dc77 ./platform/neuroforge/internal/store/vector_journal.go bcc12eb91682f3c0a626f5baeea64cb3809e56f087b9f1178a6c2546f5e301b3 ./platform/neuroforge/internal/store/vector_journal_test.go -ee2e72f00281f3b30e069abadbad2140fc841769aea27fa40f1c9f07ec877451 ./platform/neuroforge/internal/store/wal.go +e8b9a9eadab426393b47d4a0a479272449a6557b4e60af9a413589784065544f ./platform/neuroforge/internal/store/wal.go 7f0d5667c4a0e5f87f5ae86a6eb246a7a448891ccdaa3501ec81933b0430794c ./platform/neuroforge/internal/vector/hnsw.go 4bfd79da525c6d7901db01bc09c823608b641970e40aff9fdaac33da15d4432f ./platform/neuroforge/internal/vector/hnsw_test.go 6eac7fabc0f8a3cab53c758502895576fae158e3d672f8c87ad58588523919c2 ./platform/neuroforge/internal/vector/pq.go @@ -321,19 +360,19 @@ c854f70f4141344dc2c6fbec4419afe5dfe25fd1bc2da11e41781730a21c8834 ./platform/neu 32f55e82419e4b043d15682c82ade1c4bc9694bfa372f925c131c2611f48d744 ./platform/neuroforge/internal/vector/snapshot_test.go c5463c525f5ea703ea1f6df74cabc30f937f3846b1f75f237adeee2700baa977 ./platform/neuroforge/internal/vector/vector.go 276cb34ddd9f87abdac17be110e2bf15135ab83ba66bf91663d59d61fbd81125 ./platform/neuroforge/neuroforge-v0.7.3-rtx4090-example.json -9cdc135b2baa4edd699e7782b7075d09f6599c27fd26aeda136c64e12fc127ec ./platform/neuroforge/openapi.yaml -3e9a34479e5106cd7551c5bbe0eb1f6b206421ef9fbb99d7f7dd0f153f54dfeb ./scripts/backup-data.sh +621c7116df7e26b52b53d8469c41ce416bddedeb680b8e030ba2de341e42a56e ./platform/neuroforge/openapi.yaml +02ebd40234435473fa5e4bdb03f035f22564049a6f43243d7f4e175c7239ab78 ./scripts/backup-data.sh df684b1b487762248516aebb82742e5777395b504cbde42ebda85b999ae353be ./scripts/check-compose-env.py 6f1d21b7aea3265a088256801aa37a00931157523d8bd834aa4f473e99511f04 ./scripts/codebase-memory-ui.sh 5abbc6b60bca94fcb880abebae19c85c6a220c9eabb9865928b23ae86246705b ./scripts/export-obsidian.sh 01611fcaef13df301147ad539f38e96e09d32c8f0f17c0fc9a7e3dae38de6292 ./scripts/generate-secrets.sh 8b0a4be74d87cf42c98360819c6f797b9caaf10528e729159412daaaffba0771 ./scripts/go-live.sh -f8598a38e913730d5d054dd973cecc6a7632434fbbeb45932891c915c0fbc318 ./scripts/preflight.sh +0d28cf37186f9faeff2a40e506e3a3e5b8fe3a399c9c426e369bd26c9021f7b3 ./scripts/preflight.sh fe35fa5db6dabf06e6266e0de4c372604160180366d4c0a8042c7ee54b7c6dcd ./scripts/propose-draft.sh 1744be0629e845c8d94dc2a06e91ca2cc9ce5928db00300b7fb894445aeda9ff ./scripts/quality-replay.py -7f629a9fe9b538eaa028165ba2b5befd7d50fde20c052bcd453539a9a89e2351 ./scripts/release-gate.sh -42fed802f6bf6c9d46bf6eb2700fc0e10a4aa2c9447027fbae34d162805d61a0 ./scripts/research-up.sh -2e3622c298cbc7dad4df8b2b2d69a69c1f9585ee20a1474cf71f5c5807571df1 ./scripts/restore-data.sh +8b745865c41ae3fcc89280c861f78918ee947c1fae4973ca95d00a507fc3afcb ./scripts/release-gate.sh +76358a00fbc9698879d363dfca6624a13e7633181cec2ce05368cb01811b2580 ./scripts/research-up.sh +697891580c9713e6451c1e0f10509b009cfa3d3c5af4a1347f841e6f05339cea ./scripts/restore-data.sh ddd353d613d17320d6d705aee459b5b290a5f8535e19b5b664e592d13aef8fc8 ./scripts/secret-scan.sh a2c56eb668137e058cb78bb592ac359ef7e608cb9bedc7eaa930b3f4f66411ca ./scripts/status.sh 2970d5f5049323b14a3f3983c2f4eb5392d745b4fea34742e3cf56e52d740f17 ./scripts/validate.sh @@ -425,7 +464,6 @@ b4f7d72022d5009fce43b5879483de92dacfee2b3bf527306902187c3d26486b ./services/age 21b32b0b863ed62ae352011d7dd85f0134bf9909c23ee80b1c7e3c3605ef8cc4 ./services/agent/internal/web/templates/category-mappings.html d0561b87acd8fe8c0ab7f1f0bb82e1df840db8342f91798e8cb0a0408064abcc ./services/agent/internal/web/templates/dashboard.html fb5a4e2e1846046032bb878f1e878eb792497e321df6a8ce16209c07a925fdcd ./services/agent/internal/web/templates/diagnostics.html -7418ad39aa0899a85e69a9910d99ff1cb01757d5ae2edaa885c5ff7a815cb580 ./services/agent/knowledge-category-map.example.json 5a81ef73b2de4ff60c440a7a5e42da68e8ca30fab7bc62de63757157d498d72a ./services/agent/knowledge/01_active-directory.json 70b176be0117d51a35a699ab50c2ad9dc944dec807e4a193e1adbbf0ece888fc ./services/agent/knowledge/01_arbeitsplatzdrucker.json 96cf6b96e462db5e4be8345e9b18afee4e7dd5d51bcbd59e6817266725b92318 ./services/agent/knowledge/01_fachanwendung-storung.json @@ -529,15 +567,16 @@ cb3cde795233c0d4842dc7451c5855d1c071e459efbde3be0cabac32a837ba1b ./services/age 7f1d67faf4a6cea0c41c84d7b275d8a8979b19420b52db4925d8e2cd71ead3e0 ./services/agent/knowledge/16_office-aktivierung-und-lizenzierung.json f1eab883370e0a40ef52a6b6d785a510d8ed48a19d25e0a2bc95f4f2cc8e329e ./services/agent/knowledge/17_serienbriefe-und-dokumentfunktionen.json 5a0b3d5d5bc712e30a67a4de3432f69070f7e43dd99931f675e72c213006b363 ./services/agent/knowledge/example-vpn.json +7418ad39aa0899a85e69a9910d99ff1cb01757d5ae2edaa885c5ff7a815cb580 ./services/agent/knowledge-category-map.example.json 895f8400aaad550ff2b96262eaa54e954d603129682e97aa4964539eb19bd7c2 ./services/agent/run.ps1 d1d4ab8145a7131c36df51d3a9fbdcdeb0cda3de134fe24c0e8d52370862f3b2 ./services/control/Dockerfile f30ddf9251860d92717276483f7a2c2d7405f0516940ca62abc21d97ff3f3ad2 ./services/control/cmd/engineering-graph/main.go -d9a33f614749c122570eaa646478651f0371912c3ca29511da220cede48d4eeb ./services/control/engineering-graph.json +b12ace89807a1cf558d009299f59abb9103820a46d11900942bbf812ac975868 ./services/control/engineering-graph.json ba44c599b9faf861eca647614c4abd18548e2708233cf462b11eb277e96449b8 ./services/control/go.mod da37393e58ff53847f26b1051f6d3a5270571d290b33d79360bae0dcb3513829 ./services/control/graph.go e6a8021a44219219fe37ab5edc45d1908fc6579f653aa80c9966810d891ad27d ./services/control/graph_test.go -2702567a72bd89c2d4a1ab4d2fe748347b5e678493b1d99a513cc7e75d0d91dc ./services/control/index.html -6358f633a2e80844538f5f35c6f366800eb44db7ccdd8dd7cde81a9dfb33e983 ./services/control/main.go +d373caddd318992fe25be3879f3606b81bf393d55a2cb8457d3984b8af499950 ./services/control/index.html +a42b3daf9b816503801f666e322668b3ad106ff8c1e1b83855e801e7db4eabe5 ./services/control/main.go f0ec671cd3f00083d9b9d69c82ffcaa8efe08122837671e812f2a510f3c4e321 ./services/control/security_test.go 457fa6b4c81a62197eb1e76a5472cd571d5c69659110fcd9bc016582f1b5dfa1 ./services/knowledge/.dockerignore e82cbdb6336e424c4b9d8db5d149016d606579292184582788a9bf295895cda4 ./services/knowledge/.env.example diff --git a/README.md b/README.md index d3f330e..26f047c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# GLPI NeuroForge Mega v1.5.9 +# GLPI NeuroForge Mega v1.6.0 -> Release: **v1.5.9** · Article-Depth-Hardening: quellengebundene Staging-Drafts erhalten ein eigenes Context-/Output-Budget, Mindesttiefe und eine messbare Vollartikel-Qualitätsprüfung; `answer` bleibt bewusst die kompakte operative Zusammenfassung. +> Release: **v1.6.0** · Durable Master/Subagent Orchestration + Knowledge-Graph Convergence: CPU-/GPU-Arbeit wird capability-basiert, lease-gefenced und persistent geplant; importierte Memories werden bounded zu einem n:m-Synapsengraphen verknüpft und Retrieval kann mehrere Hops traversieren. Ein kontrolliertes Monorepo aus **GLPI AI Agent**, **GLPI AI Knowledgebase** und **NeuroForge + SQAR**. Ziel ist nicht ein untrennbarer Monolith, sondern eine gemeinsame Plattform mit klaren Zuständigkeiten, getrennten Credentials und nachvollziehbaren Failure-Modi. @@ -13,6 +13,19 @@ Der Fortschritt eines Research-Goals wird aus persistierten Research-Runs rekons Staging-Entwürfe sind pro Goal idempotent: Solange ein Draft aktiv im Staging liegt, wird er bei neuer Evidenz aktualisiert statt dupliziert. Nach menschlicher Promotion/Archivierung kann später wieder ein neuer Draft entstehen. +## Master / Subagents + n:m Knowledge Graph (v1.6.0) + +NeuroForge ist jetzt der autoritative **Master/Orchestrator**. CPU- und GPU-Subagents registrieren Resource-Class, Capabilities und Parallelitätsgrenzen, ziehen passende persistente Jobs und arbeiten unter Heartbeat/Lease-Fencing. Queue, Retry/Backoff, Dependencies, Idempotency und `apply_wait` überleben Master-Neustarts. Abgelaufene Lease-Tokens dürfen keine verspäteten Ergebnisse mehr schreiben; Relink-Ergebnisse sind zusätzlich an Memory-Version und Vector-Fingerprint gebunden. + +Der bisherige Knowledge-Import endet nicht mehr bei isolierten Vector-Memories. Ein bounded Graph-Backfill nutzt ANN nur zur Kandidatenauswahl und delegiert exakte Relink-Arbeit an CPU-Subagents. Ein Memory kann mehrere Synapsen besitzen; Graphmetriken messen u.a. isolierte, verknüpfte und mehrfach verknüpfte Memories, Durchschnitts-/Maximalgrad und Connected Components. Retrieval kann konfigurierbar mehrere Synapsen-Hops mit Decay verfolgen. + +Lokale Standardrollen: + +- `neuroforge-worker-cpu`: `cpu,vector.relink` +- `neuroforge-worker-gpu`: `gpu,model.chat,model.embed` + +Zusätzliche Hosts können mit `docker-compose.subagent.yml` angebunden werden. Das Control Center bleibt read-only und zeigt Master-, Worker-, Queue- und Graphzustand. Details: [`docs/MASTER-SUBAGENT-ORCHESTRATOR.md`](docs/MASTER-SUBAGENT-ORCHESTRATOR.md). + ## Unified Graph Explorer (v1.4.0) Das read-only Control Center visualisiert Runtime/Trust, Ticket-Evidence, Learning-Lineage, Research-Provenance, einen redigierten NeuroForge-Brain-Graph sowie einen reproduzierbaren Engineering-Graph aus Go-AST und Compose. Für Dateien/Symbole/Routen gibt es zusätzlich eine statische Change-Impact-/Blast-Radius-Sicht. 2D ist der operative Default; 3D ist ein optionaler, gebundener Explorer. diff --git a/RELEASE-NOTES-v1.6.0.md b/RELEASE-NOTES-v1.6.0.md new file mode 100644 index 0000000..d49b0e3 --- /dev/null +++ b/RELEASE-NOTES-v1.6.0.md @@ -0,0 +1,24 @@ +# GLPI NeuroForge Mega v1.6.0 + +## Durable Master/Subagent Orchestration + +- NeuroForge is the authoritative scheduler/master. +- Separate CPU and GPU subagents with capability/resource routing. +- Persistent priorities, dependencies, idempotency, retries/backoff and bounded queues. +- Heartbeat/lease-token fencing; expired leases cannot submit late results. +- Durable `apply_wait` phase for worker results that mutate master state. +- Admin status/job list/retry/cancel and read-only Control Center visibility. + +## Knowledge Graph Convergence + +- Bounded ANN-assisted graph backfill for imported knowledge memories. +- Multi-neighbor semantic relinking instead of leaving imported chunks isolated. +- Adjacency index, multiple relation labels per edge and bounded multi-hop retrieval. +- Graph health metrics for isolated/linked/multi-linked nodes, degree and connected components. +- Memory-version plus vector-fingerprint fencing rejects stale relink results after delete/recreate races. + +## Deployment + +- Local `neuroforge-worker-cpu` and `neuroforge-worker-gpu` services. +- `docker-compose.subagent.yml` for additional remote CPU/GPU hosts. +- Non-destructive upgrade from v1.5.9; existing volumes and knowledge remain valid. diff --git a/VERSION b/VERSION index 2b26b8d..dc1e644 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.5.9 +1.6.0 diff --git a/deployments/README.md b/deployments/README.md new file mode 100644 index 0000000..298e25d --- /dev/null +++ b/deployments/README.md @@ -0,0 +1,16 @@ +# Distributed Deployment Kits (v1.6.0) + +This directory contains three independent deployment kits: + +- `master/` - authoritative NeuroForge Master plus Agent, Knowledge, Control, optional SearXNG and optional Prometheus/Grafana. +- `cpu-subagent/` - CPU worker for `vector.relink` / graph convergence. +- `gpu-subagent/` - GPU worker plus local Ollama for `model.chat` and `model.embed`. + +The generated `NEUROFORGE_WORKER_TOKEN` is identical in all three `.env` files. Replace the RFC 5737 example IP addresses (`192.0.2.x`) with real reachable addresses before starting. + +Recommended order: +1. GPU subagent: `docker compose --profile monitoring up -d` +2. CPU subagent: `docker compose --profile monitoring up -d` +3. Master: fill GLPI credentials, then `docker compose --profile research --profile monitoring up -d` + +Only the Master holds authoritative NeuroForge state. Workers are disposable execution nodes. diff --git a/deployments/cpu-subagent/README.md b/deployments/cpu-subagent/README.md new file mode 100644 index 0000000..eb201eb --- /dev/null +++ b/deployments/cpu-subagent/README.md @@ -0,0 +1,11 @@ +# CPU Subagent + +Runs the durable CPU execution node used primarily for `vector.relink` and graph convergence. + +Edit `NEUROFORGE_MASTER_URL`, then: +```bash +./preflight.sh +docker compose --profile monitoring up -d +``` + +The optional node-exporter listens on port 9100 for Master Prometheus. Restrict it with host firewall rules to the Prometheus/Master host. diff --git a/deployments/cpu-subagent/docker-compose.yml b/deployments/cpu-subagent/docker-compose.yml new file mode 100644 index 0000000..18c716a --- /dev/null +++ b/deployments/cpu-subagent/docker-compose.yml @@ -0,0 +1,46 @@ +name: neuroforge-cpu-subagent +services: + cpu-subagent: + image: git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge-worker:${IMAGE_TAG:?Set + immutable IMAGE_TAG} + command: + - -server + - ${NEUROFORGE_MASTER_URL:?Set reachable NeuroForge master URL} + - -id + - ${NEUROFORGE_CPU_WORKER_ID:?Set CPU worker ID} + restart: unless-stopped + environment: + NEUROFORGE_WORKER_TOKEN: ${NEUROFORGE_WORKER_TOKEN:?Set worker token shared + with master} + NEUROFORGE_WORKER_RESOURCE_CLASS: cpu + NEUROFORGE_WORKER_CAPABILITIES: ${NEUROFORGE_CPU_WORKER_CAPABILITIES:-cpu,vector.relink} + NEUROFORGE_WORKER_MAX_CONCURRENCY: ${NEUROFORGE_CPU_WORKER_CONCURRENCY:-2} + NEUROFORGE_WORKER_HEARTBEAT_INTERVAL: ${NEUROFORGE_WORKER_HEARTBEAT_INTERVAL:-15s} + read_only: true + tmpfs: + - /tmp:size=32m,mode=1777 + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + node-exporter: + profiles: + - monitoring + image: ${NODE_EXPORTER_IMAGE:-prom/node-exporter:v1.12.1} + restart: unless-stopped + command: + - --path.procfs=/host/proc + - --path.sysfs=/host/sys + - --path.rootfs=/rootfs + - --collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/) + ports: + - ${EXPORTER_BIND_ADDRESS:-0.0.0.0}:${NODE_EXPORTER_HOST_PORT:-9100}:9100 + volumes: + - /proc:/host/proc:ro + - /sys:/host/sys:ro + - /:/rootfs:ro,rslave + read_only: true + security_opt: + - no-new-privileges:true + cap_drop: + - ALL diff --git a/deployments/cpu-subagent/preflight.sh b/deployments/cpu-subagent/preflight.sh new file mode 100755 index 0000000..90a0293 --- /dev/null +++ b/deployments/cpu-subagent/preflight.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env sh +set -eu +for k in NEUROFORGE_MASTER_URL NEUROFORGE_WORKER_TOKEN NEUROFORGE_CPU_WORKER_ID; do v=$(grep -E "^${k}=" .env | head -1 | cut -d= -f2- || true); [ -n "$v" ] || { echo "FEHLT: $k"; exit 1; }; echo "$v" | grep -q '192\.0\.2\.' && { echo "SETZEN: $k"; exit 1; } || true; done +command -v docker >/dev/null 2>&1 || { echo "FEHLT: docker"; exit 1; } +docker compose version >/dev/null +docker compose --profile monitoring config >/dev/null +echo "CPU subagent preflight: OK" diff --git a/deployments/gpu-subagent/README.md b/deployments/gpu-subagent/README.md new file mode 100644 index 0000000..13d3405 --- /dev/null +++ b/deployments/gpu-subagent/README.md @@ -0,0 +1,13 @@ +# GPU Subagent + Ollama + +Runs Ollama on the GPU node, pulls the configured chat and embedding models through two idempotent init services, and registers a GPU worker with capabilities `model.chat` and `model.embed`. + +Prerequisites: NVIDIA driver, NVIDIA Container Toolkit and working Docker GPU access. + +Edit `NEUROFORGE_MASTER_URL`, then: +```bash +./preflight.sh +docker compose --profile monitoring up -d +``` + +Ollama is intentionally exposed on port 11434 so the Master Agent/Knowledge services can use this GPU node directly. Restrict ports 11434, 9100 and 9400 to the Master/Prometheus host with a firewall. diff --git a/deployments/gpu-subagent/docker-compose.yml b/deployments/gpu-subagent/docker-compose.yml new file mode 100644 index 0000000..aa17baf --- /dev/null +++ b/deployments/gpu-subagent/docker-compose.yml @@ -0,0 +1,118 @@ +name: neuroforge-gpu-subagent +services: + ollama: + image: ${OLLAMA_IMAGE:-ollama/ollama:0.33.2} + restart: unless-stopped + gpus: all + environment: + OLLAMA_HOST: 0.0.0.0:11434 + OLLAMA_KEEP_ALIVE: ${OLLAMA_KEEP_ALIVE:-10m} + ports: + - ${OLLAMA_BIND_ADDRESS:-0.0.0.0}:${OLLAMA_HOST_PORT:-11434}:11434 + volumes: + - ollama-data:/root/.ollama + security_opt: + - no-new-privileges:true + healthcheck: + test: + - CMD + - ollama + - list + interval: 15s + timeout: 5s + retries: 20 + start_period: 15s + gpu-subagent: + image: git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge-worker:${IMAGE_TAG:?Set + immutable IMAGE_TAG} + command: + - -server + - ${NEUROFORGE_MASTER_URL:?Set reachable NeuroForge master URL} + - -id + - ${NEUROFORGE_GPU_WORKER_ID:?Set GPU worker ID} + restart: unless-stopped + environment: + NEUROFORGE_WORKER_TOKEN: ${NEUROFORGE_WORKER_TOKEN:?Set worker token shared + with master} + NEUROFORGE_WORKER_RESOURCE_CLASS: gpu + NEUROFORGE_WORKER_CAPABILITIES: ${NEUROFORGE_GPU_WORKER_CAPABILITIES:-gpu,model.chat,model.embed} + NEUROFORGE_WORKER_MAX_CONCURRENCY: ${NEUROFORGE_GPU_WORKER_CONCURRENCY:-1} + NEUROFORGE_WORKER_HEARTBEAT_INTERVAL: ${NEUROFORGE_WORKER_HEARTBEAT_INTERVAL:-15s} + NEUROFORGE_WORKER_OLLAMA_URL: http://ollama:11434 + NEUROFORGE_WORKER_OLLAMA_CHAT_MODEL: ${OLLAMA_MODEL:-gemma4} + NEUROFORGE_WORKER_OLLAMA_EMBEDDING_MODEL: ${OLLAMA_EMBEDDING_MODEL:-embeddinggemma} + NEUROFORGE_WORKER_OLLAMA_NUM_CTX: ${NEUROFORGE_OLLAMA_NUM_CTX:-8192} + NEUROFORGE_WORKER_OLLAMA_KEEP_ALIVE: ${OLLAMA_KEEP_ALIVE:-10m} + depends_on: + ollama-model-chat-init: + condition: service_completed_successfully + ollama-model-embed-init: + condition: service_completed_successfully + read_only: true + tmpfs: + - /tmp:size=32m,mode=1777 + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + node-exporter: + profiles: + - monitoring + image: ${NODE_EXPORTER_IMAGE:-prom/node-exporter:v1.12.1} + restart: unless-stopped + command: + - --path.procfs=/host/proc + - --path.sysfs=/host/sys + - --path.rootfs=/rootfs + - --collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/) + ports: + - ${EXPORTER_BIND_ADDRESS:-0.0.0.0}:${NODE_EXPORTER_HOST_PORT:-9100}:9100 + volumes: + - /proc:/host/proc:ro + - /sys:/host/sys:ro + - /:/rootfs:ro,rslave + read_only: true + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + dcgm-exporter: + profiles: + - monitoring + image: ${DCGM_EXPORTER_IMAGE:-nvcr.io/nvidia/k8s/dcgm-exporter:4.6.0-4.8.3-distroless} + restart: unless-stopped + gpus: all + cap_add: + - SYS_ADMIN + ports: + - ${EXPORTER_BIND_ADDRESS:-0.0.0.0}:${DCGM_EXPORTER_HOST_PORT:-9400}:9400 + security_opt: + - no-new-privileges:true + ollama-model-chat-init: + image: ${OLLAMA_IMAGE:-ollama/ollama:0.33.2} + restart: 'no' + command: + - pull + - ${OLLAMA_MODEL:-gemma4} + environment: + OLLAMA_HOST: http://ollama:11434 + depends_on: + ollama: + condition: service_healthy + security_opt: + - no-new-privileges:true + ollama-model-embed-init: + image: ${OLLAMA_IMAGE:-ollama/ollama:0.33.2} + restart: 'no' + command: + - pull + - ${OLLAMA_EMBEDDING_MODEL:-embeddinggemma} + environment: + OLLAMA_HOST: http://ollama:11434 + depends_on: + ollama: + condition: service_healthy + security_opt: + - no-new-privileges:true +volumes: + ollama-data: null diff --git a/deployments/gpu-subagent/preflight.sh b/deployments/gpu-subagent/preflight.sh new file mode 100755 index 0000000..1337bb7 --- /dev/null +++ b/deployments/gpu-subagent/preflight.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env sh +set -eu +for k in NEUROFORGE_MASTER_URL NEUROFORGE_WORKER_TOKEN NEUROFORGE_GPU_WORKER_ID OLLAMA_MODEL OLLAMA_EMBEDDING_MODEL; do v=$(grep -E "^${k}=" .env | head -1 | cut -d= -f2- || true); [ -n "$v" ] || { echo "FEHLT: $k"; exit 1; }; echo "$v" | grep -q '192\.0\.2\.' && { echo "SETZEN: $k"; exit 1; } || true; done +command -v docker >/dev/null 2>&1 || { echo "FEHLT: docker"; exit 1; } +docker compose version >/dev/null +docker compose --profile monitoring config >/dev/null +command -v nvidia-smi >/dev/null 2>&1 || echo "WARNUNG: nvidia-smi nicht gefunden; NVIDIA Host/Toolkit prüfen." +echo "GPU subagent preflight: OK" diff --git a/deployments/master/README.md b/deployments/master/README.md new file mode 100644 index 0000000..527da9b --- /dev/null +++ b/deployments/master/README.md @@ -0,0 +1,20 @@ +# Master Deployment + +The Master compose deliberately contains **no local CPU/GPU NeuroForge workers**. It uses remote subagents. Agent, Knowledge and NeuroForge use `OLLAMA_BASE_URL`/`OLLAMA_URLS`, normally pointing to the GPU subagent host. + +Before first start: +- replace the `192.0.2.x` example addresses; +- fill the five GLPI credentials in `.env`; +- expose the NeuroForge Master port only to trusted subagent networks or put it behind TLS/mTLS/reverse proxy; +- keep Grafana and Prometheus bound to loopback unless intentionally proxied. + +Start: +```bash +./preflight.sh +docker compose --profile research --profile monitoring up -d +``` + +Monitoring: +- Prometheus: `http://127.0.0.1:${PROMETHEUS_HOST_PORT:-9090}` +- Grafana: `http://127.0.0.1:${GRAFANA_HOST_PORT:-3000}` +- Dashboard is provisioned automatically in folder `NeuroForge`. diff --git a/deployments/master/backups/.gitkeep b/deployments/master/backups/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/deployments/master/deploy/searxng/settings.yml b/deployments/master/deploy/searxng/settings.yml new file mode 100644 index 0000000..d68a867 --- /dev/null +++ b/deployments/master/deploy/searxng/settings.yml @@ -0,0 +1,26 @@ +# Private SearXNG instance for NeuroForge research. +# SEARXNG_SECRET and SEARXNG_BASE_URL override the corresponding server values. +use_default_settings: true + +general: + debug: false + instance_name: "NeuroForge Research Search" + +search: + safe_search: 1 + formats: + - html + - json + +server: + secret_key: "overridden-by-SEARXNG_SECRET" + limiter: false + public_instance: false + image_proxy: false + default_http_headers: + X-Robots-Tag: "noindex, nofollow" + Referrer-Policy: "no-referrer" + +outgoing: + request_timeout: 5.0 + max_request_timeout: 15.0 diff --git a/deployments/master/docker-compose.yml b/deployments/master/docker-compose.yml new file mode 100644 index 0000000..a3e00d2 --- /dev/null +++ b/deployments/master/docker-compose.yml @@ -0,0 +1,589 @@ +name: glpi-neuroforge-master +services: + searxng: + image: ${SEARXNG_IMAGE:-docker.io/searxng/searxng:latest} + profiles: + - research + restart: unless-stopped + environment: + SEARXNG_SECRET: ${SEARXNG_SECRET} + SEARXNG_BASE_URL: http://searxng:8080/ + FORCE_OWNERSHIP: 'false' + volumes: + - ./deploy/searxng/settings.yml:/etc/searxng/settings.yml:ro + - searxng-cache:/var/cache/searxng + ports: + - 127.0.0.1:${SEARXNG_HOST_PORT:-8888}:8080 + read_only: true + tmpfs: + - /tmp:size=64m,mode=1777 + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + neuroforge: + image: git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge:${IMAGE_TAG:?Set IMAGE_TAG + to an immutable release tag, for example 1.6.0} + command: + - -data + - /app/data + - -listen + - :8080 + restart: unless-stopped + environment: + NEUROFORGE_ADMIN_TOKEN: ${NEUROFORGE_ADMIN_TOKEN:?Set a unique NeuroForge admin + token} + NEUROFORGE_APP_API_KEY: ${NEUROFORGE_APP_API_KEY:?Set a unique NeuroForge app + API key} + NEUROFORGE_INTEGRATION_TOKEN: ${NEUROFORGE_INTEGRATION_TOKEN:?Set a unique NeuroForge + integration token} + NEUROFORGE_CONTROL_READ_TOKEN: ${NEUROFORGE_CONTROL_READ_TOKEN:?Set a unique + NeuroForge control read token} + NEUROFORGE_WORKER_TOKEN: ${NEUROFORGE_WORKER_TOKEN:?Set a unique NeuroForge + worker token} + NEUROFORGE_METRICS_TOKEN: ${NEUROFORGE_METRICS_TOKEN:?Set a unique NeuroForge + metrics token} + NEUROFORGE_CLUSTER_TOKEN: ${NEUROFORGE_CLUSTER_TOKEN:-} + OPENAI_API_KEY: ${OPENAI_API_KEY:-} + NEUROFORGE_OLLAMA_URL: ${OLLAMA_BASE_URL:?Set Ollama URL reachable from master, + normally GPU subagent host} + NEUROFORGE_OLLAMA_CHAT_MODEL: ${OLLAMA_MODEL:-gemma3} + NEUROFORGE_OLLAMA_EMBEDDING_MODEL: ${OLLAMA_EMBEDDING_MODEL:-embeddinggemma} + NEUROFORGE_OLLAMA_NUM_CTX: ${NEUROFORGE_OLLAMA_NUM_CTX:-8192} + NEUROFORGE_OLLAMA_NUM_PREDICT: ${NEUROFORGE_OLLAMA_NUM_PREDICT:-0} + NEUROFORGE_WORKER_LEASE_SECONDS: ${NEUROFORGE_WORKER_LEASE_SECONDS:-120} + NEUROFORGE_WORKER_HEARTBEAT_SECONDS: ${NEUROFORGE_WORKER_HEARTBEAT_SECONDS:-15} + NEUROFORGE_WORKER_STALE_AFTER_SECONDS: ${NEUROFORGE_WORKER_STALE_AFTER_SECONDS:-60} + NEUROFORGE_WORKER_DEFAULT_MAX_ATTEMPTS: ${NEUROFORGE_WORKER_DEFAULT_MAX_ATTEMPTS:-3} + NEUROFORGE_WORKER_RETRY_BACKOFF_SECONDS: ${NEUROFORGE_WORKER_RETRY_BACKOFF_SECONDS:-15} + NEUROFORGE_WORKER_MAX_QUEUED_JOBS: ${NEUROFORGE_WORKER_MAX_QUEUED_JOBS:-5000} + NEUROFORGE_WORKER_MASTER_APPLY_MAX_ATTEMPTS: ${NEUROFORGE_WORKER_MASTER_APPLY_MAX_ATTEMPTS:-5} + NEUROFORGE_WORKER_MASTER_APPLY_BACKOFF_SECONDS: ${NEUROFORGE_WORKER_MASTER_APPLY_BACKOFF_SECONDS:-5} + NEUROFORGE_WORKER_JOB_RETENTION_HOURS: ${NEUROFORGE_WORKER_JOB_RETENTION_HOURS:-168} + NEUROFORGE_WORKER_MAX_TERMINAL_JOBS: ${NEUROFORGE_WORKER_MAX_TERMINAL_JOBS:-20000} + NEUROFORGE_GRAPH_BACKFILL_ENABLED: ${NEUROFORGE_GRAPH_BACKFILL_ENABLED:-true} + NEUROFORGE_GRAPH_BACKFILL_INTERVAL_SECONDS: ${NEUROFORGE_GRAPH_BACKFILL_INTERVAL_SECONDS:-10} + NEUROFORGE_GRAPH_BACKFILL_BATCH_SIZE: ${NEUROFORGE_GRAPH_BACKFILL_BATCH_SIZE:-64} + NEUROFORGE_GRAPH_BACKFILL_MAX_QUEUED: ${NEUROFORGE_GRAPH_BACKFILL_MAX_QUEUED:-256} + NEUROFORGE_GRAPH_BACKFILL_MIN_DEGREE: ${NEUROFORGE_GRAPH_BACKFILL_MIN_DEGREE:-3} + NEUROFORGE_GRAPH_CANDIDATE_MULTIPLIER: ${NEUROFORGE_GRAPH_CANDIDATE_MULTIPLIER:-6} + NEUROFORGE_GRAPH_RETRY_AFTER_MINUTES: ${NEUROFORGE_GRAPH_RETRY_AFTER_MINUTES:-360} + NEUROFORGE_GRAPH_REQUIRE_WORKER: ${NEUROFORGE_GRAPH_REQUIRE_WORKER:-true} + NEUROFORGE_GRAPH_MAX_HOPS: ${NEUROFORGE_GRAPH_MAX_HOPS:-3} + NEUROFORGE_GRAPH_HOP_DECAY: ${NEUROFORGE_GRAPH_HOP_DECAY:-0.60} + NEUROFORGE_GRAPH_MAX_EXPANSION: ${NEUROFORGE_GRAPH_MAX_EXPANSION:-64} + NEUROFORGE_GRAPH_MIN_EDGE_WEIGHT: ${NEUROFORGE_GRAPH_MIN_EDGE_WEIGHT:-0.05} + NEUROFORGE_OFFLOAD_CHAT: ${NEUROFORGE_OFFLOAD_CHAT:-true} + NEUROFORGE_OFFLOAD_EMBEDDINGS: ${NEUROFORGE_OFFLOAD_EMBEDDINGS:-true} + NEUROFORGE_DISTRIBUTED_INFERENCE_WAIT_SECONDS: ${NEUROFORGE_DISTRIBUTED_INFERENCE_WAIT_SECONDS:-180} + NEUROFORGE_READINESS_OLLAMA_LIVE: ${NEUROFORGE_READINESS_OLLAMA_LIVE:-true} + NEUROFORGE_CONTROLLED_LEARNING: ${NEUROFORGE_CONTROLLED_LEARNING:-true} + NEUROFORGE_GOAL_LEARNING_ENABLED: ${NEUROFORGE_GOAL_LEARNING_ENABLED:-false} + NEUROFORGE_RESEARCH_ENABLED: ${NEUROFORGE_RESEARCH_ENABLED:-false} + NEUROFORGE_SEARXNG_ENABLED: ${NEUROFORGE_SEARXNG_ENABLED:-false} + NEUROFORGE_SEARXNG_URL: ${NEUROFORGE_SEARXNG_URL:-http://searxng:8080} + NEUROFORGE_RESEARCH_GOAL_ENABLED: ${NEUROFORGE_RESEARCH_GOAL_ENABLED:-true} + NEUROFORGE_AUTONOMY_ENABLED: ${NEUROFORGE_AUTONOMY_ENABLED:-false} + NEUROFORGE_AUTONOMY_INTERVAL_MINUTES: ${NEUROFORGE_AUTONOMY_INTERVAL_MINUTES:-30} + NEUROFORGE_RESEARCH_MAX_QUERIES: ${NEUROFORGE_RESEARCH_MAX_QUERIES:-2} + NEUROFORGE_RESEARCH_MAX_PAGES: ${NEUROFORGE_RESEARCH_MAX_PAGES:-4} + NEUROFORGE_KB_STAGING_ENABLED: ${NEUROFORGE_KB_STAGING_ENABLED:-true} + NEUROFORGE_KB_STAGING_URL: http://knowledge:8080/api/integrations/staging + NEUROFORGE_KB_STAGING_TOKEN: ${KB_INTEGRATION_TOKEN:?Set the Knowledge integration + token} + NEUROFORGE_KB_STAGING_MIN_EVIDENCE: ${NEUROFORGE_KB_STAGING_MIN_EVIDENCE:-4} + NEUROFORGE_KB_STAGING_MIN_SOURCES: ${NEUROFORGE_KB_STAGING_MIN_SOURCES:-2} + NEUROFORGE_KB_STAGING_MIN_CORROBORATIONS: ${NEUROFORGE_KB_STAGING_MIN_CORROBORATIONS:-0} + NEUROFORGE_KB_STAGING_MAX_EVIDENCE: ${NEUROFORGE_KB_STAGING_MAX_EVIDENCE:-12} + NEUROFORGE_KB_STAGING_SYNTHESIS_MODE: ${NEUROFORGE_KB_STAGING_SYNTHESIS_MODE:-llm} + NEUROFORGE_KB_STAGING_REQUIRE_AUTHORITATIVE_SOURCE: ${NEUROFORGE_KB_STAGING_REQUIRE_AUTHORITATIVE_SOURCE:-true} + NEUROFORGE_KB_STAGING_MIN_AUTHORITATIVE_SOURCES: ${NEUROFORGE_KB_STAGING_MIN_AUTHORITATIVE_SOURCES:-1} + NEUROFORGE_KB_STAGING_AUTHORITATIVE_DOMAINS: ${NEUROFORGE_KB_STAGING_AUTHORITATIVE_DOMAINS:-} + NEUROFORGE_KB_STAGING_VERIFY_CLAIMS: ${NEUROFORGE_KB_STAGING_VERIFY_CLAIMS:-true} + NEUROFORGE_KB_STAGING_MIN_CLAIM_COVERAGE: ${NEUROFORGE_KB_STAGING_MIN_CLAIM_COVERAGE:-1.0} + NEUROFORGE_KB_STAGING_REQUIRE_AUTHORITATIVE_ACTIONS: ${NEUROFORGE_KB_STAGING_REQUIRE_AUTHORITATIVE_ACTIONS:-true} + NEUROFORGE_KB_STAGING_MAX_VERIFICATION_STATEMENTS: ${NEUROFORGE_KB_STAGING_MAX_VERIFICATION_STATEMENTS:-32} + NEUROFORGE_KB_STAGING_VERIFICATION_REPAIR: ${NEUROFORGE_KB_STAGING_VERIFICATION_REPAIR:-true} + NEUROFORGE_KB_STAGING_SYNTHESIS_MAX_TOKENS: ${NEUROFORGE_KB_STAGING_SYNTHESIS_MAX_TOKENS:-2600} + NEUROFORGE_KB_STAGING_EVIDENCE_PROMPT_MAX_CHARS: ${NEUROFORGE_KB_STAGING_EVIDENCE_PROMPT_MAX_CHARS:-14000} + NEUROFORGE_KB_STAGING_MIN_ARTICLE_CHARS: ${NEUROFORGE_KB_STAGING_MIN_ARTICLE_CHARS:-3500} + NEUROFORGE_KB_STAGING_TARGET_ARTICLE_CHARS: ${NEUROFORGE_KB_STAGING_TARGET_ARTICLE_CHARS:-6500} + NEUROFORGE_KB_STAGING_MAX_ARTICLE_CHARS: ${NEUROFORGE_KB_STAGING_MAX_ARTICLE_CHARS:-10000} + NEUROFORGE_KB_STAGING_MIN_ANSWER_CHARS: ${NEUROFORGE_KB_STAGING_MIN_ANSWER_CHARS:-160} + NEUROFORGE_KB_STAGING_MAX_ANSWER_CHARS: ${NEUROFORGE_KB_STAGING_MAX_ANSWER_CHARS:-1200} + ports: + - ${NEUROFORGE_BIND_ADDRESS:-0.0.0.0}:${NEUROFORGE_HOST_PORT:-8090}:8080 + volumes: + - neuroforge-data:/app/data + read_only: true + tmpfs: + - /tmp:size=64m,mode=1777 + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + healthcheck: + test: + - CMD + - wget + - -q + - -O + - '-' + - http://127.0.0.1:8080/readyz + interval: 15s + timeout: 3s + retries: 8 + start_period: 15s + stop_grace_period: 35s + agent-data-init: + image: git.send.nrw/sendnrw/glpi-neuroforge-mega-agent-data-init:${IMAGE_TAG:?Set + IMAGE_TAG to an immutable release tag, for example 1.6.0} + restart: 'no' + user: 0:0 + volumes: + - agent-data:/app/data + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + cap_add: + - CHOWN + - FOWNER + agent: + image: git.send.nrw/sendnrw/glpi-neuroforge-mega-agent:${IMAGE_TAG:?Set IMAGE_TAG + to an immutable release tag, for example 1.6.0} + restart: unless-stopped + environment: + AI_CONTENT_LABEL_ENABLED: ${AI_CONTENT_LABEL_ENABLED:-} + AUTO_CATEGORY: ${AUTO_CATEGORY:-} + AUTO_ESCALATION: ${AUTO_ESCALATION:-} + AUTO_PRIORITY: ${AUTO_PRIORITY:-} + AUTO_REPLY: ${AUTO_REPLY:-} + BRAIN_ACTIVITY_API_KEY: ${NEUROFORGE_INTEGRATION_TOKEN:?Set the NeuroForge integration + token} + BRAIN_ACTIVITY_URL: http://neuroforge:8080/api/v1/integrations/events + CATEGORY_CONFIDENCE: ${CATEGORY_CONFIDENCE:-} + CATEGORY_PROMPT_LIMIT: ${CATEGORY_PROMPT_LIMIT:-} + CHANGE_CALENDAR_ENABLED: ${CHANGE_CALENDAR_ENABLED:-} + CHANGE_LOOKAHEAD: ${CHANGE_LOOKAHEAD:-} + CHANGE_LOOKBACK: ${CHANGE_LOOKBACK:-} + COMMUNICATION_CLOSING: ${COMMUNICATION_CLOSING:-} + COMMUNICATION_LANGUAGE: ${COMMUNICATION_LANGUAGE:-} + COMMUNICATION_SALUTATION: ${COMMUNICATION_SALUTATION:-} + COMMUNICATION_SIGNATURE: ${COMMUNICATION_SIGNATURE:-} + COMMUNICATION_STYLE: ${COMMUNICATION_STYLE:-} + CONTEXT_BLOCK_AUTO_REPLY_ON_ERRORS: ${CONTEXT_BLOCK_AUTO_REPLY_ON_ERRORS:-} + CONTEXT_BLOCK_AUTO_REPLY_ON_INCIDENT: ${CONTEXT_BLOCK_AUTO_REPLY_ON_INCIDENT:-} + CONTEXT_ENABLED: ${CONTEXT_ENABLED:-} + CONTEXT_INCIDENT_REPLY_TEXT: ${CONTEXT_INCIDENT_REPLY_TEXT:-} + CONTEXT_MAINTENANCE_REPLY_TEXT: ${CONTEXT_MAINTENANCE_REPLY_TEXT:-} + CONTEXT_RELEVANCE_MIN_SCORE: ${CONTEXT_RELEVANCE_MIN_SCORE:-} + CONTEXT_STATUS_REPLY_ENABLED: ${CONTEXT_STATUS_REPLY_ENABLED:-} + CONTEXT_STATUS_REPLY_MIN_AI_CONFIDENCE: ${CONTEXT_STATUS_REPLY_MIN_AI_CONFIDENCE:-} + CONTEXT_STATUS_REPLY_MIN_FINAL_SCORE: ${CONTEXT_STATUS_REPLY_MIN_FINAL_SCORE:-} + CONTEXT_STATUS_REPLY_MIN_RELEVANCE: ${CONTEXT_STATUS_REPLY_MIN_RELEVANCE:-} + CONTEXT_TIMEOUT: ${CONTEXT_TIMEOUT:-} + CONTROL_READ_TOKEN: ${CONTROL_READ_TOKEN:?Set the Agent control read token} + DATA_DIR: /app/data + DRY_RUN: ${DRY_RUN:-} + ESCALATION_ADD_PRIVATE_FOLLOWUP: ${ESCALATION_ADD_PRIVATE_FOLLOWUP:-} + ESCALATION_ALLOWED_ACTIONS: ${ESCALATION_ALLOWED_ACTIONS:-} + ESCALATION_ALLOWED_REASON_CODES: ${ESCALATION_ALLOWED_REASON_CODES:-} + ESCALATION_ANALYSIS_TIMEOUT: ${ESCALATION_ANALYSIS_TIMEOUT:-} + ESCALATION_CONFIDENCE: ${ESCALATION_CONFIDENCE:-} + ESCALATION_ENABLED: ${ESCALATION_ENABLED:-} + ESCALATION_MAJOR_INCIDENT_MIN_RELEVANCE: ${ESCALATION_MAJOR_INCIDENT_MIN_RELEVANCE:-} + ESCALATION_MAJOR_INCIDENT_NOTE: ${ESCALATION_MAJOR_INCIDENT_NOTE:-} + ESCALATION_MANAGER_REVIEW_GROUP_ID: ${ESCALATION_MANAGER_REVIEW_GROUP_ID:-} + ESCALATION_MANAGER_REVIEW_MIN_LEVEL: ${ESCALATION_MANAGER_REVIEW_MIN_LEVEL:-} + ESCALATION_MANAGER_REVIEW_NOTE: ${ESCALATION_MANAGER_REVIEW_NOTE:-} + ESCALATION_MANAGER_REVIEW_USER_ID: ${ESCALATION_MANAGER_REVIEW_USER_ID:-} + ESCALATION_MAX_LEVEL: ${ESCALATION_MAX_LEVEL:-} + ESCALATION_MIN_AGE: ${ESCALATION_MIN_AGE:-} + ESCALATION_MIN_INACTIVITY: ${ESCALATION_MIN_INACTIVITY:-} + ESCALATION_SCAN_INTERVAL: ${ESCALATION_SCAN_INTERVAL:-} + ESCALATION_SECOND_LEVEL_GROUP_ID: ${ESCALATION_SECOND_LEVEL_GROUP_ID:-} + ESCALATION_SECOND_LEVEL_NOTE: ${ESCALATION_SECOND_LEVEL_NOTE:-} + ESCALATION_SECURITY_GROUP_ID: ${ESCALATION_SECURITY_GROUP_ID:-} + ESCALATION_SECURITY_NOTE: ${ESCALATION_SECURITY_NOTE:-} + ESCALATION_SERVICE_OWNER_GROUP_ID: ${ESCALATION_SERVICE_OWNER_GROUP_ID:-} + ESCALATION_SERVICE_OWNER_MIN_LEVEL: ${ESCALATION_SERVICE_OWNER_MIN_LEVEL:-} + ESCALATION_SERVICE_OWNER_NOTE: ${ESCALATION_SERVICE_OWNER_NOTE:-} + ESCALATION_SERVICE_OWNER_USER_ID: ${ESCALATION_SERVICE_OWNER_USER_ID:-} + ESCALATION_SLA_RISK_WINDOW: ${ESCALATION_SLA_RISK_WINDOW:-} + ESCALATION_WEBHOOK_ALLOW_INSECURE_HTTP: ${ESCALATION_WEBHOOK_ALLOW_INSECURE_HTTP:-} + ESCALATION_WEBHOOK_BEARER_TOKEN: ${ESCALATION_WEBHOOK_BEARER_TOKEN:-} + ESCALATION_WEBHOOK_TIMEOUT: ${ESCALATION_WEBHOOK_TIMEOUT:-} + ESCALATION_WEBHOOK_URL: ${ESCALATION_WEBHOOK_URL:-} + GLPI_AGENT_USER_ID: ${GLPI_AGENT_USER_ID:-} + GLPI_ALLOWED_STATUS_IDS: ${GLPI_ALLOWED_STATUS_IDS:-} + GLPI_ALLOW_INSECURE_HTTP: ${GLPI_ALLOW_INSECURE_HTTP:-} + GLPI_API_VERSION: ${GLPI_API_VERSION:-} + GLPI_CHANGE_FILTER: ${GLPI_CHANGE_FILTER:-} + GLPI_CHANGE_LIMIT: ${GLPI_CHANGE_LIMIT:-} + GLPI_CHANGE_PATH: ${GLPI_CHANGE_PATH:-} + GLPI_CLIENT_ID: ${GLPI_CLIENT_ID:?Set GLPI_CLIENT_ID} + GLPI_CLIENT_SECRET: ${GLPI_CLIENT_SECRET:?Set GLPI_CLIENT_SECRET} + GLPI_ESCALATION_FILTER: ${GLPI_ESCALATION_FILTER:-} + GLPI_ESCALATION_GROUP_PATCH_FIELD: ${GLPI_ESCALATION_GROUP_PATCH_FIELD:-} + GLPI_ESCALATION_ITIL_LINK_BODY: ${GLPI_ESCALATION_ITIL_LINK_BODY:-} + GLPI_ESCALATION_ITIL_LINK_PATH: ${GLPI_ESCALATION_ITIL_LINK_PATH:-} + GLPI_ESCALATION_LIMIT: ${GLPI_ESCALATION_LIMIT:-} + GLPI_ESCALATION_USER_PATCH_FIELD: ${GLPI_ESCALATION_USER_PATCH_FIELD:-} + GLPI_KB_AUTO_REPLY: ${GLPI_KB_AUTO_REPLY:-} + GLPI_KB_AUTO_REPLY_ALLOW_UNCATEGORIZED: ${GLPI_KB_AUTO_REPLY_ALLOW_UNCATEGORIZED:-} + GLPI_KB_AUTO_REPLY_CATEGORY_IDS: ${GLPI_KB_AUTO_REPLY_CATEGORY_IDS:-} + GLPI_KB_AUTO_REPLY_ITIL_CATEGORY_IDS: ${GLPI_KB_AUTO_REPLY_ITIL_CATEGORY_IDS:-} + GLPI_KB_AUTO_REPLY_UNCATEGORIZED_ARTICLE_IDS: ${GLPI_KB_AUTO_REPLY_UNCATEGORIZED_ARTICLE_IDS:-} + GLPI_KB_ENABLED: ${GLPI_KB_ENABLED:-} + GLPI_KB_FILTER: ${GLPI_KB_FILTER:-} + GLPI_KB_LIMIT: ${GLPI_KB_LIMIT:-} + GLPI_KB_PATH: ${GLPI_KB_PATH:-} + GLPI_KB_SOURCE: ${GLPI_KB_SOURCE:-} + GLPI_KB_SYNC_INTERVAL: ${GLPI_KB_SYNC_INTERVAL:-} + GLPI_MAJOR_INCIDENT_FILTER: ${GLPI_MAJOR_INCIDENT_FILTER:-} + GLPI_MAJOR_INCIDENT_LIMIT: ${GLPI_MAJOR_INCIDENT_LIMIT:-} + GLPI_PASSWORD: ${GLPI_PASSWORD:?Set GLPI_PASSWORD} + GLPI_POLL_INTERVAL: ${GLPI_POLL_INTERVAL:-} + GLPI_POLL_LIMIT: ${GLPI_POLL_LIMIT:-} + GLPI_TICKET_FILTER: ${GLPI_TICKET_FILTER:-} + GLPI_TIMEOUT: ${GLPI_TIMEOUT:-} + GLPI_URL: ${GLPI_URL:?Set GLPI_URL} + GLPI_USERNAME: ${GLPI_USERNAME:?Set GLPI_USERNAME} + GLPI_USER_DEVICE_FILTER_TEMPLATE: ${GLPI_USER_DEVICE_FILTER_TEMPLATE:-} + GLPI_USER_DEVICE_LIMIT: ${GLPI_USER_DEVICE_LIMIT:-} + GLPI_USER_DEVICE_PATHS: ${GLPI_USER_DEVICE_PATHS:-} + HTTP_ADDR: :8080 + KNOWLEDGE_ALLOWED_SOURCES: ${KNOWLEDGE_ALLOWED_SOURCES:-} + KNOWLEDGE_AUDIT_TOP_K: ${KNOWLEDGE_AUDIT_TOP_K:-} + KNOWLEDGE_AUTO_REPLY_SOURCES: ${KNOWLEDGE_AUTO_REPLY_SOURCES:-} + KNOWLEDGE_CANDIDATE_MAX_GAP: ${KNOWLEDGE_CANDIDATE_MAX_GAP:-} + KNOWLEDGE_CATEGORY_MAP_FILE: ${KNOWLEDGE_CATEGORY_MAP_FILE:-} + KNOWLEDGE_CATEGORY_MODE: ${KNOWLEDGE_CATEGORY_MODE:-} + KNOWLEDGE_CATEGORY_SOURCES: ${KNOWLEDGE_CATEGORY_SOURCES:-} + KNOWLEDGE_CHUNK_OVERLAP_WORDS: ${KNOWLEDGE_CHUNK_OVERLAP_WORDS:-} + KNOWLEDGE_CHUNK_WORDS: ${KNOWLEDGE_CHUNK_WORDS:-} + KNOWLEDGE_DIR: /app/knowledge + KNOWLEDGE_EMBEDDING_PROFILE: ${KNOWLEDGE_EMBEDDING_PROFILE:-} + KNOWLEDGE_EMBED_BATCH_SIZE: ${KNOWLEDGE_EMBED_BATCH_SIZE:-} + KNOWLEDGE_EVIDENCE_WEIGHT_AI: ${KNOWLEDGE_EVIDENCE_WEIGHT_AI:-} + KNOWLEDGE_EVIDENCE_WEIGHT_CATEGORY: ${KNOWLEDGE_EVIDENCE_WEIGHT_CATEGORY:-} + KNOWLEDGE_EVIDENCE_WEIGHT_RETRIEVAL: ${KNOWLEDGE_EVIDENCE_WEIGHT_RETRIEVAL:-} + KNOWLEDGE_IGNORE_GLOBS: ${KNOWLEDGE_IGNORE_GLOBS:-} + KNOWLEDGE_INDEX_MODE: ${KNOWLEDGE_INDEX_MODE:-} + KNOWLEDGE_INDEX_SCAN_INTERVAL: ${KNOWLEDGE_INDEX_SCAN_INTERVAL:-} + KNOWLEDGE_MAX_CHUNKS_PER_DOC: ${KNOWLEDGE_MAX_CHUNKS_PER_DOC:-} + KNOWLEDGE_MAX_QUERY_CHUNKS: ${KNOWLEDGE_MAX_QUERY_CHUNKS:-} + KNOWLEDGE_MIN_SCORE: ${KNOWLEDGE_MIN_SCORE:-} + KNOWLEDGE_RETRIEVAL_FLOOR: ${KNOWLEDGE_RETRIEVAL_FLOOR:-} + KNOWLEDGE_TOP_K: ${KNOWLEDGE_TOP_K:-} + KNOWLEDGE_VECTOR_BACKEND: ${KNOWLEDGE_VECTOR_BACKEND:-dual} + KNOWLEDGE_WEB_EDIT_ENABLED: ${AGENT_LEGACY_KNOWLEDGE_EDITOR_ENABLED:-false} + KNOWLEDGE_WEIGHT_CATEGORY: ${KNOWLEDGE_WEIGHT_CATEGORY:-} + KNOWLEDGE_WEIGHT_KEYWORDS: ${KNOWLEDGE_WEIGHT_KEYWORDS:-} + KNOWLEDGE_WEIGHT_LEXICAL: ${KNOWLEDGE_WEIGHT_LEXICAL:-} + KNOWLEDGE_WEIGHT_SEMANTIC: ${KNOWLEDGE_WEIGHT_SEMANTIC:-} + KNOWLEDGE_WEIGHT_TITLE: ${KNOWLEDGE_WEIGHT_TITLE:-} + LEARNING_ENABLED: ${LEARNING_ENABLED:-} + LEARNING_EXAMPLES_PER_CATEGORY: ${LEARNING_EXAMPLES_PER_CATEGORY:-} + LEARNING_MAX_EXAMPLES: ${LEARNING_MAX_EXAMPLES:-} + LOG_LEVEL: ${LOG_LEVEL:-} + MAJOR_INCIDENTS_ENABLED: ${MAJOR_INCIDENTS_ENABLED:-} + NEUROFORGE_API_KEY: ${NEUROFORGE_INTEGRATION_TOKEN:?Set the NeuroForge integration + token} + NEUROFORGE_FAIL_OPEN: ${NEUROFORGE_FAIL_OPEN:-true} + NEUROFORGE_NAMESPACE: ${NEUROFORGE_NAMESPACE:-glpi-agent} + NEUROFORGE_SEARCH_K: ${NEUROFORGE_SEARCH_K:-128} + NEUROFORGE_TIMEOUT: ${NEUROFORGE_TIMEOUT:-} + NEUROFORGE_URL: http://neuroforge:8080 + OLLAMA_EMBEDDING_MODEL: ${OLLAMA_EMBEDDING_MODEL:-} + OLLAMA_FAILOVER_ATTEMPTS: ${OLLAMA_FAILOVER_ATTEMPTS:-} + OLLAMA_FAILOVER_ENABLED: ${OLLAMA_FAILOVER_ENABLED:-} + OLLAMA_JSON_RETRIES: ${OLLAMA_JSON_RETRIES:-} + OLLAMA_KEEP_ALIVE: ${OLLAMA_KEEP_ALIVE:-} + OLLAMA_MAX_CONCURRENT: ${OLLAMA_MAX_CONCURRENT:-} + OLLAMA_MODEL: ${OLLAMA_MODEL:-} + OLLAMA_NODE_FAILURE_COOLDOWN: ${OLLAMA_NODE_FAILURE_COOLDOWN:-} + OLLAMA_NODE_HEALTH_INTERVAL: ${OLLAMA_NODE_HEALTH_INTERVAL:-} + OLLAMA_NODE_MAX_INFLIGHT: ${OLLAMA_NODE_MAX_INFLIGHT:-} + OLLAMA_NODE_NAMES: ${OLLAMA_NODE_NAMES:-} + OLLAMA_NODE_REQUEST_TIMEOUT: ${OLLAMA_NODE_REQUEST_TIMEOUT:-} + OLLAMA_NODE_WEIGHTS: ${OLLAMA_NODE_WEIGHTS:-} + OLLAMA_NUM_PREDICT: ${OLLAMA_NUM_PREDICT:-} + OLLAMA_REQUIRE_EMBEDDING_MODEL: ${OLLAMA_REQUIRE_EMBEDDING_MODEL:-} + OLLAMA_REQUIRE_SAME_MODEL_DIGEST: ${OLLAMA_REQUIRE_SAME_MODEL_DIGEST:-} + OLLAMA_ROUTING_MODE: ${OLLAMA_ROUTING_MODE:-} + OLLAMA_THINK: ${OLLAMA_THINK:-} + OLLAMA_TIMEOUT: ${OLLAMA_TIMEOUT:-} + OLLAMA_URL: ${OLLAMA_BASE_URL:?Set Ollama URL reachable from master} + OLLAMA_URLS: ${OLLAMA_URLS:?Set one or more Ollama URLs reachable from master} + OUTCOME_LEARNING_ENABLED: ${OUTCOME_LEARNING_ENABLED:-true} + OUTCOME_LEARNING_FAIL_OPEN: ${OUTCOME_LEARNING_FAIL_OPEN:-false} + OUTCOME_LEARNING_MAX_OUTCOMES: ${OUTCOME_LEARNING_MAX_OUTCOMES:-2000} + OUTCOME_RETRIEVAL_ENABLED: ${OUTCOME_RETRIEVAL_ENABLED:-true} + OUTCOME_RETRIEVAL_FAIL_OPEN: ${OUTCOME_RETRIEVAL_FAIL_OPEN:-true} + OUTCOME_RETRIEVAL_MIN_SIMILARITY: ${OUTCOME_RETRIEVAL_MIN_SIMILARITY:-0.58} + OUTCOME_RETRIEVAL_SEARCH_K: ${OUTCOME_RETRIEVAL_SEARCH_K:-6} + PRIORITY_ALLOWED_REASON_CODES: ${PRIORITY_ALLOWED_REASON_CODES:-} + PRIORITY_ANALYSIS_TIMEOUT: ${PRIORITY_ANALYSIS_TIMEOUT:-} + PRIORITY_CONFIDENCE: ${PRIORITY_CONFIDENCE:-} + PRIORITY_ENABLED: ${PRIORITY_ENABLED:-} + PRIORITY_MAX_INCREASE: ${PRIORITY_MAX_INCREASE:-} + QUEUE_SIZE: ${QUEUE_SIZE:-} + RAG_ENABLED: ${RAG_ENABLED:-} + REPLY_CONFIDENCE: ${REPLY_CONFIDENCE:-} + UPTIME_KUMA_API_KEY: ${UPTIME_KUMA_API_KEY:-} + UPTIME_KUMA_ENABLED: ${UPTIME_KUMA_ENABLED:-} + UPTIME_KUMA_INCLUDE_MAINTENANCE: ${UPTIME_KUMA_INCLUDE_MAINTENANCE:-} + UPTIME_KUMA_MAX_ISSUES: ${UPTIME_KUMA_MAX_ISSUES:-} + UPTIME_KUMA_MODE: ${UPTIME_KUMA_MODE:-} + UPTIME_KUMA_STATUS_PAGES: ${UPTIME_KUMA_STATUS_PAGES:-} + UPTIME_KUMA_TIMEOUT: ${UPTIME_KUMA_TIMEOUT:-} + UPTIME_KUMA_URL: ${UPTIME_KUMA_URL:-} + USER_DEVICE_CONTEXT_ENABLED: ${USER_DEVICE_CONTEXT_ENABLED:-} + WEBHOOK_SECRET: ${WEBHOOK_SECRET:-} + WEB_ALLOW_ANONYMOUS: ${WEB_ALLOW_ANONYMOUS:-} + WEB_PASSWORD: ${WEB_PASSWORD:-} + WEB_USERNAME: ${WEB_USERNAME:-} + WORKERS: ${WORKERS:-} + ports: + - 127.0.0.1:${AGENT_HOST_PORT:-8080}:8080 + volumes: + - agent-data:/app/data + - ${KB_DATA_PATH:-./knowledge}:/app/knowledge:ro + depends_on: + agent-data-init: + condition: service_completed_successfully + read_only: true + tmpfs: + - /tmp:size=64m,mode=1777 + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + healthcheck: + test: + - CMD + - /app/glpi-ai-agent + - healthcheck + interval: 15s + timeout: 3s + retries: 8 + start_period: 10s + stop_grace_period: 20s + knowledge: + image: git.send.nrw/sendnrw/glpi-neuroforge-mega-knowledge:${IMAGE_TAG:?Set IMAGE_TAG + to an immutable release tag, for example 1.6.0} + restart: unless-stopped + environment: + APP_MODE: ${KB_APP_MODE:-editor} + DATA_DIR: /data/knowledge + BACKUP_DIR: /data/backups + STAGING_DIR: /data/staging + LISTEN_ADDR: :8080 + APP_TITLE: ${APP_TITLE:-} + APP_SUBTITLE: ${APP_SUBTITLE:-} + AUTO_RELOAD_INTERVAL: ${AUTO_RELOAD_INTERVAL:-} + BASIC_AUTH_USER: ${BASIC_AUTH_USER:?Set the Knowledge web user} + BASIC_AUTH_PASSWORD: ${BASIC_AUTH_PASSWORD:?Set the Knowledge web password} + AI_FALLBACK_ENABLED: ${AI_FALLBACK_ENABLED:-false} + OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:?Set Ollama URL reachable from master} + OLLAMA_MODEL: ${OLLAMA_MODEL:-gemma3} + OLLAMA_TIMEOUT: ${OLLAMA_TIMEOUT:-10m} + OLLAMA_MAX_CONCURRENT: ${OLLAMA_MAX_CONCURRENT:-2} + OLLAMA_STAGING_AUTO_REPLY: ${OLLAMA_STAGING_AUTO_REPLY:-false} + OLLAMA_STAGING_MIN_SCORE: ${OLLAMA_STAGING_MIN_SCORE:-0.72} + BRAIN_ACTIVITY_URL: http://neuroforge:8080/api/v1/integrations/events + BRAIN_ACTIVITY_API_KEY: ${NEUROFORGE_INTEGRATION_TOKEN:?Set the NeuroForge integration + token} + KB_INTEGRATION_TOKEN: ${KB_INTEGRATION_TOKEN:?Set the Knowledge integration + token} + ports: + - 127.0.0.1:${KNOWLEDGE_HOST_PORT:-8081}:8080 + volumes: + - ${KB_DATA_PATH:-./knowledge}:/data/knowledge:${KB_DATA_MOUNT_MODE:-rw} + - ${KB_STAGING_PATH:-./staging}:/data/staging:rw + - ${KB_BACKUP_PATH:-./backups}:/data/backups:rw + read_only: true + tmpfs: + - /tmp:size=32m,mode=1777 + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + stop_grace_period: 35s + control: + image: git.send.nrw/sendnrw/glpi-neuroforge-mega-control:${IMAGE_TAG:?Set IMAGE_TAG + to an immutable release tag, for example 1.6.0} + restart: unless-stopped + environment: + CONTROL_ADDR: :8070 + CONTROL_BASIC_AUTH_USER: ${CONTROL_BASIC_AUTH_USER:?Set the Control Center user} + CONTROL_BASIC_AUTH_PASSWORD: ${CONTROL_BASIC_AUTH_PASSWORD:?Set the Control + Center password} + AGENT_URL: http://agent:8080 + KNOWLEDGE_URL: http://knowledge:8080 + NEUROFORGE_URL: http://neuroforge:8080 + NEUROFORGE_API_KEY: ${NEUROFORGE_CONTROL_READ_TOKEN:?Set the NeuroForge control + read token} + CONTROL_READ_TOKEN: ${CONTROL_READ_TOKEN:?Set the Agent control read token} + CODEBASE_MEMORY_URL: ${CODEBASE_MEMORY_URL:-} + PUBLIC_CODEBASE_MEMORY_URL: ${PUBLIC_CODEBASE_MEMORY_URL:-} + KNOWLEDGE_VECTOR_BACKEND: ${KNOWLEDGE_VECTOR_BACKEND:-dual} + NEUROFORGE_SEARCH_K: ${NEUROFORGE_SEARCH_K:-128} + NEUROFORGE_FAIL_OPEN: ${NEUROFORGE_FAIL_OPEN:-true} + NEUROFORGE_CONTROLLED_LEARNING: ${NEUROFORGE_CONTROLLED_LEARNING:-true} + NEUROFORGE_GOAL_LEARNING_ENABLED: ${NEUROFORGE_GOAL_LEARNING_ENABLED:-false} + OUTCOME_LEARNING_ENABLED: ${OUTCOME_LEARNING_ENABLED:-true} + OUTCOME_RETRIEVAL_ENABLED: ${OUTCOME_RETRIEVAL_ENABLED:-true} + OUTCOME_RETRIEVAL_SEARCH_K: ${OUTCOME_RETRIEVAL_SEARCH_K:-6} + OUTCOME_RETRIEVAL_MIN_SIMILARITY: ${OUTCOME_RETRIEVAL_MIN_SIMILARITY:-0.58} + OUTCOME_RETRIEVAL_FAIL_OPEN: ${OUTCOME_RETRIEVAL_FAIL_OPEN:-true} + NEUROFORGE_RESEARCH_ENABLED: ${NEUROFORGE_RESEARCH_ENABLED:-false} + NEUROFORGE_SEARXNG_ENABLED: ${NEUROFORGE_SEARXNG_ENABLED:-false} + NEUROFORGE_KB_STAGING_ENABLED: ${NEUROFORGE_KB_STAGING_ENABLED:-true} + NEUROFORGE_AUTONOMY_ENABLED: ${NEUROFORGE_AUTONOMY_ENABLED:-false} + PUBLIC_AGENT_URL: http://localhost:${AGENT_HOST_PORT:-8080} + PUBLIC_KNOWLEDGE_URL: http://localhost:${KNOWLEDGE_HOST_PORT:-8081} + PUBLIC_NEUROFORGE_URL: http://localhost:${NEUROFORGE_HOST_PORT:-8090}/admin + ports: + - 127.0.0.1:${CONTROL_HOST_PORT:-8070}:8070 + read_only: true + tmpfs: + - /tmp:size=16m,mode=1777 + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + extra_hosts: + - host.docker.internal:host-gateway + stop_grace_period: 25s + prometheus-secrets-init: + profiles: + - monitoring + image: alpine:3.22.1 + restart: 'no' + environment: + NEUROFORGE_METRICS_TOKEN: ${NEUROFORGE_METRICS_TOKEN:?Set NeuroForge metrics + token} + command: + - /bin/sh + - -ec + - printf "%s" "$${NEUROFORGE_METRICS_TOKEN}" > /secrets/neuroforge_metrics.token; + chown 65534:65534 /secrets/neuroforge_metrics.token; chmod 0400 /secrets/neuroforge_metrics.token + volumes: + - prometheus-secrets:/secrets + read_only: true + tmpfs: + - /tmp:size=8m,mode=1777 + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + prometheus-config-init: + profiles: + - monitoring + image: alpine:3.22.1 + restart: 'no' + environment: + CPU_TARGET: ${CPU_SUBAGENT_NODE_EXPORTER_TARGET:-cpu-subagent.example.invalid:9100} + GPU_TARGET: ${GPU_SUBAGENT_NODE_EXPORTER_TARGET:-gpu-subagent.example.invalid:9100} + GPU_DCGM_TARGET: ${GPU_SUBAGENT_DCGM_EXPORTER_TARGET:-gpu-subagent.example.invalid:9400} + command: + - /bin/sh + - -ec + - sed -e "s|__CPU_TARGET__|$${CPU_TARGET}|g" -e "s|__GPU_TARGET__|$${GPU_TARGET}|g" + -e "s|__GPU_DCGM_TARGET__|$${GPU_DCGM_TARGET}|g" /template/prometheus.yml.template + > /rendered/prometheus.yml + volumes: + - ./monitoring/prometheus:/template:ro + - prometheus-config:/rendered + read_only: true + tmpfs: + - /tmp:size=8m,mode=1777 + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + node-exporter: + profiles: + - monitoring + image: ${NODE_EXPORTER_IMAGE:-prom/node-exporter:v1.12.1} + restart: unless-stopped + command: + - --path.procfs=/host/proc + - --path.sysfs=/host/sys + - --path.rootfs=/rootfs + - --collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/) + volumes: + - /proc:/host/proc:ro + - /sys:/host/sys:ro + - /:/rootfs:ro,rslave + read_only: true + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + prometheus: + profiles: + - monitoring + image: ${PROMETHEUS_IMAGE:-prom/prometheus:v3.14.0} + restart: unless-stopped + command: + - --config.file=/etc/prometheus/prometheus.yml + - --storage.tsdb.path=/prometheus + - --storage.tsdb.retention.time=${PROMETHEUS_RETENTION:-30d} + - --web.enable-lifecycle + ports: + - 127.0.0.1:${PROMETHEUS_HOST_PORT:-9090}:9090 + volumes: + - prometheus-config:/etc/prometheus:ro + - ./monitoring/prometheus/alerts.yml:/etc/prometheus-alerts/alerts.yml:ro + - prometheus-data:/prometheus + - prometheus-secrets:/etc/prometheus-secrets:ro + depends_on: + neuroforge: + condition: service_healthy + prometheus-secrets-init: + condition: service_completed_successfully + prometheus-config-init: + condition: service_completed_successfully + read_only: true + tmpfs: + - /tmp:size=32m,mode=1777 + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + grafana: + profiles: + - monitoring + image: ${GRAFANA_IMAGE:-grafana/grafana:13.2.0} + restart: unless-stopped + environment: + GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:-admin} + GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:?Set Grafana admin password} + GF_USERS_ALLOW_SIGN_UP: 'false' + GF_AUTH_ANONYMOUS_ENABLED: 'false' + GF_SERVER_ROOT_URL: ${GRAFANA_ROOT_URL:-http://localhost:3000} + ports: + - 127.0.0.1:${GRAFANA_HOST_PORT:-3000}:3000 + volumes: + - grafana-data:/var/lib/grafana + - ./monitoring/grafana/provisioning:/etc/grafana/provisioning:ro + - ./monitoring/grafana/dashboards:/var/lib/grafana/dashboards:ro + depends_on: + prometheus: + condition: service_started + security_opt: + - no-new-privileges:true + cap_drop: + - ALL +volumes: + neuroforge-data: null + agent-data: null + searxng-cache: null + prometheus-data: null + prometheus-secrets: null + prometheus-config: null + grafana-data: null diff --git a/deployments/master/knowledge/.gitkeep b/deployments/master/knowledge/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/deployments/master/monitoring/README.md b/deployments/master/monitoring/README.md new file mode 100644 index 0000000..46778d2 --- /dev/null +++ b/deployments/master/monitoring/README.md @@ -0,0 +1,29 @@ +# Prometheus / Grafana Monitoring + +The `monitoring` profile starts: +- Prometheus 3.14.0 +- Grafana 13.2.0 +- node-exporter 1.12.1 on the Master + +Prometheus also scrapes the CPU/GPU subagent node-exporters and NVIDIA DCGM exporter using targets from the Master `.env`. NeuroForge `/metrics` is authenticated with the metrics bearer token; an init container writes that token into a Docker volume instead of embedding it in the Prometheus config. + +Provisioned dashboard panels include: +- Master up, memories, synapses +- online CPU/GPU workers +- durable jobs by status +- inflight/capacity by worker +- linked/isolated/multi-linked memories +- graph degree/components/largest component +- HNSW and Disk-PQ population +- HTTP request rate and P95 latency +- Master heap +- Master/CPU/GPU host CPU and memory +- NVIDIA GPU utilization, framebuffer and power (when DCGM exporter is enabled) + +The bundled Prometheus alert rules cover Master down, missing CPU/GPU workers, failed jobs, stuck `apply_wait`, large queue, and a persistently highly-isolated graph. + +Validation: +```bash +./validate.sh +``` +This renders the Prometheus template with the targets from the Master `.env` and runs the pinned Prometheus image's own `promtool check config`. diff --git a/deployments/master/monitoring/grafana/dashboards/neuroforge-master-subagents.json b/deployments/master/monitoring/grafana/dashboards/neuroforge-master-subagents.json new file mode 100644 index 0000000..84c4a08 --- /dev/null +++ b/deployments/master/monitoring/grafana/dashboards/neuroforge-master-subagents.json @@ -0,0 +1,1448 @@ +{ + "id": null, + "uid": "neuroforge-master-subagents", + "title": "NeuroForge Master / Subagents / Knowledge Graph", + "tags": [ + "neuroforge", + "orchestrator", + "knowledge-graph" + ], + "timezone": "browser", + "schemaVersion": 41, + "version": 1, + "refresh": "15s", + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "templating": { + "list": [] + }, + "annotations": { + "list": [] + }, + "panels": [ + { + "id": 1, + "type": "stat", + "title": "Master Up", + "description": "", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "x": 0, + "y": 0, + "w": 4, + "h": 4 + }, + "targets": [ + { + "refId": "A", + "expr": "neuroforge_up", + "instant": true, + "range": false + } + ], + "fieldConfig": { + "defaults": { + "unit": "short", + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "options": { + "reduceOptions": { + "values": false, + "calcs": [ + "lastNotNull" + ], + "fields": "" + }, + "orientation": "auto", + "textMode": "auto", + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto" + } + }, + { + "id": 2, + "type": "stat", + "title": "Memories", + "description": "", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "x": 4, + "y": 0, + "w": 4, + "h": 4 + }, + "targets": [ + { + "refId": "A", + "expr": "neuroforge_memories", + "instant": true, + "range": false + } + ], + "fieldConfig": { + "defaults": { + "unit": "short", + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "options": { + "reduceOptions": { + "values": false, + "calcs": [ + "lastNotNull" + ], + "fields": "" + }, + "orientation": "auto", + "textMode": "auto", + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto" + } + }, + { + "id": 3, + "type": "stat", + "title": "Synapses", + "description": "", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "x": 8, + "y": 0, + "w": 4, + "h": 4 + }, + "targets": [ + { + "refId": "A", + "expr": "neuroforge_synapses", + "instant": true, + "range": false + } + ], + "fieldConfig": { + "defaults": { + "unit": "short", + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "options": { + "reduceOptions": { + "values": false, + "calcs": [ + "lastNotNull" + ], + "fields": "" + }, + "orientation": "auto", + "textMode": "auto", + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto" + } + }, + { + "id": 4, + "type": "stat", + "title": "Online CPU Workers", + "description": "", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "x": 12, + "y": 0, + "w": 4, + "h": 4 + }, + "targets": [ + { + "refId": "A", + "expr": "sum(neuroforge_workers{resource=\"cpu\",status=\"online\"})", + "instant": true, + "range": false + } + ], + "fieldConfig": { + "defaults": { + "unit": "short", + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "options": { + "reduceOptions": { + "values": false, + "calcs": [ + "lastNotNull" + ], + "fields": "" + }, + "orientation": "auto", + "textMode": "auto", + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto" + } + }, + { + "id": 5, + "type": "stat", + "title": "Online GPU Workers", + "description": "", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "x": 16, + "y": 0, + "w": 4, + "h": 4 + }, + "targets": [ + { + "refId": "A", + "expr": "sum(neuroforge_workers{resource=\"gpu\",status=\"online\"})", + "instant": true, + "range": false + } + ], + "fieldConfig": { + "defaults": { + "unit": "short", + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "options": { + "reduceOptions": { + "values": false, + "calcs": [ + "lastNotNull" + ], + "fields": "" + }, + "orientation": "auto", + "textMode": "auto", + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto" + } + }, + { + "id": 6, + "type": "stat", + "title": "Queued Jobs", + "description": "", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "x": 20, + "y": 0, + "w": 4, + "h": 4 + }, + "targets": [ + { + "refId": "A", + "expr": "neuroforge_jobs{status=\"queued\"}", + "instant": true, + "range": false + } + ], + "fieldConfig": { + "defaults": { + "unit": "short", + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "options": { + "reduceOptions": { + "values": false, + "calcs": [ + "lastNotNull" + ], + "fields": "" + }, + "orientation": "auto", + "textMode": "auto", + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto" + } + }, + { + "id": 7, + "type": "stat", + "title": "Linked Memories", + "description": "", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "x": 0, + "y": 4, + "w": 4, + "h": 4 + }, + "targets": [ + { + "refId": "A", + "expr": "neuroforge_graph_memories{state=\"linked\"}", + "instant": true, + "range": false + } + ], + "fieldConfig": { + "defaults": { + "unit": "short", + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "options": { + "reduceOptions": { + "values": false, + "calcs": [ + "lastNotNull" + ], + "fields": "" + }, + "orientation": "auto", + "textMode": "auto", + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto" + } + }, + { + "id": 8, + "type": "stat", + "title": "Isolated Memories", + "description": "", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "x": 4, + "y": 4, + "w": 4, + "h": 4 + }, + "targets": [ + { + "refId": "A", + "expr": "neuroforge_graph_memories{state=\"isolated\"}", + "instant": true, + "range": false + } + ], + "fieldConfig": { + "defaults": { + "unit": "short", + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "options": { + "reduceOptions": { + "values": false, + "calcs": [ + "lastNotNull" + ], + "fields": "" + }, + "orientation": "auto", + "textMode": "auto", + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto" + } + }, + { + "id": 9, + "type": "stat", + "title": "Multi-linked", + "description": "", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "x": 8, + "y": 4, + "w": 4, + "h": 4 + }, + "targets": [ + { + "refId": "A", + "expr": "neuroforge_graph_memories{state=\"multi_linked\"}", + "instant": true, + "range": false + } + ], + "fieldConfig": { + "defaults": { + "unit": "short", + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "options": { + "reduceOptions": { + "values": false, + "calcs": [ + "lastNotNull" + ], + "fields": "" + }, + "orientation": "auto", + "textMode": "auto", + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto" + } + }, + { + "id": 10, + "type": "stat", + "title": "Average Degree", + "description": "", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "x": 12, + "y": 4, + "w": 4, + "h": 4 + }, + "targets": [ + { + "refId": "A", + "expr": "neuroforge_graph_average_degree", + "instant": true, + "range": false + } + ], + "fieldConfig": { + "defaults": { + "unit": "short", + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "options": { + "reduceOptions": { + "values": false, + "calcs": [ + "lastNotNull" + ], + "fields": "" + }, + "orientation": "auto", + "textMode": "auto", + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto" + } + }, + { + "id": 11, + "type": "stat", + "title": "Connected Components", + "description": "", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "x": 16, + "y": 4, + "w": 4, + "h": 4 + }, + "targets": [ + { + "refId": "A", + "expr": "neuroforge_graph_connected_components", + "instant": true, + "range": false + } + ], + "fieldConfig": { + "defaults": { + "unit": "short", + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "options": { + "reduceOptions": { + "values": false, + "calcs": [ + "lastNotNull" + ], + "fields": "" + }, + "orientation": "auto", + "textMode": "auto", + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto" + } + }, + { + "id": 12, + "type": "stat", + "title": "Largest Component", + "description": "", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "x": 20, + "y": 4, + "w": 4, + "h": 4 + }, + "targets": [ + { + "refId": "A", + "expr": "neuroforge_graph_largest_component", + "instant": true, + "range": false + } + ], + "fieldConfig": { + "defaults": { + "unit": "short", + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "options": { + "reduceOptions": { + "values": false, + "calcs": [ + "lastNotNull" + ], + "fields": "" + }, + "orientation": "auto", + "textMode": "auto", + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto" + } + }, + { + "id": 13, + "type": "timeseries", + "title": "Orchestrator Jobs by Status", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "x": 0, + "y": 8, + "w": 12, + "h": 8 + }, + "targets": [ + { + "refId": "A", + "expr": "neuroforge_jobs", + "legendFormat": "{{status}}", + "range": true + } + ], + "fieldConfig": { + "defaults": { + "unit": "short", + "custom": { + "drawStyle": "line", + "lineInterpolation": "linear", + "lineWidth": 1, + "fillOpacity": 8, + "showPoints": "auto", + "spanNulls": false + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "options": { + "legend": { + "displayMode": "list", + "placement": "bottom", + "calcs": [ + "lastNotNull" + ] + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + } + }, + { + "id": 14, + "type": "timeseries", + "title": "Worker Inflight", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "x": 12, + "y": 8, + "w": 12, + "h": 8 + }, + "targets": [ + { + "refId": "A", + "expr": "neuroforge_worker_inflight", + "legendFormat": "{{worker}} / {{resource}}", + "range": true + } + ], + "fieldConfig": { + "defaults": { + "unit": "short", + "custom": { + "drawStyle": "line", + "lineInterpolation": "linear", + "lineWidth": 1, + "fillOpacity": 8, + "showPoints": "auto", + "spanNulls": false + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "options": { + "legend": { + "displayMode": "list", + "placement": "bottom", + "calcs": [ + "lastNotNull" + ] + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + } + }, + { + "id": 15, + "type": "timeseries", + "title": "Worker Capacity", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "x": 0, + "y": 16, + "w": 12, + "h": 8 + }, + "targets": [ + { + "refId": "A", + "expr": "neuroforge_worker_capacity", + "legendFormat": "{{worker}} / {{resource}}", + "range": true + } + ], + "fieldConfig": { + "defaults": { + "unit": "short", + "custom": { + "drawStyle": "line", + "lineInterpolation": "linear", + "lineWidth": 1, + "fillOpacity": 8, + "showPoints": "auto", + "spanNulls": false + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "options": { + "legend": { + "displayMode": "list", + "placement": "bottom", + "calcs": [ + "lastNotNull" + ] + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + } + }, + { + "id": 16, + "type": "timeseries", + "title": "Graph Linkage", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "x": 12, + "y": 16, + "w": 12, + "h": 8 + }, + "targets": [ + { + "refId": "A", + "expr": "neuroforge_graph_memories", + "legendFormat": "{{state}}", + "range": true + } + ], + "fieldConfig": { + "defaults": { + "unit": "short", + "custom": { + "drawStyle": "line", + "lineInterpolation": "linear", + "lineWidth": 1, + "fillOpacity": 8, + "showPoints": "auto", + "spanNulls": false + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "options": { + "legend": { + "displayMode": "list", + "placement": "bottom", + "calcs": [ + "lastNotNull" + ] + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + } + }, + { + "id": 17, + "type": "timeseries", + "title": "Vector Index Population", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "x": 0, + "y": 24, + "w": 12, + "h": 8 + }, + "targets": [ + { + "refId": "A", + "expr": "neuroforge_hnsw_nodes", + "legendFormat": "HNSW", + "range": true + }, + { + "refId": "B", + "expr": "neuroforge_disk_pq_items", + "legendFormat": "Disk PQ", + "range": true + } + ], + "fieldConfig": { + "defaults": { + "unit": "short", + "custom": { + "drawStyle": "line", + "lineInterpolation": "linear", + "lineWidth": 1, + "fillOpacity": 8, + "showPoints": "auto", + "spanNulls": false + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "options": { + "legend": { + "displayMode": "list", + "placement": "bottom", + "calcs": [ + "lastNotNull" + ] + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + } + }, + { + "id": 18, + "type": "timeseries", + "title": "HTTP Request Rate", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "x": 12, + "y": 24, + "w": 12, + "h": 8 + }, + "targets": [ + { + "refId": "A", + "expr": "sum by (code) (rate(neuroforge_http_requests_total[5m]))", + "legendFormat": "HTTP {{code}}", + "range": true + } + ], + "fieldConfig": { + "defaults": { + "unit": "reqps", + "custom": { + "drawStyle": "line", + "lineInterpolation": "linear", + "lineWidth": 1, + "fillOpacity": 8, + "showPoints": "auto", + "spanNulls": false + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "options": { + "legend": { + "displayMode": "list", + "placement": "bottom", + "calcs": [ + "lastNotNull" + ] + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + } + }, + { + "id": 19, + "type": "timeseries", + "title": "HTTP P95 Latency", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "x": 0, + "y": 32, + "w": 12, + "h": 8 + }, + "targets": [ + { + "refId": "A", + "expr": "histogram_quantile(0.95, sum by (le) (rate(neuroforge_http_request_duration_seconds_bucket[5m])))", + "legendFormat": "p95", + "range": true + } + ], + "fieldConfig": { + "defaults": { + "unit": "s", + "custom": { + "drawStyle": "line", + "lineInterpolation": "linear", + "lineWidth": 1, + "fillOpacity": 8, + "showPoints": "auto", + "spanNulls": false + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "options": { + "legend": { + "displayMode": "list", + "placement": "bottom", + "calcs": [ + "lastNotNull" + ] + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + } + }, + { + "id": 20, + "type": "timeseries", + "title": "Master Heap", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "x": 12, + "y": 32, + "w": 12, + "h": 8 + }, + "targets": [ + { + "refId": "A", + "expr": "neuroforge_runtime_heap_alloc_bytes", + "legendFormat": "heap alloc", + "range": true + } + ], + "fieldConfig": { + "defaults": { + "unit": "bytes", + "custom": { + "drawStyle": "line", + "lineInterpolation": "linear", + "lineWidth": 1, + "fillOpacity": 8, + "showPoints": "auto", + "spanNulls": false + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "options": { + "legend": { + "displayMode": "list", + "placement": "bottom", + "calcs": [ + "lastNotNull" + ] + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + } + }, + { + "id": 21, + "type": "timeseries", + "title": "Master / CPU / GPU Host CPU", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "x": 0, + "y": 40, + "w": 12, + "h": 8 + }, + "targets": [ + { + "refId": "A", + "expr": "100 - (avg by (job) (rate(node_cpu_seconds_total{mode=\"idle\"}[5m])) * 100)", + "legendFormat": "{{job}}", + "range": true + } + ], + "fieldConfig": { + "defaults": { + "unit": "percent", + "custom": { + "drawStyle": "line", + "lineInterpolation": "linear", + "lineWidth": 1, + "fillOpacity": 8, + "showPoints": "auto", + "spanNulls": false + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "options": { + "legend": { + "displayMode": "list", + "placement": "bottom", + "calcs": [ + "lastNotNull" + ] + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + } + }, + { + "id": 22, + "type": "timeseries", + "title": "Host Memory Usage", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "x": 12, + "y": 40, + "w": 12, + "h": 8 + }, + "targets": [ + { + "refId": "A", + "expr": "100 * (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes))", + "legendFormat": "{{job}}", + "range": true + } + ], + "fieldConfig": { + "defaults": { + "unit": "percent", + "custom": { + "drawStyle": "line", + "lineInterpolation": "linear", + "lineWidth": 1, + "fillOpacity": 8, + "showPoints": "auto", + "spanNulls": false + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "options": { + "legend": { + "displayMode": "list", + "placement": "bottom", + "calcs": [ + "lastNotNull" + ] + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + } + }, + { + "id": 23, + "type": "timeseries", + "title": "GPU Utilization", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "x": 0, + "y": 48, + "w": 8, + "h": 8 + }, + "targets": [ + { + "refId": "A", + "expr": "avg by (gpu) (DCGM_FI_DEV_GPU_UTIL)", + "legendFormat": "GPU {{gpu}}", + "range": true + } + ], + "fieldConfig": { + "defaults": { + "unit": "percent", + "custom": { + "drawStyle": "line", + "lineInterpolation": "linear", + "lineWidth": 1, + "fillOpacity": 8, + "showPoints": "auto", + "spanNulls": false + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "options": { + "legend": { + "displayMode": "list", + "placement": "bottom", + "calcs": [ + "lastNotNull" + ] + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + } + }, + { + "id": 24, + "type": "timeseries", + "title": "GPU Framebuffer Used", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "x": 8, + "y": 48, + "w": 8, + "h": 8 + }, + "targets": [ + { + "refId": "A", + "expr": "DCGM_FI_DEV_FB_USED", + "legendFormat": "GPU {{gpu}}", + "range": true + } + ], + "fieldConfig": { + "defaults": { + "unit": "MiB", + "custom": { + "drawStyle": "line", + "lineInterpolation": "linear", + "lineWidth": 1, + "fillOpacity": 8, + "showPoints": "auto", + "spanNulls": false + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "options": { + "legend": { + "displayMode": "list", + "placement": "bottom", + "calcs": [ + "lastNotNull" + ] + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + } + }, + { + "id": 25, + "type": "timeseries", + "title": "GPU Power", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "x": 16, + "y": 48, + "w": 8, + "h": 8 + }, + "targets": [ + { + "refId": "A", + "expr": "DCGM_FI_DEV_POWER_USAGE", + "legendFormat": "GPU {{gpu}}", + "range": true + } + ], + "fieldConfig": { + "defaults": { + "unit": "watt", + "custom": { + "drawStyle": "line", + "lineInterpolation": "linear", + "lineWidth": 1, + "fillOpacity": 8, + "showPoints": "auto", + "spanNulls": false + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "options": { + "legend": { + "displayMode": "list", + "placement": "bottom", + "calcs": [ + "lastNotNull" + ] + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + } + } + ] +} diff --git a/deployments/master/monitoring/grafana/provisioning/dashboards/dashboards.yml b/deployments/master/monitoring/grafana/provisioning/dashboards/dashboards.yml new file mode 100644 index 0000000..a1722a2 --- /dev/null +++ b/deployments/master/monitoring/grafana/provisioning/dashboards/dashboards.yml @@ -0,0 +1,11 @@ +apiVersion: 1 +providers: + - name: NeuroForge + orgId: 1 + folder: NeuroForge + type: file + disableDeletion: true + updateIntervalSeconds: 30 + allowUiUpdates: false + options: + path: /var/lib/grafana/dashboards diff --git a/deployments/master/monitoring/grafana/provisioning/datasources/prometheus.yml b/deployments/master/monitoring/grafana/provisioning/datasources/prometheus.yml new file mode 100644 index 0000000..433ef5c --- /dev/null +++ b/deployments/master/monitoring/grafana/provisioning/datasources/prometheus.yml @@ -0,0 +1,9 @@ +apiVersion: 1 +datasources: + - name: Prometheus + uid: prometheus + type: prometheus + access: proxy + url: http://prometheus:9090 + isDefault: true + editable: false diff --git a/deployments/master/monitoring/prometheus/alerts.yml b/deployments/master/monitoring/prometheus/alerts.yml new file mode 100644 index 0000000..4b9cf81 --- /dev/null +++ b/deployments/master/monitoring/prometheus/alerts.yml @@ -0,0 +1,58 @@ +groups: + - name: neuroforge-production + rules: + - alert: NeuroForgeMasterDown + expr: up{job="neuroforge"} == 0 + for: 1m + labels: {severity: critical} + annotations: + summary: "NeuroForge Master is down" + description: "Prometheus cannot scrape the NeuroForge Master for more than 1 minute." + + - alert: NeuroForgeCPUWorkerMissing + expr: sum(neuroforge_workers{resource="cpu",status="online"}) < 1 + for: 2m + labels: {severity: critical} + annotations: + summary: "No online CPU subagent" + description: "Graph relink/backfill jobs cannot converge without a CPU worker." + + - alert: NeuroForgeGPUWorkerMissing + expr: sum(neuroforge_workers{resource="gpu",status="online"}) < 1 + for: 2m + labels: {severity: warning} + annotations: + summary: "No online GPU subagent" + description: "Distributed chat/embed jobs have no online GPU worker." + + - alert: NeuroForgeFailedJobs + expr: neuroforge_jobs{status="failed"} > 0 + for: 5m + labels: {severity: warning} + annotations: + summary: "NeuroForge has failed durable jobs" + description: "At least one orchestrator job has remained failed for 5 minutes." + + - alert: NeuroForgeApplyWaitStuck + expr: neuroforge_jobs{status="apply_wait"} > 0 + for: 10m + labels: {severity: critical} + annotations: + summary: "Master apply phase appears stuck" + description: "A durable job has remained in apply_wait for more than 10 minutes." + + - alert: NeuroForgeQueueHigh + expr: neuroforge_jobs{status="queued"} > 1000 + for: 10m + labels: {severity: warning} + annotations: + summary: "NeuroForge queue is high" + description: "More than 1000 jobs have remained queued for 10 minutes." + + - alert: NeuroForgeGraphHighlyIsolated + expr: (neuroforge_graph_memories{state="isolated"} / clamp_min(neuroforge_memories, 1)) > 0.80 and on() neuroforge_memories > 1000 + for: 30m + labels: {severity: warning} + annotations: + summary: "Knowledge graph remains highly isolated" + description: "More than 80% of memories are still isolated after 30 minutes." diff --git a/deployments/master/monitoring/prometheus/prometheus.yml.template b/deployments/master/monitoring/prometheus/prometheus.yml.template new file mode 100644 index 0000000..5e5899a --- /dev/null +++ b/deployments/master/monitoring/prometheus/prometheus.yml.template @@ -0,0 +1,31 @@ +global: + scrape_interval: 15s + evaluation_interval: 15s + +rule_files: + - /etc/prometheus-alerts/alerts.yml + +scrape_configs: + - job_name: neuroforge + metrics_path: /metrics + authorization: + type: Bearer + credentials_file: /etc/prometheus-secrets/neuroforge_metrics.token + static_configs: + - targets: ["neuroforge:8080"] + + - job_name: master_node + static_configs: + - targets: ["node-exporter:9100"] + + - job_name: cpu_subagent_node + static_configs: + - targets: ["__CPU_TARGET__"] + + - job_name: gpu_subagent_node + static_configs: + - targets: ["__GPU_TARGET__"] + + - job_name: gpu_dcgm + static_configs: + - targets: ["__GPU_DCGM_TARGET__"] diff --git a/deployments/master/monitoring/validate.sh b/deployments/master/monitoring/validate.sh new file mode 100755 index 0000000..b79baef --- /dev/null +++ b/deployments/master/monitoring/validate.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env sh +set -eu +cd "$(dirname "$0")" +command -v docker >/dev/null 2>&1 || { echo "FEHLT: docker"; exit 1; } +ENV_FILE=../.env +getenv() { grep -E "^$1=" "$ENV_FILE" | head -1 | cut -d= -f2-; } +CPU_TARGET=$(getenv CPU_SUBAGENT_NODE_EXPORTER_TARGET) +GPU_TARGET=$(getenv GPU_SUBAGENT_NODE_EXPORTER_TARGET) +GPU_DCGM_TARGET=$(getenv GPU_SUBAGENT_DCGM_EXPORTER_TARGET) +METRICS_TOKEN=$(getenv NEUROFORGE_METRICS_TOKEN) +PROM_IMAGE=$(getenv PROMETHEUS_IMAGE) +TMP=$(mktemp -d) +trap 'rm -rf "$TMP"' EXIT +sed -e "s|__CPU_TARGET__|${CPU_TARGET}|g" \ + -e "s|__GPU_TARGET__|${GPU_TARGET}|g" \ + -e "s|__GPU_DCGM_TARGET__|${GPU_DCGM_TARGET}|g" \ + prometheus/prometheus.yml.template > "$TMP/prometheus.yml" +printf '%s' "$METRICS_TOKEN" > "$TMP/neuroforge_metrics.token" +docker run --rm --entrypoint=/bin/promtool \ + -v "$TMP/prometheus.yml:/etc/prometheus/prometheus.yml:ro" \ + -v "$PWD/prometheus/alerts.yml:/etc/prometheus-alerts/alerts.yml:ro" \ + -v "$TMP/neuroforge_metrics.token:/etc/prometheus-secrets/neuroforge_metrics.token:ro" \ + "$PROM_IMAGE" check config /etc/prometheus/prometheus.yml +echo "Prometheus promtool validation: OK" diff --git a/deployments/master/preflight.sh b/deployments/master/preflight.sh new file mode 100755 index 0000000..e333617 --- /dev/null +++ b/deployments/master/preflight.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env sh +set -eu +bad=0 +check() { key="$1"; val=$(grep -E "^${key}=" .env | head -1 | cut -d= -f2- || true); if [ -z "$val" ] || echo "$val" | grep -Eq 'CHANGE_ME|example\.invalid|192\.0\.2\.'; then echo "SETZEN: $key"; bad=1; fi; } +for k in GLPI_URL GLPI_CLIENT_ID GLPI_CLIENT_SECRET GLPI_USERNAME GLPI_PASSWORD OLLAMA_BASE_URL OLLAMA_URLS; do check "$k"; done +if [ "$bad" -ne 0 ]; then echo "Preflight fehlgeschlagen: Platzhalter ersetzen."; exit 1; fi +command -v docker >/dev/null 2>&1 || { echo "FEHLT: docker"; exit 1; } +docker compose version >/dev/null +docker compose --profile research --profile monitoring config >/dev/null +[ -f monitoring/prometheus/prometheus.yml.template ] +[ -f monitoring/prometheus/alerts.yml ] +[ -f monitoring/grafana/dashboards/neuroforge-master-subagents.json ] +mkdir -p knowledge staging backups +./monitoring/validate.sh +echo "Master preflight: OK" diff --git a/deployments/master/staging/.gitkeep b/deployments/master/staging/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 88dcad2..63175c2 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -6,7 +6,12 @@ services: context: ./platform/neuroforge target: server pull_policy: never - neuroforge-worker: + neuroforge-worker-cpu: + build: + context: ./platform/neuroforge + target: worker + pull_policy: never + neuroforge-worker-gpu: build: context: ./platform/neuroforge target: worker diff --git a/docker-compose.subagent.yml b/docker-compose.subagent.yml new file mode 100644 index 0000000..7f8bd35 --- /dev/null +++ b/docker-compose.subagent.yml @@ -0,0 +1,57 @@ +# Remote NeuroForge subagents. Run this file on additional CPU/GPU hosts. +# Examples: +# docker compose -f docker-compose.subagent.yml --profile cpu up -d +# docker compose -f docker-compose.subagent.yml --profile gpu up -d +# The master URL must be reachable from this host/container. +name: neuroforge-subagents +services: + cpu-subagent: + profiles: [cpu] + image: git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge-worker:${IMAGE_TAG:?Set IMAGE_TAG, e.g. 1.6.0} + command: + - -server + - ${NEUROFORGE_MASTER_URL:?Set the reachable NeuroForge master URL} + - -id + - ${NEUROFORGE_CPU_WORKER_ID:-cpu-${HOSTNAME:-remote}} + restart: unless-stopped + environment: + NEUROFORGE_WORKER_TOKEN: ${NEUROFORGE_WORKER_TOKEN:?Set the worker token shared with the master} + NEUROFORGE_WORKER_RESOURCE_CLASS: cpu + NEUROFORGE_WORKER_CAPABILITIES: ${NEUROFORGE_CPU_WORKER_CAPABILITIES:-cpu,vector.relink} + NEUROFORGE_WORKER_MAX_CONCURRENCY: ${NEUROFORGE_CPU_WORKER_CONCURRENCY:-2} + NEUROFORGE_WORKER_HEARTBEAT_INTERVAL: ${NEUROFORGE_WORKER_HEARTBEAT_INTERVAL:-15s} + read_only: true + tmpfs: + - /tmp:size=32m,mode=1777 + security_opt: + - no-new-privileges:true + cap_drop: [ALL] + + gpu-subagent: + profiles: [gpu] + image: git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge-worker:${IMAGE_TAG:?Set IMAGE_TAG, e.g. 1.6.0} + command: + - -server + - ${NEUROFORGE_MASTER_URL:?Set the reachable NeuroForge master URL} + - -id + - ${NEUROFORGE_GPU_WORKER_ID:-gpu-${HOSTNAME:-remote}} + restart: unless-stopped + environment: + NEUROFORGE_WORKER_TOKEN: ${NEUROFORGE_WORKER_TOKEN:?Set the worker token shared with the master} + NEUROFORGE_WORKER_RESOURCE_CLASS: gpu + NEUROFORGE_WORKER_CAPABILITIES: ${NEUROFORGE_GPU_WORKER_CAPABILITIES:-gpu,model.chat,model.embed} + NEUROFORGE_WORKER_MAX_CONCURRENCY: ${NEUROFORGE_GPU_WORKER_CONCURRENCY:-1} + NEUROFORGE_WORKER_HEARTBEAT_INTERVAL: ${NEUROFORGE_WORKER_HEARTBEAT_INTERVAL:-15s} + NEUROFORGE_WORKER_OLLAMA_URL: ${NEUROFORGE_WORKER_OLLAMA_URL:?Set Ollama URL reachable from the GPU subagent} + NEUROFORGE_WORKER_OLLAMA_CHAT_MODEL: ${OLLAMA_MODEL:-gemma3} + NEUROFORGE_WORKER_OLLAMA_EMBEDDING_MODEL: ${OLLAMA_EMBEDDING_MODEL:-embeddinggemma} + NEUROFORGE_WORKER_OLLAMA_NUM_CTX: ${NEUROFORGE_OLLAMA_NUM_CTX:-8192} + NEUROFORGE_WORKER_OLLAMA_KEEP_ALIVE: ${OLLAMA_KEEP_ALIVE:-10m} + read_only: true + tmpfs: + - /tmp:size=32m,mode=1777 + security_opt: + - no-new-privileges:true + cap_drop: [ALL] + extra_hosts: + - host.docker.internal:host-gateway diff --git a/docker-compose.yml b/docker-compose.yml index 95a4d75..df01540 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -33,7 +33,7 @@ services: cap_drop: - ALL neuroforge: - image: git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.5.9} + image: git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.6.0} command: - -data - /app/data @@ -54,6 +54,32 @@ services: NEUROFORGE_OLLAMA_EMBEDDING_MODEL: ${OLLAMA_EMBEDDING_MODEL:-embeddinggemma} NEUROFORGE_OLLAMA_NUM_CTX: ${NEUROFORGE_OLLAMA_NUM_CTX:-8192} NEUROFORGE_OLLAMA_NUM_PREDICT: ${NEUROFORGE_OLLAMA_NUM_PREDICT:-0} + # Durable master/subagent scheduler and graph convergence. + NEUROFORGE_WORKER_LEASE_SECONDS: ${NEUROFORGE_WORKER_LEASE_SECONDS:-120} + NEUROFORGE_WORKER_HEARTBEAT_SECONDS: ${NEUROFORGE_WORKER_HEARTBEAT_SECONDS:-15} + NEUROFORGE_WORKER_STALE_AFTER_SECONDS: ${NEUROFORGE_WORKER_STALE_AFTER_SECONDS:-60} + NEUROFORGE_WORKER_DEFAULT_MAX_ATTEMPTS: ${NEUROFORGE_WORKER_DEFAULT_MAX_ATTEMPTS:-3} + NEUROFORGE_WORKER_RETRY_BACKOFF_SECONDS: ${NEUROFORGE_WORKER_RETRY_BACKOFF_SECONDS:-15} + NEUROFORGE_WORKER_MAX_QUEUED_JOBS: ${NEUROFORGE_WORKER_MAX_QUEUED_JOBS:-5000} + NEUROFORGE_WORKER_MASTER_APPLY_MAX_ATTEMPTS: ${NEUROFORGE_WORKER_MASTER_APPLY_MAX_ATTEMPTS:-5} + NEUROFORGE_WORKER_MASTER_APPLY_BACKOFF_SECONDS: ${NEUROFORGE_WORKER_MASTER_APPLY_BACKOFF_SECONDS:-5} + NEUROFORGE_WORKER_JOB_RETENTION_HOURS: ${NEUROFORGE_WORKER_JOB_RETENTION_HOURS:-168} + NEUROFORGE_WORKER_MAX_TERMINAL_JOBS: ${NEUROFORGE_WORKER_MAX_TERMINAL_JOBS:-20000} + NEUROFORGE_GRAPH_BACKFILL_ENABLED: ${NEUROFORGE_GRAPH_BACKFILL_ENABLED:-true} + NEUROFORGE_GRAPH_BACKFILL_INTERVAL_SECONDS: ${NEUROFORGE_GRAPH_BACKFILL_INTERVAL_SECONDS:-10} + NEUROFORGE_GRAPH_BACKFILL_BATCH_SIZE: ${NEUROFORGE_GRAPH_BACKFILL_BATCH_SIZE:-64} + NEUROFORGE_GRAPH_BACKFILL_MAX_QUEUED: ${NEUROFORGE_GRAPH_BACKFILL_MAX_QUEUED:-256} + NEUROFORGE_GRAPH_BACKFILL_MIN_DEGREE: ${NEUROFORGE_GRAPH_BACKFILL_MIN_DEGREE:-3} + NEUROFORGE_GRAPH_CANDIDATE_MULTIPLIER: ${NEUROFORGE_GRAPH_CANDIDATE_MULTIPLIER:-6} + NEUROFORGE_GRAPH_RETRY_AFTER_MINUTES: ${NEUROFORGE_GRAPH_RETRY_AFTER_MINUTES:-360} + NEUROFORGE_GRAPH_REQUIRE_WORKER: ${NEUROFORGE_GRAPH_REQUIRE_WORKER:-true} + NEUROFORGE_GRAPH_MAX_HOPS: ${NEUROFORGE_GRAPH_MAX_HOPS:-3} + NEUROFORGE_GRAPH_HOP_DECAY: ${NEUROFORGE_GRAPH_HOP_DECAY:-0.60} + NEUROFORGE_GRAPH_MAX_EXPANSION: ${NEUROFORGE_GRAPH_MAX_EXPANSION:-64} + NEUROFORGE_GRAPH_MIN_EDGE_WEIGHT: ${NEUROFORGE_GRAPH_MIN_EDGE_WEIGHT:-0.05} + NEUROFORGE_OFFLOAD_CHAT: ${NEUROFORGE_OFFLOAD_CHAT:-true} + NEUROFORGE_OFFLOAD_EMBEDDINGS: ${NEUROFORGE_OFFLOAD_EMBEDDINGS:-true} + NEUROFORGE_DISTRIBUTED_INFERENCE_WAIT_SECONDS: ${NEUROFORGE_DISTRIBUTED_INFERENCE_WAIT_SECONDS:-180} NEUROFORGE_READINESS_OLLAMA_LIVE: ${NEUROFORGE_READINESS_OLLAMA_LIVE:-true} NEUROFORGE_CONTROLLED_LEARNING: ${NEUROFORGE_CONTROLLED_LEARNING:-true} NEUROFORGE_GOAL_LEARNING_ENABLED: ${NEUROFORGE_GOAL_LEARNING_ENABLED:-false} @@ -115,16 +141,20 @@ services: retries: 8 start_period: 15s stop_grace_period: 35s - neuroforge-worker: - image: git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge-worker:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.5.9} + neuroforge-worker-cpu: + image: git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge-worker:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.6.0} command: - -server - http://neuroforge:8080 - -id - - mega-worker-1 + - mega-cpu-1 restart: unless-stopped environment: NEUROFORGE_WORKER_TOKEN: ${NEUROFORGE_WORKER_TOKEN:?Set a unique NeuroForge worker token} + NEUROFORGE_WORKER_RESOURCE_CLASS: cpu + NEUROFORGE_WORKER_CAPABILITIES: cpu,vector.relink + NEUROFORGE_WORKER_MAX_CONCURRENCY: ${NEUROFORGE_CPU_WORKER_CONCURRENCY:-2} + NEUROFORGE_WORKER_HEARTBEAT_INTERVAL: ${NEUROFORGE_WORKER_HEARTBEAT_INTERVAL:-15s} depends_on: neuroforge: condition: service_healthy @@ -135,8 +165,41 @@ services: - no-new-privileges:true cap_drop: - ALL + + neuroforge-worker-gpu: + image: git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge-worker:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.6.0} + command: + - -server + - http://neuroforge:8080 + - -id + - mega-gpu-1 + restart: unless-stopped + environment: + NEUROFORGE_WORKER_TOKEN: ${NEUROFORGE_WORKER_TOKEN:?Set a unique NeuroForge worker token} + NEUROFORGE_WORKER_RESOURCE_CLASS: gpu + NEUROFORGE_WORKER_CAPABILITIES: gpu,model.chat,model.embed + NEUROFORGE_WORKER_MAX_CONCURRENCY: ${NEUROFORGE_GPU_WORKER_CONCURRENCY:-1} + NEUROFORGE_WORKER_HEARTBEAT_INTERVAL: ${NEUROFORGE_WORKER_HEARTBEAT_INTERVAL:-15s} + NEUROFORGE_WORKER_OLLAMA_URL: ${OLLAMA_BASE_URL:-http://ollama:11434} + NEUROFORGE_WORKER_OLLAMA_CHAT_MODEL: ${OLLAMA_MODEL:-gemma3} + NEUROFORGE_WORKER_OLLAMA_EMBEDDING_MODEL: ${OLLAMA_EMBEDDING_MODEL:-embeddinggemma} + NEUROFORGE_WORKER_OLLAMA_NUM_CTX: ${NEUROFORGE_OLLAMA_NUM_CTX:-8192} + NEUROFORGE_WORKER_OLLAMA_KEEP_ALIVE: ${OLLAMA_KEEP_ALIVE:-10m} + depends_on: + neuroforge: + condition: service_healthy + ollama: + condition: service_started + read_only: true + tmpfs: + - /tmp:size=32m,mode=1777 + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + agent-data-init: - image: git.send.nrw/sendnrw/glpi-neuroforge-mega-agent-data-init:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.5.9} + image: git.send.nrw/sendnrw/glpi-neuroforge-mega-agent-data-init:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.6.0} restart: 'no' user: 0:0 volumes: @@ -149,7 +212,7 @@ services: - CHOWN - FOWNER agent: - image: git.send.nrw/sendnrw/glpi-neuroforge-mega-agent:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.5.9} + image: git.send.nrw/sendnrw/glpi-neuroforge-mega-agent:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.6.0} restart: unless-stopped environment: AI_CONTENT_LABEL_ENABLED: ${AI_CONTENT_LABEL_ENABLED:-} @@ -370,7 +433,7 @@ services: start_period: 10s stop_grace_period: 20s knowledge: - image: git.send.nrw/sendnrw/glpi-neuroforge-mega-knowledge:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.5.9} + image: git.send.nrw/sendnrw/glpi-neuroforge-mega-knowledge:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.6.0} restart: unless-stopped environment: APP_MODE: ${KB_APP_MODE:-editor} @@ -411,7 +474,7 @@ services: - ALL stop_grace_period: 35s control: - image: git.send.nrw/sendnrw/glpi-neuroforge-mega-control:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.5.9} + image: git.send.nrw/sendnrw/glpi-neuroforge-mega-control:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.6.0} restart: unless-stopped environment: CONTROL_ADDR: :8070 diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 0d7532d..3b33772 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -200,3 +200,7 @@ Die beiden Evidenzklassen werden absichtlich nicht vermischt. Outcome-Memories l Für source-begrenzte Exact-Fallbacks hält der Store einen rebuildbaren In-Memory-Index `Provenance.Source -> Memory IDs`. Damit wächst der Fallback mit der betreffenden Integration/Source statt mit dem gesamten Memory-Katalog. HNSW/Disk-PQ bleiben globale Kandidatenindizes. Die Qualitätsmessung ist vom Schreibpfad getrennt: `/api/quality/replay` ist read-only und evaluiert live die aktuelle Knowledge-/Outcome-Retrieval-Konfiguration gegen einen bereitgestellten historischen Fallkorpus. + +## v1.6.0 Master/Subagent consistency model + +NeuroForge owns the authoritative durable state. Subagents are stateless executors: they register capabilities, claim only compatible jobs and return lease-fenced results. Jobs and second-phase master applies are persisted through the normal WAL/checkpoint path, so restarts do not lose queued/retrying/apply-wait work. The graph backfill uses ANN candidate selection plus bounded exact relink jobs; pairwise synapses form an n:m adjacency graph and retrieval can traverse several bounded hops. See `MASTER-SUBAGENT-ORCHESTRATOR.md`. diff --git a/docs/CONTROL-CENTER.md b/docs/CONTROL-CENTER.md index 1dcd128..93fc56b 100644 --- a/docs/CONTROL-CENTER.md +++ b/docs/CONTROL-CENTER.md @@ -46,3 +46,7 @@ Die eigentlichen Laufzeitmetriken und Einzelfall-Evidenzen bleiben beim Agenten ## v1.4 Unified Graph Explorer The Control Center remains read-only. Its graph views use a dedicated Agent `CONTROL_READ_TOKEN` and the scoped NeuroForge app key. The Engineering Graph is embedded from a reproducible Go AST/Compose snapshot; optional Codebase Memory MCP is developer-only. See `UNIFIED-GRAPH.md`. + +## v1.6.0 Orchestrator / Knowledge Graph + +The read-only Control Center queries scoped NeuroForge endpoints for scheduler and graph state. It displays leader/follower role, online/stale subagents, queue states (`queued`, `claimed`, `retry_wait`, `blocked`, `apply_wait`, `failed`) and graph connectivity (`isolated`, `linked`, `multi_linked`, average/max degree, components). Administrative retry/cancel/backfill actions remain on the separately authenticated NeuroForge admin API and are not exposed by Control Center. diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index 2799abb..d972e31 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -121,3 +121,9 @@ The synthesis call, syntax repair and grounding rewrite use `NEUROFORGE_KB_STAGI Claim verification checks all material article statements in batches of `NEUROFORGE_KB_STAGING_MAX_VERIFICATION_STATEMENTS`; the value is a batch size, not a total verification cap. A hard safety ceiling of 128 material statements remains. Staging persists `article_quality` with actual article/answer lengths, configured bounds, evidence count, prompt size, expansion status and synthesis token usage. Fortinet `support-forum` pages are treated as vendor-community evidence rather than authoritative primary documentation. Editorial `technical-tip` and `troubleshooting-tip` pages remain eligible as authoritative first-party material. + +### Master/Subagent Orchestrator & Knowledge Graph (v1.6.0) + +The master scheduler is controlled by `NEUROFORGE_WORKER_LEASE_SECONDS`, `NEUROFORGE_WORKER_HEARTBEAT_SECONDS`, `NEUROFORGE_WORKER_STALE_AFTER_SECONDS`, retry/backoff/queue/retention settings, and the graph-backfill limits shown in `.env.example`. Local workers use `NEUROFORGE_CPU_WORKER_CONCURRENCY` / `NEUROFORGE_GPU_WORKER_CONCURRENCY`; remote workers use `docker-compose.subagent.yml` with a reachable `NEUROFORGE_MASTER_URL` and unique `NEUROFORGE_CPU_WORKER_ID` / `NEUROFORGE_GPU_WORKER_ID`. + +`NEUROFORGE_OFFLOAD_CHAT` and `NEUROFORGE_OFFLOAD_EMBEDDINGS` prefer a live capability-compatible GPU subagent. If no compatible live subagent is available, NeuroForge continues through its normal provider router. Graph convergence is bounded by `NEUROFORGE_GRAPH_BACKFILL_BATCH_SIZE`, `NEUROFORGE_GRAPH_BACKFILL_MAX_QUEUED`, `NEUROFORGE_GRAPH_BACKFILL_MIN_DEGREE`, `NEUROFORGE_GRAPH_CANDIDATE_MULTIPLIER` and the retry cooldown. Multi-hop retrieval is bounded by `NEUROFORGE_GRAPH_MAX_HOPS`, `NEUROFORGE_GRAPH_HOP_DECAY`, `NEUROFORGE_GRAPH_MAX_EXPANSION` and `NEUROFORGE_GRAPH_MIN_EDGE_WEIGHT`. diff --git a/docs/IMPLEMENTED.md b/docs/IMPLEMENTED.md index 0171344..6c16deb 100644 --- a/docs/IMPLEMENTED.md +++ b/docs/IMPLEMENTED.md @@ -26,6 +26,13 @@ - lokales Outcome-Audit mit `pending|learned|failed`, Retry und unveränderlicher Revisionskette - Stale-Run-Schutz gegen Lernen aus überholten GLPI-Ticketzuständen - getrennte Schalter für Research/SearXNG und zeitgesteuerte Autonomie +- persistenter Master/Subagent-Orchestrator mit Resource-/Capability-Routing, Priorität, DAG-Dependencies, Retry/Backoff und Idempotenz +- Heartbeat/Lease-Fencing inklusive Expiry-Prüfung und stale-result Schutz +- zweiphasiger `apply_wait`-Commit für Worker-Ergebnisse, die autoritativen Master-State verändern +- CPU-Subagents für `vector.relink`, GPU-Subagents für `model.chat`/`model.embed`, optional remote über `docker-compose.subagent.yml` +- bounded Knowledge-Graph-Backfill für importierte Memories (ANN-Kandidaten statt O(N²)) +- echte n:m-Adjamenz, Relationstypen, Multi-Hop-Retrieval und Graph-Health-Metriken +- Memory-Version + Vector-Fingerprint-Fencing gegen Delete/Recreate-Races während Relink ## Bewusst nicht automatisiert diff --git a/docs/MASTER-SUBAGENT-ORCHESTRATOR.md b/docs/MASTER-SUBAGENT-ORCHESTRATOR.md new file mode 100644 index 0000000..4d05cff --- /dev/null +++ b/docs/MASTER-SUBAGENT-ORCHESTRATOR.md @@ -0,0 +1,144 @@ +# NeuroForge Master / Subagent Orchestrator (v1.6.0) + +## Zielbild + +NeuroForge ist der autoritative **Master/Orchestrator**. Subagents besitzen keinen eigenen autoritativen Knowledge-State. Sie registrieren sich mit Resource-Class und Capabilities, ziehen passende Jobs und liefern Ergebnisse unter Lease-Fencing zurück. + +```text + durable WAL/checkpoint + │ + ┌────────▼────────┐ + │ NeuroForge │ + │ Master │ + │ scheduler + DAG │ + └───────┬─────────┘ + capability /│\ lease-fenced jobs + / │ \ + ┌───────────┐ │ ┌─────────────┐ + │ CPU agent │ │ │ GPU agent │ + │ relinking │ │ │ chat/embed │ + └───────────┘ │ └─────────────┘ + │ + more subagents +``` + +## Konsistenzmodell + +- Jobs sind persistent; Queue, Retry, Dependencies und Worker-Ergebnis überleben einen Master-Neustart. +- Jeder Claim erhält ein eindeutiges `lease_token`. Eine verspätete Antwort einer abgelaufenen Lease wird auch dann abgewiesen, wenn der Job noch nicht neu vergeben wurde; nach Reclaim verhindert der neue Token zusätzlich stale writes. +- Heartbeats verlängern nur aktive, zum Worker passende Leases. +- Fehler werden exponentiell mit begrenztem Backoff wiederholt; `max_attempts` verhindert Endlosschleifen. +- Abhängigkeiten (`depends_on`) bilden einen DAG: Child-Jobs bleiben `blocked`, bis alle Parents `done` sind. Ein terminal fehlgeschlagener Parent lässt das Child fail-closed scheitern. +- Worker-Ergebnisse, die autoritativen Master-State verändern (`vector.relink`), verwenden eine zweite persistente Phase `apply_wait`. Der Ergebnisblob ist bereits geschrieben, bevor der Master ihn anwendet. Master-Apply wird idempotent wiederholt und erst danach wird der Job `done`. +- Terminale Job-Historie wird nach Retention/Cap bereinigt; noch referenzierte Dependency-Jobs werden erhalten. +- In Clusterbetrieb plant und appliziert nur der aktuelle NeuroForge-Leader. Standalone ist der einzelne Master autoritativ. + +## Resource-/Capability-Routing + +Aktuelle Standardtypen: + +| Job | Resource | Capabilities | Wirkung | +|---|---|---|---| +| `vector.relink` | CPU | `cpu,vector.relink` | ANN-Kandidaten exakt nachbewerten, n:m-Synapsen liefern | +| `model.embed` | GPU | `gpu,model.embed` | Embedding über den GPU-Subagent/Ollama | +| `model.chat` | GPU | `gpu,model.chat` | Chat/Structured Output über den GPU-Subagent/Ollama | + +Ein Worker kann zusätzliche Capabilities deklarieren. Ein Job wird nur an einen Worker vergeben, der **alle** verlangten Capabilities besitzt und unter seinem `max_concurrency` liegt. + +## Knowledge-Graph statt 1:1 + +Eine Graphdatenbank besteht intern weiterhin aus paarweisen Kanten. Entscheidend für einen echten Graphen ist, dass ein Knoten mehrere Kanten besitzen kann und Traversierung mehrere Hops folgt. v1.6.0 stellt beides sicher: + +- `vector.relink` verbindet einen Memory-Knoten mit mehreren ANN-Nachbarn (`RecallK`). +- `GraphBackfillMinDegree` erzwingt für geeignete Memories einen Mindestgrad statt einer einzigen Partnerkante. +- `synapseAdj` hält eine echte Adjazenzliste und verhindert O(E)-Scans pro Hop. +- Retrieval propagiert bounded über `GraphMaxHops` Hops mit `GraphHopDecay`. +- Kanten tragen `relations[]`, z.B. `semantic_similarity`, `association`, `coactivation`. +- `GraphStats` misst `multi_linked_memories`, Max-/Durchschnittsgrad, Connected Components und Largest Component. Damit ist n:m-Verkettung objektiv prüfbar. + +Der aktuelle automatische Backfill erzeugt primär **assoziative/semantische** Beziehungen. Das ist ein Wissensgraph im graphentheoretischen Sinn, aber noch keine vollständig ontologische Aussagenlogik wie `causes`, `solves`, `depends_on`. Solche Relationstypen können später kontrolliert ergänzt werden, ohne das n:m-/Multi-Hop-Fundament zu ändern. + +## Bulk-Backfill für große Knowledge-Bestände + +Der Master legt **nicht** alle möglichen Paare als O(N²)-Jobs an. Für jeden Kandidaten wird zuerst der ANN-Index verwendet; nur eine begrenzte Menge Vektoren wird in einen `vector.relink`-Job geschrieben. + +Wichtige Limits: + +```env +NEUROFORGE_GRAPH_BACKFILL_BATCH_SIZE=64 +NEUROFORGE_GRAPH_BACKFILL_MAX_QUEUED=256 +NEUROFORGE_GRAPH_BACKFILL_MIN_DEGREE=3 +NEUROFORGE_GRAPH_CANDIDATE_MULTIPLIER=6 +NEUROFORGE_GRAPH_RETRY_AFTER_MINUTES=360 +``` + +Bereits wartende Targets werden bei der nächsten Planung übersprungen, damit ein großer Korpus nicht durch die lexikographisch ersten IDs verhungert. + +## Monitoring + +Read-only Control-API: + +```text +GET /api/v1/integrations/orchestrator/status +GET /api/v1/integrations/graph/status +``` + +Admin-API: + +```text +GET /admin/api/orchestrator/status +GET /admin/api/orchestrator/jobs +POST /admin/api/orchestrator/jobs/{id}/retry +POST /admin/api/orchestrator/jobs/{id}/cancel +GET /admin/api/graph/status +POST /admin/api/graph/backfill +``` + +Prometheus enthält u.a.: + +```text +neuroforge_jobs{status="queued|claimed|retry_wait|blocked|apply_wait|done|failed|canceled"} +neuroforge_workers{resource="cpu|gpu",status="online|stale"} +neuroforge_worker_inflight{worker="...",resource="..."} +neuroforge_worker_capacity{worker="...",resource="..."} +neuroforge_graph_memories{state="linked|isolated|multi_linked"} +neuroforge_graph_max_degree +neuroforge_graph_average_degree +neuroforge_graph_connected_components +neuroforge_graph_largest_component +``` + +## Remote Subagent + +Auf einem zusätzlichen Host nur `docker-compose.subagent.yml` und eine kleine `.env` bereitstellen. Beispiel GPU: + +```env +IMAGE_TAG=1.6.0 +NEUROFORGE_MASTER_URL=https://neuroforge.internal.example +NEUROFORGE_WORKER_TOKEN= +NEUROFORGE_GPU_WORKER_ID=gpu-node-02 +NEUROFORGE_WORKER_OLLAMA_URL=http://127.0.0.1:11434 +OLLAMA_MODEL=gemma4 +OLLAMA_EMBEDDING_MODEL=embeddinggemma +NEUROFORGE_GPU_WORKER_CONCURRENCY=1 +``` + +Dann: + +```bash +docker compose -f docker-compose.subagent.yml --profile gpu up -d +``` + +Der Master-Port muss vom Subagent erreichbar sein. Für Netze außerhalb eines vertrauenswürdigen internen Segments gehört TLS/mTLS vor den Master-Endpunkt (Reverse Proxy/Service Mesh). Der Worker-Token ist ein Service-Credential und muss separat von App/Admin/Integration-Tokens bleiben. + +## Operationaler Zielzustand + +Ein gesunder Zustand hat: + +- mindestens einen `online` CPU-Worker für Graph-Konvergenz, +- optional mindestens einen `online` GPU-Worker für Offload, +- keine dauerhaft wachsende `failed`-/`apply_wait`-Queue, +- sinkende Zahl `isolated` und steigende Zahl `multi_linked`, +- `max_degree > 1` und `largest_component > 1`, +- bounded Queue und stabile Worker-Leases, +- keine Readiness-Abhängigkeit des Masters vom Worker (verhindert Startup-Deadlocks). diff --git a/docs/MIGRATION-v1.5.9-to-v1.6.0.md b/docs/MIGRATION-v1.5.9-to-v1.6.0.md new file mode 100644 index 0000000..5931d9f --- /dev/null +++ b/docs/MIGRATION-v1.5.9-to-v1.6.0.md @@ -0,0 +1,28 @@ +# Migration v1.5.9 → v1.6.0 + +Die Migration ist **nicht destruktiv**. Keine Volumes, Knowledge-Dateien, Memories, Goals oder Staging-Drafts löschen. + +1. v1.6.0 Images bauen/publizieren. +2. `IMAGE_TAG=1.6.0` setzen. +3. Die neuen Orchestrator-/Graph-Variablen aus `.env.example` übernehmen. Die Defaults sind produktionsnah und bounded. +4. Den bisherigen einzelnen `neuroforge-worker` durch `neuroforge-worker-cpu` und `neuroforge-worker-gpu` ersetzen. +5. Stack neu erzeugen; Volumes beibehalten. + +```bash +docker compose --profile research pull +docker compose --profile research up -d --force-recreate --remove-orphans +``` + +Danach prüfen: + +```bash +docker compose ps +curl -s -H "Authorization: Bearer $NEUROFORGE_CONTROL_READ_TOKEN" \ + http://127.0.0.1:${NEUROFORGE_HOST_PORT:-8090}/api/v1/integrations/orchestrator/status | jq +curl -s -H "Authorization: Bearer $NEUROFORGE_CONTROL_READ_TOKEN" \ + http://127.0.0.1:${NEUROFORGE_HOST_PORT:-8090}/api/v1/integrations/graph/status | jq +``` + +Ein gesunder Zustand hat mindestens einen online CPU-Worker, keine dauerhaft wachsende `failed`/`apply_wait` Queue und eine sinkende Zahl isolierter Memories. GPU-Offload ist optional; ohne online GPU-Subagent fällt NeuroForge auf den normalen Provider-Router zurück. + +Remote Subagents erhalten eindeutige `NEUROFORGE_CPU_WORKER_ID` bzw. `NEUROFORGE_GPU_WORKER_ID`. Außerhalb eines vertrauenswürdigen internen Netzes den Master nur hinter TLS/mTLS veröffentlichen. diff --git a/docs/RELEASE-v1.6.0.md b/docs/RELEASE-v1.6.0.md new file mode 100644 index 0000000..2d66842 --- /dev/null +++ b/docs/RELEASE-v1.6.0.md @@ -0,0 +1,32 @@ +# GLPI NeuroForge Mega v1.6.0 + +v1.6.0 erweitert NeuroForge von einem einzelnen Workerpfad zu einem dauerhaften Master/Subagent-Orchestrator und schließt die Graph-Lücke großer Knowledge-Imports. + +## Orchestrator + +- persistente Jobs im NeuroForge-WAL/Checkpoint +- Resource-Class + Capability-Routing (`cpu`, `gpu`, `vector.relink`, `model.chat`, `model.embed`) +- Priorität, Idempotency-Key, Parent/Dependencies, Retry mit begrenztem Backoff und Max-Attempts +- Heartbeats, Worker-Staleness, Max-Concurrency und lease-token fencing +- abgelaufene Leases werden auch dann abgewiesen, wenn noch kein anderer Worker den Job übernommen hat +- Worker-Ergebnisse mit Master-Mutation verwenden `apply_wait`: Resultat zuerst persistent, danach idempotenter Master-Apply +- Apply-Retries überleben Neustarts; terminale Jobhistorie wird bounded bereinigt +- Admin-Status, Jobliste, Retry, Cancel und manueller Graph-Backfill + +## Knowledge Graph + +- importierte `knowledge.chunk`-Memories werden bounded nachverknüpft +- ANN bestimmt nur Kandidaten; CPU-Subagents führen exakte Similarity-Bewertung aus +- ein Memory kann mehrere Edges besitzen; `GraphBackfillMinDegree` verhindert den früher praktisch isolierten/1:1-artigen Zustand +- Adjazenzindex + bounded Multi-Hop-Retrieval mit Hop-Decay +- Synapsen können mehrere `relations[]` tragen +- Graphmetriken: linked/isolated/multi-linked, average/max degree, connected components, largest component +- Relink-Payload bindet Target und Kandidaten an Version + Vector-Fingerprint; veraltete Delete/Recreate-Ergebnisse werden verworfen + +## Deployment + +Der lokale Stack enthält getrennte CPU- und GPU-Subagents. Zusätzliche Hosts verwenden `docker-compose.subagent.yml`. Für Remote-Netze ist TLS/mTLS vor dem Master-Endpunkt erforderlich; der Worker-Token bleibt ein separates Service-Credential. + +## Compatibility + +Die Migration ist nicht destruktiv. Bestehende Memories/Knowledge/Goals bleiben erhalten. Nach dem Upgrade beginnt der Graph-Backfill kontrolliert im Hintergrund; deshalb können `neuroforge_synapses` und die Graph-Connectivity-Metriken nach und nach steigen. diff --git a/knowledge/01_active-directory.json b/knowledge/01_active-directory.json index 7202608..9ae0117 100644 --- a/knowledge/01_active-directory.json +++ b/knowledge/01_active-directory.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-ACTIVE-DIRECTORY-SELECT", - "title": "Active Directory", - "text": "Auswahlziel: Server, Backend und Plattformen \u003e Active Directory. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn der zentrale Verzeichnisdienst, Domänencontroller, Replikation, Vertrauensstellung, LDAP-Funktion oder die Domäne als Plattform gestört oder zu ändern ist. Typische Ticketformulierungen sind: „AD-Replikation fehlerhaft“; „Domänencontroller nicht erreichbar“; „LDAP-Abfrage schlägt fehl“; „Domänendienst gestört“. Nicht auswählen, wenn nur ein Benutzerkonto angelegt, ein Kennwort zurückgesetzt oder eine einzelne Gruppenmitgliedschaft geändert werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Einzelne Benutzer- und Gruppenaufträge gehören in die entsprechenden Kategorien unter Benutzerkonten und Berechtigungen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Active Directory“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Server, Backend und Plattformen \u003e Active Directory" - ], - "keywords": [ - "Active Directory", - "AD", - "Domänencontroller", - "Domain Controller", - "LDAP", - "Replikation", - "Domäne", - "Verzeichnisdienst", - "Server, Backend und Plattformen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/active-directory", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-ACTIVE-DIRECTORY-SELECT", + "title": "Active Directory", + "text": "Auswahlziel: Server, Backend und Plattformen \u003e Active Directory. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn der zentrale Verzeichnisdienst, Domänencontroller, Replikation, Vertrauensstellung, LDAP-Funktion oder die Domäne als Plattform gestört oder zu ändern ist. Typische Ticketformulierungen sind: „AD-Replikation fehlerhaft“; „Domänencontroller nicht erreichbar“; „LDAP-Abfrage schlägt fehl“; „Domänendienst gestört“. Nicht auswählen, wenn nur ein Benutzerkonto angelegt, ein Kennwort zurückgesetzt oder eine einzelne Gruppenmitgliedschaft geändert werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Einzelne Benutzer- und Gruppenaufträge gehören in die entsprechenden Kategorien unter Benutzerkonten und Berechtigungen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Active Directory“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Server, Backend und Plattformen \u003e Active Directory" + ], + "keywords": [ + "Active Directory", + "AD", + "Domänencontroller", + "Domain Controller", + "LDAP", + "Replikation", + "Domäne", + "Verzeichnisdienst", + "Server, Backend und Plattformen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/active-directory", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/01_arbeitsplatzdrucker.json b/knowledge/01_arbeitsplatzdrucker.json index d4b6463..5136857 100644 --- a/knowledge/01_arbeitsplatzdrucker.json +++ b/knowledge/01_arbeitsplatzdrucker.json @@ -1,25 +1,25 @@ -{ - "id": "KAT-ARBEITSPLATZDRUCKER-SELECT", - "title": "Arbeitsplatzdrucker", - "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Arbeitsplatzdrucker. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein direkt einem Arbeitsplatz zugeordneter Drucker nicht druckt, Papierstau, schlechte Druckqualität, lokale Verbindung oder einen Gerätefehler zeigt. Typische Ticketformulierungen sind: „Lokaler Drucker druckt nicht“; „Papierstau am Arbeitsplatzdrucker“; „Druck blass“; „USB-Drucker wird nicht erkannt“. Nicht auswählen, wenn ein zentraler Netzwerkdrucker, Multifunktionsgerät oder Druckserver betroffen ist oder ein neues Gerät beschafft werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Verbrauchsmaterial und Beschaffung werden bei Bedarf separat zugeordnet.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Arbeitsplatzdrucker“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Drucken, Scannen und Kopieren \u003e Arbeitsplatzdrucker" - ], - "keywords": [ - "Arbeitsplatzdrucker", - "lokaler Drucker", - "Papierstau", - "Druckqualität", - "USB-Drucker", - "druckt nicht", - "Toner", - "Drucken, Scannen und Kopieren" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/arbeitsplatzdrucker", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-ARBEITSPLATZDRUCKER-SELECT", + "title": "Arbeitsplatzdrucker", + "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Arbeitsplatzdrucker. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein direkt einem Arbeitsplatz zugeordneter Drucker nicht druckt, Papierstau, schlechte Druckqualität, lokale Verbindung oder einen Gerätefehler zeigt. Typische Ticketformulierungen sind: „Lokaler Drucker druckt nicht“; „Papierstau am Arbeitsplatzdrucker“; „Druck blass“; „USB-Drucker wird nicht erkannt“. Nicht auswählen, wenn ein zentraler Netzwerkdrucker, Multifunktionsgerät oder Druckserver betroffen ist oder ein neues Gerät beschafft werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Verbrauchsmaterial und Beschaffung werden bei Bedarf separat zugeordnet.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Arbeitsplatzdrucker“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Drucken, Scannen und Kopieren \u003e Arbeitsplatzdrucker" + ], + "keywords": [ + "Arbeitsplatzdrucker", + "lokaler Drucker", + "Papierstau", + "Druckqualität", + "USB-Drucker", + "druckt nicht", + "Toner", + "Drucken, Scannen und Kopieren" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/arbeitsplatzdrucker", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/01_fachanwendung-storung.json b/knowledge/01_fachanwendung-storung.json index c5b619a..2f961d6 100644 --- a/knowledge/01_fachanwendung-storung.json +++ b/knowledge/01_fachanwendung-storung.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-FACHANWENDUNG-STORUNG-SELECT", - "title": "Fachanwendung – Störung", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Störung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn eine konkrete Fachanwendung eine Fehlermeldung zeigt, eine fachliche Funktion nicht arbeitet, Masken nicht laden, Verarbeitungsschritte abbrechen oder einzelne Module nicht verfügbar sind. Typische Ticketformulierungen sind: „Fachverfahren zeigt Fehler“; „Buchung kann nicht abgeschlossen werden“; „Maske bleibt leer“; „Modul startet nicht“. Nicht auswählen, wenn das Problem ausschließlich durch Netzwerk, Serverplattform, Datenbankplattform oder das zentrale Benutzerkonto verursacht wird; wenn es sich nur um eine Bedienungsfrage oder neue Anforderung handelt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Die konkrete Anwendung, Fehlermeldung, betroffene Funktion und Anzahl der Betroffenen sind entscheidend.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Störung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Störung" - ], - "keywords": [ - "Fachanwendung", - "Fachverfahren", - "Fehlermeldung", - "Störung", - "Modul", - "Maske", - "Verarbeitung abgebrochen", - "Anwendungsfehler", - "funktioniert nicht", - "Fachanwendung – Störung", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-storung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-FACHANWENDUNG-STORUNG-SELECT", + "title": "Fachanwendung – Störung", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Störung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn eine konkrete Fachanwendung eine Fehlermeldung zeigt, eine fachliche Funktion nicht arbeitet, Masken nicht laden, Verarbeitungsschritte abbrechen oder einzelne Module nicht verfügbar sind. Typische Ticketformulierungen sind: „Fachverfahren zeigt Fehler“; „Buchung kann nicht abgeschlossen werden“; „Maske bleibt leer“; „Modul startet nicht“. Nicht auswählen, wenn das Problem ausschließlich durch Netzwerk, Serverplattform, Datenbankplattform oder das zentrale Benutzerkonto verursacht wird; wenn es sich nur um eine Bedienungsfrage oder neue Anforderung handelt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Die konkrete Anwendung, Fehlermeldung, betroffene Funktion und Anzahl der Betroffenen sind entscheidend.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Störung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Störung" + ], + "keywords": [ + "Fachanwendung", + "Fachverfahren", + "Fehlermeldung", + "Störung", + "Modul", + "Maske", + "Verarbeitung abgebrochen", + "Anwendungsfehler", + "funktioniert nicht", + "Fachanwendung – Störung", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-storung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/01_festnetztelefon.json b/knowledge/01_festnetztelefon.json index 84fa100..e13963c 100644 --- a/knowledge/01_festnetztelefon.json +++ b/knowledge/01_festnetztelefon.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-FESTNETZTELEFON-SELECT", - "title": "Festnetztelefon", - "text": "Auswahlziel: Telefonie und Kommunikation \u003e Festnetztelefon. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Tischtelefon oder Festnetzanschluss nicht klingelt, keine Gespräche ermöglicht, Tonprobleme zeigt, defekt ist oder lokal eingerichtet werden muss. Typische Ticketformulierungen sind: „Telefon hat keinen Wählton“; „Tischtelefon defekt“; „Anrufer nicht hörbar“; „Festnetztelefon startet nicht“. Nicht auswählen, wenn eine Rufnummer neu vergeben, eine Rufgruppe geändert oder ein Mobilfunkgerät betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei flächigem Telefonieausfall ist eine zentrale Störung zu prüfen und höher zu priorisieren.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Festnetztelefon“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Telefonie und Kommunikation \u003e Festnetztelefon" - ], - "keywords": [ - "Festnetz", - "Telefon", - "Tischtelefon", - "Hörer", - "Wählton", - "Telefonapparat", - "kein Ton", - "Telefon defekt", - "Festnetztelefon", - "Telefonie und Kommunikation" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/festnetztelefon", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-FESTNETZTELEFON-SELECT", + "title": "Festnetztelefon", + "text": "Auswahlziel: Telefonie und Kommunikation \u003e Festnetztelefon. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Tischtelefon oder Festnetzanschluss nicht klingelt, keine Gespräche ermöglicht, Tonprobleme zeigt, defekt ist oder lokal eingerichtet werden muss. Typische Ticketformulierungen sind: „Telefon hat keinen Wählton“; „Tischtelefon defekt“; „Anrufer nicht hörbar“; „Festnetztelefon startet nicht“. Nicht auswählen, wenn eine Rufnummer neu vergeben, eine Rufgruppe geändert oder ein Mobilfunkgerät betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei flächigem Telefonieausfall ist eine zentrale Störung zu prüfen und höher zu priorisieren.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Festnetztelefon“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Telefonie und Kommunikation \u003e Festnetztelefon" + ], + "keywords": [ + "Festnetz", + "Telefon", + "Tischtelefon", + "Hörer", + "Wählton", + "Telefonapparat", + "kein Ton", + "Telefon defekt", + "Festnetztelefon", + "Telefonie und Kommunikation" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/festnetztelefon", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/01_hardwarebeschaffung.json b/knowledge/01_hardwarebeschaffung.json index c820cff..0de5b50 100644 --- a/knowledge/01_hardwarebeschaffung.json +++ b/knowledge/01_hardwarebeschaffung.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-HARDWAREBESCHAFFUNG-SELECT", - "title": "Hardwarebeschaffung", - "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Hardwarebeschaffung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn Hardware außerhalb eines einfachen Support-Ersatzes beschafft werden soll, etwa Server, Netzwerkkomponenten, Arbeitsplatzgeräte, Spezialhardware oder größere Stückzahlen. Typische Ticketformulierungen sind: „Serverhardware bestellen“; „Switches beschaffen“; „Spezialscanner kaufen“; „Rahmenbestellung für Notebooks“. Nicht auswählen, wenn ein vorhandenes Gerät nur repariert, umgesetzt oder zurückgegeben wird; für konkrete Arbeitsplatz-Neubeschaffung kann auch die spezialisierte Kategorie unter Arbeitsplatz genutzt werden. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Das jeweilige Fachteam liefert Spezifikation und Bedarf; Beschaffung führt kaufmännischen Prozess.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Hardwarebeschaffung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Beschaffung, Verträge und Lizenzen \u003e Hardwarebeschaffung" - ], - "keywords": [ - "Hardwarebeschaffung", - "Hardware bestellen", - "Kauf", - "Angebot", - "Server kaufen", - "Switch beschaffen", - "Geräte bestellen", - "Investition", - "Beschaffung, Verträge und Lizenzen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/hardwarebeschaffung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-HARDWAREBESCHAFFUNG-SELECT", + "title": "Hardwarebeschaffung", + "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Hardwarebeschaffung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn Hardware außerhalb eines einfachen Support-Ersatzes beschafft werden soll, etwa Server, Netzwerkkomponenten, Arbeitsplatzgeräte, Spezialhardware oder größere Stückzahlen. Typische Ticketformulierungen sind: „Serverhardware bestellen“; „Switches beschaffen“; „Spezialscanner kaufen“; „Rahmenbestellung für Notebooks“. Nicht auswählen, wenn ein vorhandenes Gerät nur repariert, umgesetzt oder zurückgegeben wird; für konkrete Arbeitsplatz-Neubeschaffung kann auch die spezialisierte Kategorie unter Arbeitsplatz genutzt werden. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Das jeweilige Fachteam liefert Spezifikation und Bedarf; Beschaffung führt kaufmännischen Prozess.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Hardwarebeschaffung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Beschaffung, Verträge und Lizenzen \u003e Hardwarebeschaffung" + ], + "keywords": [ + "Hardwarebeschaffung", + "Hardware bestellen", + "Kauf", + "Angebot", + "Server kaufen", + "Switch beschaffen", + "Geräte bestellen", + "Investition", + "Beschaffung, Verträge und Lizenzen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/hardwarebeschaffung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/01_kennwort-zurucksetzen.json b/knowledge/01_kennwort-zurucksetzen.json index 33f28de..0e9e8c2 100644 --- a/knowledge/01_kennwort-zurucksetzen.json +++ b/knowledge/01_kennwort-zurucksetzen.json @@ -1,29 +1,29 @@ -{ - "id": "KAT-KENNWORT-ZURUCKSETZEN-SELECT", - "title": "Kennwort zurücksetzen", - "text": "Auswahlziel: Benutzerkonten und Berechtigungen \u003e Kennwort zurücksetzen. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Benutzer sein Kennwort vergessen hat, das Kennwort abgelaufen ist, eine Anmeldung wegen falscher Kennworteingaben scheitert oder das persönliche Domänenkonto gesperrt wurde. Auch typische Folgeprobleme nach einer Kennwortänderung, etwa gespeicherte alte Kennwörter auf weiteren Geräten, gehören hierher. Typische Ticketformulierungen sind: „Kennwort vergessen“; „Passwort abgelaufen“; „Konto gesperrt“; „Account locked“; „Anmeldung funktioniert nach Kennwortänderung nicht“. Nicht auswählen, wenn ein Konto neu angelegt, umbenannt oder gelöscht werden soll; wenn Rollen in einer Fachanwendung fehlen; wenn eine technische Störung des Active Directory mehrere Benutzer betrifft. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Der Support prüft Identität, betroffenen Dienst und mögliche Altkennwörter. Zentrale AD-Störungen werden an Infrastruktur und Backend übergeben.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Benutzerkonten und Berechtigungen \u003e Kennwort zurücksetzen“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Benutzerkonten und Berechtigungen \u003e Kennwort zurücksetzen" - ], - "keywords": [ - "Kennwort", - "Passwort", - "Kennwort zurücksetzen", - "Passwort vergessen", - "Konto gesperrt", - "Account locked", - "Login", - "Anmeldung", - "Domänenkonto", - "AD-Konto", - "falsches Kennwort", - "Benutzerkonten und Berechtigungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/benutzerkonten-und-berechtigungen/kennwort-zurucksetzen", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-KENNWORT-ZURUCKSETZEN-SELECT", + "title": "Kennwort zurücksetzen", + "text": "Auswahlziel: Benutzerkonten und Berechtigungen \u003e Kennwort zurücksetzen. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Benutzer sein Kennwort vergessen hat, das Kennwort abgelaufen ist, eine Anmeldung wegen falscher Kennworteingaben scheitert oder das persönliche Domänenkonto gesperrt wurde. Auch typische Folgeprobleme nach einer Kennwortänderung, etwa gespeicherte alte Kennwörter auf weiteren Geräten, gehören hierher. Typische Ticketformulierungen sind: „Kennwort vergessen“; „Passwort abgelaufen“; „Konto gesperrt“; „Account locked“; „Anmeldung funktioniert nach Kennwortänderung nicht“. Nicht auswählen, wenn ein Konto neu angelegt, umbenannt oder gelöscht werden soll; wenn Rollen in einer Fachanwendung fehlen; wenn eine technische Störung des Active Directory mehrere Benutzer betrifft. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Der Support prüft Identität, betroffenen Dienst und mögliche Altkennwörter. Zentrale AD-Störungen werden an Infrastruktur und Backend übergeben.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Benutzerkonten und Berechtigungen \u003e Kennwort zurücksetzen“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Benutzerkonten und Berechtigungen \u003e Kennwort zurücksetzen" + ], + "keywords": [ + "Kennwort", + "Passwort", + "Kennwort zurücksetzen", + "Passwort vergessen", + "Konto gesperrt", + "Account locked", + "Login", + "Anmeldung", + "Domänenkonto", + "AD-Konto", + "falsches Kennwort", + "Benutzerkonten und Berechtigungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/benutzerkonten-und-berechtigungen/kennwort-zurucksetzen", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/01_lan.json b/knowledge/01_lan.json index 27c1c32..e479cae 100644 --- a/knowledge/01_lan.json +++ b/knowledge/01_lan.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-LAN-SELECT", - "title": "LAN", - "text": "Auswahlziel: Netzwerk und Verbindungen \u003e LAN. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn eine kabelgebundene Netzwerkverbindung, Netzwerkdose, Patchung oder Ethernet-Verbindung nicht funktioniert, instabil ist oder neu bereitgestellt werden soll. Typische Ticketformulierungen sind: „Netzwerkdose ohne Verbindung“; „LAN bricht ab“; „Kein Netzwerk über Kabel“; „Neue Dose patchen“. Nicht auswählen, wenn ausschließlich WLAN, VPN, Internetzugang oder ein einzelner defekter Dockingadapter betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei nur einem Arbeitsplatz prüft der Support zunächst Kabel, Dock und Gerät; zentrale Komponenten liegen bei Infrastruktur.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e LAN“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Netzwerk und Verbindungen \u003e LAN" - ], - "keywords": [ - "LAN", - "Ethernet", - "Netzwerkdose", - "Netzwerkkabel", - "Patchen", - "kabelgebunden", - "kein Netzwerk", - "Switchport", - "Netzwerk und Verbindungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/lan", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-LAN-SELECT", + "title": "LAN", + "text": "Auswahlziel: Netzwerk und Verbindungen \u003e LAN. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn eine kabelgebundene Netzwerkverbindung, Netzwerkdose, Patchung oder Ethernet-Verbindung nicht funktioniert, instabil ist oder neu bereitgestellt werden soll. Typische Ticketformulierungen sind: „Netzwerkdose ohne Verbindung“; „LAN bricht ab“; „Kein Netzwerk über Kabel“; „Neue Dose patchen“. Nicht auswählen, wenn ausschließlich WLAN, VPN, Internetzugang oder ein einzelner defekter Dockingadapter betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei nur einem Arbeitsplatz prüft der Support zunächst Kabel, Dock und Gerät; zentrale Komponenten liegen bei Infrastruktur.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e LAN“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Netzwerk und Verbindungen \u003e LAN" + ], + "keywords": [ + "LAN", + "Ethernet", + "Netzwerkdose", + "Netzwerkkabel", + "Patchen", + "kabelgebunden", + "kein Netzwerk", + "Switchport", + "Netzwerk und Verbindungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/lan", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/01_neue-it-anforderung.json b/knowledge/01_neue-it-anforderung.json index 0ae5d63..9277582 100644 --- a/knowledge/01_neue-it-anforderung.json +++ b/knowledge/01_neue-it-anforderung.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-NEUE-IT-ANFORDERUNG-SELECT", - "title": "Neue IT-Anforderung", - "text": "Auswahlziel: Projekte, Änderungen und neue Anforderungen \u003e Neue IT-Anforderung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn ein bislang nicht vorhandener IT-Service, eine neue technische Fähigkeit oder ein organisationsübergreifender Bedarf zunächst bewertet, priorisiert und einem Fachteam zugeordnet werden soll. Typische Ticketformulierungen sind: „Neuen digitalen Dienst prüfen“; „Zusätzlichen IT-Service bereitstellen“; „Neue technische Lösung benötigt“; „Unklarer neuer IT-Bedarf“. Nicht auswählen, wenn die Lösung bereits eindeutig eine bestehende Fachanwendung betrifft, nur Hardware bestellt oder eine normale Störung gemeldet wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Diese Kategorie dient der qualifizierten Erstbewertung; danach erfolgt Übergabe an das zuständige Fachteam oder Projekt.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Projekte, Änderungen und neue Anforderungen \u003e Neue IT-Anforderung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Projekte, Änderungen und neue Anforderungen \u003e Neue IT-Anforderung" - ], - "keywords": [ - "neue IT-Anforderung", - "neuer Service", - "neue Lösung", - "Bedarf", - "Anforderung", - "Idee", - "Digitalisierung", - "Prüfauftrag", - "Neue IT-Anforderung", - "Projekte, Änderungen und neue Anforderungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/projekte-anderungen-und-neue-anforderungen/neue-it-anforderung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-NEUE-IT-ANFORDERUNG-SELECT", + "title": "Neue IT-Anforderung", + "text": "Auswahlziel: Projekte, Änderungen und neue Anforderungen \u003e Neue IT-Anforderung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn ein bislang nicht vorhandener IT-Service, eine neue technische Fähigkeit oder ein organisationsübergreifender Bedarf zunächst bewertet, priorisiert und einem Fachteam zugeordnet werden soll. Typische Ticketformulierungen sind: „Neuen digitalen Dienst prüfen“; „Zusätzlichen IT-Service bereitstellen“; „Neue technische Lösung benötigt“; „Unklarer neuer IT-Bedarf“. Nicht auswählen, wenn die Lösung bereits eindeutig eine bestehende Fachanwendung betrifft, nur Hardware bestellt oder eine normale Störung gemeldet wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Diese Kategorie dient der qualifizierten Erstbewertung; danach erfolgt Übergabe an das zuständige Fachteam oder Projekt.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Projekte, Änderungen und neue Anforderungen \u003e Neue IT-Anforderung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Projekte, Änderungen und neue Anforderungen \u003e Neue IT-Anforderung" + ], + "keywords": [ + "neue IT-Anforderung", + "neuer Service", + "neue Lösung", + "Bedarf", + "Anforderung", + "Idee", + "Digitalisierung", + "Prüfauftrag", + "Neue IT-Anforderung", + "Projekte, Änderungen und neue Anforderungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/projekte-anderungen-und-neue-anforderungen/neue-it-anforderung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/01_padagogisches-netzwerk.json b/knowledge/01_padagogisches-netzwerk.json index 4413951..bc8a4e6 100644 --- a/knowledge/01_padagogisches-netzwerk.json +++ b/knowledge/01_padagogisches-netzwerk.json @@ -1,25 +1,25 @@ -{ - "id": "KAT-PADAGOGISCHES-NETZWERK-SELECT", - "title": "Pädagogisches Netzwerk", - "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Pädagogisches Netzwerk. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn das pädagogische Netz einer Schule, seine Zugänge, Segmentierung, Internetnutzung oder schulbezogene Netzwerkdienste betroffen sind. Typische Ticketformulierungen sind: „Schülernetz nicht erreichbar“; „Pädagogisches WLAN gestört“; „Unterrichtsnetz ausgefallen“; „Zugang im pädagogischen Netz fehlt“. Nicht auswählen, wenn ausschließlich das Verwaltungsnetz, eine einzelne allgemeine Netzwerkdose oder die zentrale kommunale Standortanbindung betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Während der Übergangszeit primär Team Schulen; zentrale Infrastrukturursachen werden an Infrastruktur und Backend übergeben.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Pädagogisches Netzwerk“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Schulspezifische IT-Systeme \u003e Pädagogisches Netzwerk" - ], - "keywords": [ - "pädagogisches Netzwerk", - "Schülernetz", - "Unterrichtsnetz", - "pädagogisches WLAN", - "Schulnetz", - "Pädagogiknetz", - "Pädagogisches Netzwerk", - "Schulspezifische IT-Systeme" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/padagogisches-netzwerk", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-PADAGOGISCHES-NETZWERK-SELECT", + "title": "Pädagogisches Netzwerk", + "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Pädagogisches Netzwerk. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn das pädagogische Netz einer Schule, seine Zugänge, Segmentierung, Internetnutzung oder schulbezogene Netzwerkdienste betroffen sind. Typische Ticketformulierungen sind: „Schülernetz nicht erreichbar“; „Pädagogisches WLAN gestört“; „Unterrichtsnetz ausgefallen“; „Zugang im pädagogischen Netz fehlt“. Nicht auswählen, wenn ausschließlich das Verwaltungsnetz, eine einzelne allgemeine Netzwerkdose oder die zentrale kommunale Standortanbindung betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Während der Übergangszeit primär Team Schulen; zentrale Infrastrukturursachen werden an Infrastruktur und Backend übergeben.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Pädagogisches Netzwerk“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Schulspezifische IT-Systeme \u003e Pädagogisches Netzwerk" + ], + "keywords": [ + "pädagogisches Netzwerk", + "Schülernetz", + "Unterrichtsnetz", + "pädagogisches WLAN", + "Schulnetz", + "Pädagogiknetz", + "Pädagogisches Netzwerk", + "Schulspezifische IT-Systeme" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/padagogisches-netzwerk", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/01_pc-und-notebook.json b/knowledge/01_pc-und-notebook.json index 5aa4646..e2a66be 100644 --- a/knowledge/01_pc-und-notebook.json +++ b/knowledge/01_pc-und-notebook.json @@ -1,30 +1,30 @@ -{ - "id": "KAT-PC-UND-NOTEBOOK-SELECT", - "title": "PC und Notebook", - "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e PC und Notebook. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein einzelner Arbeitsplatz-PC oder ein dienstliches Notebook nicht startet, abstürzt, sehr langsam ist, ungewöhnliche Geräusche macht, einen Hardwaredefekt zeigt oder lokal nicht nutzbar ist. Typische Ticketformulierungen sind: „Notebook startet nicht“; „PC friert ein“; „Laptop-Akku defekt“; „Arbeitsplatzrechner sehr langsam“; „Gerät zeigt Bluescreen“. Nicht auswählen, wenn mehrere Geräte gleichzeitig betroffen sind, ein zentraler Dienst ausfällt, ein neues Gerät beschafft werden soll oder ausschließlich Monitor, Dockingstation oder Zubehör betroffen sind. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei mehreren gleichzeitig betroffenen Geräten ist ein zentraler Infrastruktur- oder Sicherheitsbezug zu prüfen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e PC und Notebook“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Arbeitsplatz und Endgeräte \u003e PC und Notebook" - ], - "keywords": [ - "PC", - "Computer", - "Notebook", - "Laptop", - "Arbeitsplatzrechner", - "startet nicht", - "Absturz", - "Bluescreen", - "langsam", - "Akku", - "Hardwaredefekt", - "PC und Notebook", - "Arbeitsplatz und Endgeräte" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/pc-und-notebook", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-PC-UND-NOTEBOOK-SELECT", + "title": "PC und Notebook", + "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e PC und Notebook. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein einzelner Arbeitsplatz-PC oder ein dienstliches Notebook nicht startet, abstürzt, sehr langsam ist, ungewöhnliche Geräusche macht, einen Hardwaredefekt zeigt oder lokal nicht nutzbar ist. Typische Ticketformulierungen sind: „Notebook startet nicht“; „PC friert ein“; „Laptop-Akku defekt“; „Arbeitsplatzrechner sehr langsam“; „Gerät zeigt Bluescreen“. Nicht auswählen, wenn mehrere Geräte gleichzeitig betroffen sind, ein zentraler Dienst ausfällt, ein neues Gerät beschafft werden soll oder ausschließlich Monitor, Dockingstation oder Zubehör betroffen sind. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei mehreren gleichzeitig betroffenen Geräten ist ein zentraler Infrastruktur- oder Sicherheitsbezug zu prüfen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e PC und Notebook“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Arbeitsplatz und Endgeräte \u003e PC und Notebook" + ], + "keywords": [ + "PC", + "Computer", + "Notebook", + "Laptop", + "Arbeitsplatzrechner", + "startet nicht", + "Absturz", + "Bluescreen", + "langsam", + "Akku", + "Hardwaredefekt", + "PC und Notebook", + "Arbeitsplatz und Endgeräte" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/pc-und-notebook", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/01_sonstiges-und-unklare-zuordnung.json b/knowledge/01_sonstiges-und-unklare-zuordnung.json index 632b608..e4f3e5e 100644 --- a/knowledge/01_sonstiges-und-unklare-zuordnung.json +++ b/knowledge/01_sonstiges-und-unklare-zuordnung.json @@ -1,24 +1,24 @@ -{ - "id": "KAT-SONSTIGES-UND-UNKLARE-ZUORDNUNG-SELECT", - "title": "Sonstiges und unklare Zuordnung", - "text": "Auswahlziel: Sonstiges und unklare Zuordnung \u003e Sonstiges und unklare Zuordnung. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, nur wenn das Ticket trotz ausreichender Beschreibung keiner vorhandenen Kategorie zuverlässig zugeordnet werden kann, mehrere völlig unterschiedliche Anliegen untrennbar vermischt oder der betroffene IT-Service nicht erkennbar ist. Typische Ticketformulierungen sind: „Unklarer IT-Fehler ohne erkennbaren Dienst“; „Mehrere nicht trennbare Anliegen“; „Betroffenes System nicht identifizierbar“. Nicht auswählen, wenn anhand von Anwendung, Gerät, Fehlermeldung, Standort oder gewünschter Leistung eine spezifische Kategorie gewählt werden kann. Diese Kategorie darf nicht allein wegen kurzer oder unvollständiger Formulierung bevorzugt werden. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Diese Auffangkategorie soll selten verwendet und regelmäßig ausgewertet werden. Vor Auswahl sind Hauptbegriffe und Kontext gegen alle spezifischen Kategorien zu prüfen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Sonstiges und unklare Zuordnung \u003e Sonstiges und unklare Zuordnung“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Sonstiges und unklare Zuordnung \u003e Sonstiges und unklare Zuordnung" - ], - "keywords": [ - "sonstiges", - "unklar", - "keine Zuordnung", - "allgemeines IT-Problem", - "nicht näher beschrieben", - "divers", - "Sonstiges und unklare Zuordnung" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/sonstiges-und-unklare-zuordnung/sonstiges-und-unklare-zuordnung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-SONSTIGES-UND-UNKLARE-ZUORDNUNG-SELECT", + "title": "Sonstiges und unklare Zuordnung", + "text": "Auswahlziel: Sonstiges und unklare Zuordnung \u003e Sonstiges und unklare Zuordnung. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, nur wenn das Ticket trotz ausreichender Beschreibung keiner vorhandenen Kategorie zuverlässig zugeordnet werden kann, mehrere völlig unterschiedliche Anliegen untrennbar vermischt oder der betroffene IT-Service nicht erkennbar ist. Typische Ticketformulierungen sind: „Unklarer IT-Fehler ohne erkennbaren Dienst“; „Mehrere nicht trennbare Anliegen“; „Betroffenes System nicht identifizierbar“. Nicht auswählen, wenn anhand von Anwendung, Gerät, Fehlermeldung, Standort oder gewünschter Leistung eine spezifische Kategorie gewählt werden kann. Diese Kategorie darf nicht allein wegen kurzer oder unvollständiger Formulierung bevorzugt werden. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Diese Auffangkategorie soll selten verwendet und regelmäßig ausgewertet werden. Vor Auswahl sind Hauptbegriffe und Kontext gegen alle spezifischen Kategorien zu prüfen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Sonstiges und unklare Zuordnung \u003e Sonstiges und unklare Zuordnung“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Sonstiges und unklare Zuordnung \u003e Sonstiges und unklare Zuordnung" + ], + "keywords": [ + "sonstiges", + "unklar", + "keine Zuordnung", + "allgemeines IT-Problem", + "nicht näher beschrieben", + "divers", + "Sonstiges und unklare Zuordnung" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/sonstiges-und-unklare-zuordnung/sonstiges-und-unklare-zuordnung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/01_verdachtige-e-mail-und-phishing.json b/knowledge/01_verdachtige-e-mail-und-phishing.json index fbdf56c..8bf3cf2 100644 --- a/knowledge/01_verdachtige-e-mail-und-phishing.json +++ b/knowledge/01_verdachtige-e-mail-und-phishing.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-VERDACHTIGE-E-MAIL-UND-PHISHING-SELECT", - "title": "Verdächtige E-Mail und Phishing", - "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Verdächtige E-Mail und Phishing. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn eine E-Mail verdächtig wirkt, einen ungewöhnlichen Link oder Anhang enthält, Zugangsdaten abfragt, eine Zahlung fordert oder der Absender möglicherweise gefälscht ist. Typische Ticketformulierungen sind: „Verdächtige Rechnung per E-Mail“; „Link in Mail angeklickt“; „Absender scheint gefälscht“; „Passwortabfrage per Mail“. Nicht auswählen, wenn es sich nur um normalen Spam ohne Sicherheitsbezug, eine Outlook-Client-Störung oder eine bekannte legitime Nachricht handelt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei geklicktem Link, geöffnetem Anhang oder eingegebenen Zugangsdaten ist die Dringlichkeit zu erhöhen und gegebenenfalls Sicherheitsvorfall zu wählen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Verdächtige E-Mail und Phishing“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "IT-Sicherheit und Datensicherung \u003e Verdächtige E-Mail und Phishing" - ], - "keywords": [ - "Phishing", - "verdächtige E-Mail", - "Fake Mail", - "gefälschter Absender", - "verdächtiger Link", - "Anhang", - "Spam", - "Zugangsdaten", - "CEO-Fraud", - "Verdächtige E-Mail und Phishing", - "IT-Sicherheit und Datensicherung" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/verdachtige-e-mail-und-phishing", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-VERDACHTIGE-E-MAIL-UND-PHISHING-SELECT", + "title": "Verdächtige E-Mail und Phishing", + "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Verdächtige E-Mail und Phishing. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn eine E-Mail verdächtig wirkt, einen ungewöhnlichen Link oder Anhang enthält, Zugangsdaten abfragt, eine Zahlung fordert oder der Absender möglicherweise gefälscht ist. Typische Ticketformulierungen sind: „Verdächtige Rechnung per E-Mail“; „Link in Mail angeklickt“; „Absender scheint gefälscht“; „Passwortabfrage per Mail“. Nicht auswählen, wenn es sich nur um normalen Spam ohne Sicherheitsbezug, eine Outlook-Client-Störung oder eine bekannte legitime Nachricht handelt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei geklicktem Link, geöffnetem Anhang oder eingegebenen Zugangsdaten ist die Dringlichkeit zu erhöhen und gegebenenfalls Sicherheitsvorfall zu wählen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Verdächtige E-Mail und Phishing“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "IT-Sicherheit und Datensicherung \u003e Verdächtige E-Mail und Phishing" + ], + "keywords": [ + "Phishing", + "verdächtige E-Mail", + "Fake Mail", + "gefälschter Absender", + "verdächtiger Link", + "Anhang", + "Spam", + "Zugangsdaten", + "CEO-Fraud", + "Verdächtige E-Mail und Phishing", + "IT-Sicherheit und Datensicherung" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/verdachtige-e-mail-und-phishing", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/02_benutzerkonto-anlegen-andern-oder-loschen.json b/knowledge/02_benutzerkonto-anlegen-andern-oder-loschen.json index bc2d5e5..7b1ba31 100644 --- a/knowledge/02_benutzerkonto-anlegen-andern-oder-loschen.json +++ b/knowledge/02_benutzerkonto-anlegen-andern-oder-loschen.json @@ -1,30 +1,30 @@ -{ - "id": "KAT-BENUTZERKONTO-ANLEGEN-ANDERN-ODER-LOSCHEN-SELECT", - "title": "Benutzerkonto anlegen, ändern oder löschen", - "text": "Auswahlziel: Benutzerkonten und Berechtigungen \u003e Benutzerkonto anlegen, ändern oder löschen. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn ein persönliches Benutzerkonto für Eintritt, Versetzung, Namensänderung, Organisationswechsel, längere Abwesenheit oder Austritt erstellt, angepasst, deaktiviert oder gelöscht werden muss. Dazu zählen technische Kontodaten im zentralen Verzeichnisdienst. Typische Ticketformulierungen sind: „Neuer Mitarbeiter benötigt ein Konto“; „Nachname hat sich geändert“; „Konto zum Austritt deaktivieren“; „Benutzer in andere Organisationseinheit verschieben“. Nicht auswählen, wenn nur ein Kennwort zurückgesetzt werden muss; wenn ausschließlich eine Rolle in einer Fachanwendung betroffen ist; wenn ein Funktionspostfach benötigt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Fachliche Berechtigungen werden nicht automatisch mit dieser Kategorie abgedeckt und müssen gegebenenfalls separat beantragt werden.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Benutzerkonten und Berechtigungen \u003e Benutzerkonto anlegen, ändern oder löschen“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Benutzerkonten und Berechtigungen \u003e Benutzerkonto anlegen, ändern oder löschen" - ], - "keywords": [ - "Benutzerkonto anlegen", - "Account erstellen", - "neuer Mitarbeiter", - "Eintritt", - "Austritt", - "Konto löschen", - "Konto deaktivieren", - "Namensänderung", - "Versetzung", - "Organisationseinheit", - "AD-Benutzer", - "Benutzerkonto anlegen, ändern oder löschen", - "Benutzerkonten und Berechtigungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/benutzerkonten-und-berechtigungen/benutzerkonto-anlegen-andern-oder-loschen", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-BENUTZERKONTO-ANLEGEN-ANDERN-ODER-LOSCHEN-SELECT", + "title": "Benutzerkonto anlegen, ändern oder löschen", + "text": "Auswahlziel: Benutzerkonten und Berechtigungen \u003e Benutzerkonto anlegen, ändern oder löschen. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn ein persönliches Benutzerkonto für Eintritt, Versetzung, Namensänderung, Organisationswechsel, längere Abwesenheit oder Austritt erstellt, angepasst, deaktiviert oder gelöscht werden muss. Dazu zählen technische Kontodaten im zentralen Verzeichnisdienst. Typische Ticketformulierungen sind: „Neuer Mitarbeiter benötigt ein Konto“; „Nachname hat sich geändert“; „Konto zum Austritt deaktivieren“; „Benutzer in andere Organisationseinheit verschieben“. Nicht auswählen, wenn nur ein Kennwort zurückgesetzt werden muss; wenn ausschließlich eine Rolle in einer Fachanwendung betroffen ist; wenn ein Funktionspostfach benötigt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Fachliche Berechtigungen werden nicht automatisch mit dieser Kategorie abgedeckt und müssen gegebenenfalls separat beantragt werden.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Benutzerkonten und Berechtigungen \u003e Benutzerkonto anlegen, ändern oder löschen“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Benutzerkonten und Berechtigungen \u003e Benutzerkonto anlegen, ändern oder löschen" + ], + "keywords": [ + "Benutzerkonto anlegen", + "Account erstellen", + "neuer Mitarbeiter", + "Eintritt", + "Austritt", + "Konto löschen", + "Konto deaktivieren", + "Namensänderung", + "Versetzung", + "Organisationseinheit", + "AD-Benutzer", + "Benutzerkonto anlegen, ändern oder löschen", + "Benutzerkonten und Berechtigungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/benutzerkonten-und-berechtigungen/benutzerkonto-anlegen-andern-oder-loschen", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/02_fachanwendung-bedienung-und-beratung.json b/knowledge/02_fachanwendung-bedienung-und-beratung.json index 6ea66a4..ad1d0c4 100644 --- a/knowledge/02_fachanwendung-bedienung-und-beratung.json +++ b/knowledge/02_fachanwendung-bedienung-und-beratung.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-FACHANWENDUNG-BEDIENUNG-UND-BERATUNG-SELECT", - "title": "Fachanwendung – Bedienung und Beratung", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Bedienung und Beratung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn die Anwendung technisch funktioniert, aber Unterstützung bei Bedienung, Prozessschritten, Eingaben, fachlicher Nutzung oder Best-Practice benötigt wird. Typische Ticketformulierungen sind: „Wie erfasse ich einen Vorgang“; „Wo finde ich die Auswertung“; „Unterstützung bei Arbeitsschritt“; „Frage zur Bedienung des Fachverfahrens“. Nicht auswählen, wenn eine Fehlermeldung oder ein technischer Ausfall vorliegt; wenn eine neue Funktion entwickelt oder eine Berechtigung geändert werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Reine Standardfragen können im First-Level angenommen werden; fachliche Prozessberatung bleibt bei Fachanwendungen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Bedienung und Beratung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Bedienung und Beratung" - ], - "keywords": [ - "Bedienung", - "Anleitung", - "Wie kann ich", - "Wo finde ich", - "Beratung", - "Nutzung", - "Arbeitsschritt", - "Fachverfahren Hilfe", - "Anwenderfrage", - "Fachanwendung – Bedienung und Beratung", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-bedienung-und-beratung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-FACHANWENDUNG-BEDIENUNG-UND-BERATUNG-SELECT", + "title": "Fachanwendung – Bedienung und Beratung", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Bedienung und Beratung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn die Anwendung technisch funktioniert, aber Unterstützung bei Bedienung, Prozessschritten, Eingaben, fachlicher Nutzung oder Best-Practice benötigt wird. Typische Ticketformulierungen sind: „Wie erfasse ich einen Vorgang“; „Wo finde ich die Auswertung“; „Unterstützung bei Arbeitsschritt“; „Frage zur Bedienung des Fachverfahrens“. Nicht auswählen, wenn eine Fehlermeldung oder ein technischer Ausfall vorliegt; wenn eine neue Funktion entwickelt oder eine Berechtigung geändert werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Reine Standardfragen können im First-Level angenommen werden; fachliche Prozessberatung bleibt bei Fachanwendungen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Bedienung und Beratung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Bedienung und Beratung" + ], + "keywords": [ + "Bedienung", + "Anleitung", + "Wie kann ich", + "Wo finde ich", + "Beratung", + "Nutzung", + "Arbeitsschritt", + "Fachverfahren Hilfe", + "Anwenderfrage", + "Fachanwendung – Bedienung und Beratung", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-bedienung-und-beratung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/02_gruppenrichtlinien.json b/knowledge/02_gruppenrichtlinien.json index a0b7ac2..43ef190 100644 --- a/knowledge/02_gruppenrichtlinien.json +++ b/knowledge/02_gruppenrichtlinien.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-GRUPPENRICHTLINIEN-SELECT", - "title": "Gruppenrichtlinien", - "text": "Auswahlziel: Server, Backend und Plattformen \u003e Gruppenrichtlinien. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn zentrale GPOs technisch erstellt, getestet, verteilt, analysiert oder korrigiert werden sollen und mehrere Systeme oder definierte Organisationseinheiten betreffen. Typische Ticketformulierungen sind: „Neue GPO verteilen“; „Richtlinie wird nicht übernommen“; „Zentrale Windows-Einstellung ändern“; „GPO-Fehler analysieren“. Nicht auswählen, wenn lediglich eine Gruppenmitgliedschaft geändert oder eine lokale Client-Einstellung repariert werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Die ähnliche Kategorie AD-Gruppen und Gruppenrichtlinien dient eher konkreten Benutzer-/Gruppenaufträgen; diese Kategorie dem Plattformbetrieb und größeren GPO-Arbeiten.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Gruppenrichtlinien“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Server, Backend und Plattformen \u003e Gruppenrichtlinien" - ], - "keywords": [ - "Gruppenrichtlinie", - "GPO", - "Group Policy", - "gpupdate", - "Richtlinie", - "OU", - "zentrale Einstellung", - "Policy", - "Gruppenrichtlinien", - "Server, Backend und Plattformen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/gruppenrichtlinien", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-GRUPPENRICHTLINIEN-SELECT", + "title": "Gruppenrichtlinien", + "text": "Auswahlziel: Server, Backend und Plattformen \u003e Gruppenrichtlinien. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn zentrale GPOs technisch erstellt, getestet, verteilt, analysiert oder korrigiert werden sollen und mehrere Systeme oder definierte Organisationseinheiten betreffen. Typische Ticketformulierungen sind: „Neue GPO verteilen“; „Richtlinie wird nicht übernommen“; „Zentrale Windows-Einstellung ändern“; „GPO-Fehler analysieren“. Nicht auswählen, wenn lediglich eine Gruppenmitgliedschaft geändert oder eine lokale Client-Einstellung repariert werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Die ähnliche Kategorie AD-Gruppen und Gruppenrichtlinien dient eher konkreten Benutzer-/Gruppenaufträgen; diese Kategorie dem Plattformbetrieb und größeren GPO-Arbeiten.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Gruppenrichtlinien“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Server, Backend und Plattformen \u003e Gruppenrichtlinien" + ], + "keywords": [ + "Gruppenrichtlinie", + "GPO", + "Group Policy", + "gpupdate", + "Richtlinie", + "OU", + "zentrale Einstellung", + "Policy", + "Gruppenrichtlinien", + "Server, Backend und Plattformen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/gruppenrichtlinien", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/02_monitor-und-dockingstation.json b/knowledge/02_monitor-und-dockingstation.json index a63b879..5c0cca5 100644 --- a/knowledge/02_monitor-und-dockingstation.json +++ b/knowledge/02_monitor-und-dockingstation.json @@ -1,30 +1,30 @@ -{ - "id": "KAT-MONITOR-UND-DOCKINGSTATION-SELECT", - "title": "Monitor und Dockingstation", - "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Monitor und Dockingstation. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Monitor kein Bild zeigt, flackert, falsch erkannt wird oder eine Dockingstation Bild, Netzwerk, USB oder Stromversorgung nicht korrekt durchreicht. Typische Ticketformulierungen sind: „Zweiter Bildschirm bleibt schwarz“; „Dockingstation erkennt Netzwerk nicht“; „Monitor flackert“; „Notebook lädt am Dock nicht“. Nicht auswählen, wenn der gesamte PC nicht startet, ein flächiges Netzwerkproblem vorliegt oder ein neuer Monitor beziehungsweise eine neue Dockingstation beschafft werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei Netzwerkproblemen über ein Dock zunächst lokale Prüfung durch den Support; bei mehreren Betroffenen Übergabe an Infrastruktur.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Monitor und Dockingstation“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Arbeitsplatz und Endgeräte \u003e Monitor und Dockingstation" - ], - "keywords": [ - "Monitor", - "Bildschirm", - "Display", - "Dockingstation", - "Dock", - "kein Bild", - "flackert", - "zweiter Bildschirm", - "USB-C", - "HDMI", - "DisplayPort", - "Monitor und Dockingstation", - "Arbeitsplatz und Endgeräte" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/monitor-und-dockingstation", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-MONITOR-UND-DOCKINGSTATION-SELECT", + "title": "Monitor und Dockingstation", + "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Monitor und Dockingstation. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Monitor kein Bild zeigt, flackert, falsch erkannt wird oder eine Dockingstation Bild, Netzwerk, USB oder Stromversorgung nicht korrekt durchreicht. Typische Ticketformulierungen sind: „Zweiter Bildschirm bleibt schwarz“; „Dockingstation erkennt Netzwerk nicht“; „Monitor flackert“; „Notebook lädt am Dock nicht“. Nicht auswählen, wenn der gesamte PC nicht startet, ein flächiges Netzwerkproblem vorliegt oder ein neuer Monitor beziehungsweise eine neue Dockingstation beschafft werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei Netzwerkproblemen über ein Dock zunächst lokale Prüfung durch den Support; bei mehreren Betroffenen Übergabe an Infrastruktur.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Monitor und Dockingstation“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Arbeitsplatz und Endgeräte \u003e Monitor und Dockingstation" + ], + "keywords": [ + "Monitor", + "Bildschirm", + "Display", + "Dockingstation", + "Dock", + "kein Bild", + "flackert", + "zweiter Bildschirm", + "USB-C", + "HDMI", + "DisplayPort", + "Monitor und Dockingstation", + "Arbeitsplatz und Endgeräte" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/monitor-und-dockingstation", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/02_netzwerkdrucker.json b/knowledge/02_netzwerkdrucker.json index c74921b..868f645 100644 --- a/knowledge/02_netzwerkdrucker.json +++ b/knowledge/02_netzwerkdrucker.json @@ -1,25 +1,25 @@ -{ - "id": "KAT-NETZWERKDRUCKER-SELECT", - "title": "Netzwerkdrucker", - "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Netzwerkdrucker. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein gemeinsam genutzter Netzwerkdrucker nicht erreichbar ist, Druckaufträge hängen, eine Warteschlange fehlerhaft ist oder mehrere Benutzer auf dasselbe Gerät nicht drucken können. Typische Ticketformulierungen sind: „Netzwerkdrucker offline“; „Druckwarteschlange hängt“; „Mehrere Nutzer können nicht drucken“; „Drucker nicht verbunden“. Nicht auswählen, wenn ein lokaler USB-Drucker, Kopierer oder rein zentraler Druckserverdienst ohne konkretes Gerät betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei zentralem Druckserver- oder Netzwerkproblem wird an Infrastruktur und Backend übergeben.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Netzwerkdrucker“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Drucken, Scannen und Kopieren \u003e Netzwerkdrucker" - ], - "keywords": [ - "Netzwerkdrucker", - "Druckwarteschlange", - "Print Queue", - "offline", - "gemeinsamer Drucker", - "Druckauftrag hängt", - "IP-Drucker", - "Drucken, Scannen und Kopieren" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/netzwerkdrucker", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-NETZWERKDRUCKER-SELECT", + "title": "Netzwerkdrucker", + "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Netzwerkdrucker. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein gemeinsam genutzter Netzwerkdrucker nicht erreichbar ist, Druckaufträge hängen, eine Warteschlange fehlerhaft ist oder mehrere Benutzer auf dasselbe Gerät nicht drucken können. Typische Ticketformulierungen sind: „Netzwerkdrucker offline“; „Druckwarteschlange hängt“; „Mehrere Nutzer können nicht drucken“; „Drucker nicht verbunden“. Nicht auswählen, wenn ein lokaler USB-Drucker, Kopierer oder rein zentraler Druckserverdienst ohne konkretes Gerät betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei zentralem Druckserver- oder Netzwerkproblem wird an Infrastruktur und Backend übergeben.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Netzwerkdrucker“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Drucken, Scannen und Kopieren \u003e Netzwerkdrucker" + ], + "keywords": [ + "Netzwerkdrucker", + "Druckwarteschlange", + "Print Queue", + "offline", + "gemeinsamer Drucker", + "Druckauftrag hängt", + "IP-Drucker", + "Drucken, Scannen und Kopieren" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/netzwerkdrucker", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/02_rufnummer-und-nebenstelle.json b/knowledge/02_rufnummer-und-nebenstelle.json index a06c2ec..b580fa8 100644 --- a/knowledge/02_rufnummer-und-nebenstelle.json +++ b/knowledge/02_rufnummer-und-nebenstelle.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-RUFNUMMER-UND-NEBENSTELLE-SELECT", - "title": "Rufnummer und Nebenstelle", - "text": "Auswahlziel: Telefonie und Kommunikation \u003e Rufnummer und Nebenstelle. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn eine Rufnummer oder Nebenstelle neu eingerichtet, geändert, einem Arbeitsplatz zugeordnet, portiert oder aufgehoben werden soll. Typische Ticketformulierungen sind: „Neue Nebenstelle einrichten“; „Rufnummer umziehen“; „Durchwahl ändern“; „Nebenstelle löschen“. Nicht auswählen, wenn nur das Telefon defekt ist, eine Weiterleitung benötigt wird oder ein Mobilfunkvertrag betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Beschaffungs- oder Vertragsanteile werden bei Bedarf an Leitung und Finanzen / Beschaffung übergeben.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Rufnummer und Nebenstelle“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Telefonie und Kommunikation \u003e Rufnummer und Nebenstelle" - ], - "keywords": [ - "Rufnummer", - "Nebenstelle", - "Durchwahl", - "Telefonnummer", - "Portierung", - "Nummer zuordnen", - "Nummer ändern", - "Rufnummer und Nebenstelle", - "Telefonie und Kommunikation" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/rufnummer-und-nebenstelle", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-RUFNUMMER-UND-NEBENSTELLE-SELECT", + "title": "Rufnummer und Nebenstelle", + "text": "Auswahlziel: Telefonie und Kommunikation \u003e Rufnummer und Nebenstelle. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn eine Rufnummer oder Nebenstelle neu eingerichtet, geändert, einem Arbeitsplatz zugeordnet, portiert oder aufgehoben werden soll. Typische Ticketformulierungen sind: „Neue Nebenstelle einrichten“; „Rufnummer umziehen“; „Durchwahl ändern“; „Nebenstelle löschen“. Nicht auswählen, wenn nur das Telefon defekt ist, eine Weiterleitung benötigt wird oder ein Mobilfunkvertrag betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Beschaffungs- oder Vertragsanteile werden bei Bedarf an Leitung und Finanzen / Beschaffung übergeben.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Rufnummer und Nebenstelle“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Telefonie und Kommunikation \u003e Rufnummer und Nebenstelle" + ], + "keywords": [ + "Rufnummer", + "Nebenstelle", + "Durchwahl", + "Telefonnummer", + "Portierung", + "Nummer zuordnen", + "Nummer ändern", + "Rufnummer und Nebenstelle", + "Telefonie und Kommunikation" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/rufnummer-und-nebenstelle", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/02_schadsoftware-und-virenfund.json b/knowledge/02_schadsoftware-und-virenfund.json index 1650b22..f7fe331 100644 --- a/knowledge/02_schadsoftware-und-virenfund.json +++ b/knowledge/02_schadsoftware-und-virenfund.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-SCHADSOFTWARE-UND-VIRENFUND-SELECT", - "title": "Schadsoftware und Virenfund", - "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Schadsoftware und Virenfund. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Virenscanner, EDR oder ein anderes Schutzsystem Malware, Trojaner, Ransomware, unerwünschte Software oder eine verdächtige Datei auf einem Gerät meldet. Typische Ticketformulierungen sind: „Virenscanner meldet Trojaner“; „Datei in Quarantäne“; „Ransomware-Verdacht“; „Malware-Fund auf Notebook“. Nicht auswählen, wenn lediglich ein Virenscanner-Update fehlt, eine allgemeine Schwachstelle bekannt ist oder nur eine verdächtige E-Mail noch nicht geöffnet wurde. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Betroffene Geräte nicht weiter verwenden und nicht eigenständig bereinigen; bei möglicher Ausbreitung als Sicherheitsvorfall eskalieren.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Schadsoftware und Virenfund“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "IT-Sicherheit und Datensicherung \u003e Schadsoftware und Virenfund" - ], - "keywords": [ - "Virus", - "Malware", - "Trojaner", - "Ransomware", - "Virenfund", - "Quarantäne", - "EDR", - "infiziert", - "Schadsoftware", - "Schadsoftware und Virenfund", - "IT-Sicherheit und Datensicherung" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/schadsoftware-und-virenfund", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-SCHADSOFTWARE-UND-VIRENFUND-SELECT", + "title": "Schadsoftware und Virenfund", + "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Schadsoftware und Virenfund. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Virenscanner, EDR oder ein anderes Schutzsystem Malware, Trojaner, Ransomware, unerwünschte Software oder eine verdächtige Datei auf einem Gerät meldet. Typische Ticketformulierungen sind: „Virenscanner meldet Trojaner“; „Datei in Quarantäne“; „Ransomware-Verdacht“; „Malware-Fund auf Notebook“. Nicht auswählen, wenn lediglich ein Virenscanner-Update fehlt, eine allgemeine Schwachstelle bekannt ist oder nur eine verdächtige E-Mail noch nicht geöffnet wurde. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Betroffene Geräte nicht weiter verwenden und nicht eigenständig bereinigen; bei möglicher Ausbreitung als Sicherheitsvorfall eskalieren.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Schadsoftware und Virenfund“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "IT-Sicherheit und Datensicherung \u003e Schadsoftware und Virenfund" + ], + "keywords": [ + "Virus", + "Malware", + "Trojaner", + "Ransomware", + "Virenfund", + "Quarantäne", + "EDR", + "infiziert", + "Schadsoftware", + "Schadsoftware und Virenfund", + "IT-Sicherheit und Datensicherung" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/schadsoftware-und-virenfund", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/02_schulverwaltungsnetz.json b/knowledge/02_schulverwaltungsnetz.json index 1ee6fb2..59e074d 100644 --- a/knowledge/02_schulverwaltungsnetz.json +++ b/knowledge/02_schulverwaltungsnetz.json @@ -1,23 +1,23 @@ -{ - "id": "KAT-SCHULVERWALTUNGSNETZ-SELECT", - "title": "Schulverwaltungsnetz", - "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Schulverwaltungsnetz. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn das getrennte Verwaltungsnetz einer Schule, Arbeitsplätze der Schulverwaltung oder schulverwaltungsspezifische Netzzugänge betroffen sind. Typische Ticketformulierungen sind: „Sekretariat ohne Verwaltungsnetz“; „Schulleitungs-PC erreicht Verwaltungsdienste nicht“; „Verwaltungs-WLAN gestört“. Nicht auswählen, wenn das pädagogische Schülernetz, eine konkrete Schulverwaltungsanwendung oder die gesamte Standortanbindung ausfällt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Zentrale Netzwerkkomponenten werden durch Team Schulen qualifiziert und an Infrastruktur und Backend weitergegeben.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Schulverwaltungsnetz“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Schulspezifische IT-Systeme \u003e Schulverwaltungsnetz" - ], - "keywords": [ - "Schulverwaltungsnetz", - "Verwaltungsnetz Schule", - "Sekretariat Netzwerk", - "Schulleitung Netzwerk", - "Schulverwaltung", - "Schulspezifische IT-Systeme" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/schulverwaltungsnetz", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-SCHULVERWALTUNGSNETZ-SELECT", + "title": "Schulverwaltungsnetz", + "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Schulverwaltungsnetz. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn das getrennte Verwaltungsnetz einer Schule, Arbeitsplätze der Schulverwaltung oder schulverwaltungsspezifische Netzzugänge betroffen sind. Typische Ticketformulierungen sind: „Sekretariat ohne Verwaltungsnetz“; „Schulleitungs-PC erreicht Verwaltungsdienste nicht“; „Verwaltungs-WLAN gestört“. Nicht auswählen, wenn das pädagogische Schülernetz, eine konkrete Schulverwaltungsanwendung oder die gesamte Standortanbindung ausfällt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Zentrale Netzwerkkomponenten werden durch Team Schulen qualifiziert und an Infrastruktur und Backend weitergegeben.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Schulverwaltungsnetz“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Schulspezifische IT-Systeme \u003e Schulverwaltungsnetz" + ], + "keywords": [ + "Schulverwaltungsnetz", + "Verwaltungsnetz Schule", + "Sekretariat Netzwerk", + "Schulleitung Netzwerk", + "Schulverwaltung", + "Schulspezifische IT-Systeme" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/schulverwaltungsnetz", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/02_softwarebeschaffung.json b/knowledge/02_softwarebeschaffung.json index 6c5df7a..88a4f90 100644 --- a/knowledge/02_softwarebeschaffung.json +++ b/knowledge/02_softwarebeschaffung.json @@ -1,25 +1,25 @@ -{ - "id": "KAT-SOFTWAREBESCHAFFUNG-SELECT", - "title": "Softwarebeschaffung", - "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Softwarebeschaffung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn neue Software, ein neues Produkt, ein Abonnement oder eine kostenpflichtige Erweiterung beschafft und vertraglich beauftragt werden soll. Typische Ticketformulierungen sind: „Neue Software kaufen“; „SaaS-Angebot beauftragen“; „Kostenpflichtiges Modul beschaffen“; „Softwareangebot prüfen“. Nicht auswählen, wenn bereits freigegebene Software nur installiert, eine Fachanwendung eingeführt oder eine vorhandene Lizenz technisch nicht erkannt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische, datenschutzrechtliche und sicherheitsbezogene Prüfung erfolgt vor Beauftragung.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Softwarebeschaffung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Beschaffung, Verträge und Lizenzen \u003e Softwarebeschaffung" - ], - "keywords": [ - "Softwarebeschaffung", - "Software kaufen", - "SaaS", - "Abonnement", - "Lizenz kaufen", - "Angebot Software", - "Bestellung Software", - "Beschaffung, Verträge und Lizenzen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/softwarebeschaffung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-SOFTWAREBESCHAFFUNG-SELECT", + "title": "Softwarebeschaffung", + "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Softwarebeschaffung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn neue Software, ein neues Produkt, ein Abonnement oder eine kostenpflichtige Erweiterung beschafft und vertraglich beauftragt werden soll. Typische Ticketformulierungen sind: „Neue Software kaufen“; „SaaS-Angebot beauftragen“; „Kostenpflichtiges Modul beschaffen“; „Softwareangebot prüfen“. Nicht auswählen, wenn bereits freigegebene Software nur installiert, eine Fachanwendung eingeführt oder eine vorhandene Lizenz technisch nicht erkannt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische, datenschutzrechtliche und sicherheitsbezogene Prüfung erfolgt vor Beauftragung.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Softwarebeschaffung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Beschaffung, Verträge und Lizenzen \u003e Softwarebeschaffung" + ], + "keywords": [ + "Softwarebeschaffung", + "Software kaufen", + "SaaS", + "Abonnement", + "Lizenz kaufen", + "Angebot Software", + "Bestellung Software", + "Beschaffung, Verträge und Lizenzen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/softwarebeschaffung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/02_wlan.json b/knowledge/02_wlan.json index 8fde4df..e94822c 100644 --- a/knowledge/02_wlan.json +++ b/knowledge/02_wlan.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-WLAN-SELECT", - "title": "WLAN", - "text": "Auswahlziel: Netzwerk und Verbindungen \u003e WLAN. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn eine drahtlose Netzwerkverbindung nicht hergestellt wird, häufig abbricht, zu schwach ist, ein WLAN-Bereich nicht versorgt wird oder ein SSID-/Authentifizierungsproblem besteht. Typische Ticketformulierungen sind: „WLAN verbindet nicht“; „Schlechter Empfang im Raum“; „SSID fehlt“; „WLAN bricht ständig ab“. Nicht auswählen, wenn Mobilfunkempfang, kabelgebundenes LAN oder ein allgemeines Internetproblem ohne WLAN-Bezug gemeint ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei einem einzelnen Gerät kann der Support vorprüfen; flächige Abdeckung und Access Points liegen bei Infrastruktur.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e WLAN“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Netzwerk und Verbindungen \u003e WLAN" - ], - "keywords": [ - "WLAN", - "Wi-Fi", - "SSID", - "Funknetz", - "Access Point", - "schlechter Empfang", - "keine Verbindung", - "WLAN-Abdeckung", - "Netzwerk und Verbindungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/wlan", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-WLAN-SELECT", + "title": "WLAN", + "text": "Auswahlziel: Netzwerk und Verbindungen \u003e WLAN. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn eine drahtlose Netzwerkverbindung nicht hergestellt wird, häufig abbricht, zu schwach ist, ein WLAN-Bereich nicht versorgt wird oder ein SSID-/Authentifizierungsproblem besteht. Typische Ticketformulierungen sind: „WLAN verbindet nicht“; „Schlechter Empfang im Raum“; „SSID fehlt“; „WLAN bricht ständig ab“. Nicht auswählen, wenn Mobilfunkempfang, kabelgebundenes LAN oder ein allgemeines Internetproblem ohne WLAN-Bezug gemeint ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei einem einzelnen Gerät kann der Support vorprüfen; flächige Abdeckung und Access Points liegen bei Infrastruktur.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e WLAN“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Netzwerk und Verbindungen \u003e WLAN" + ], + "keywords": [ + "WLAN", + "Wi-Fi", + "SSID", + "Funknetz", + "Access Point", + "schlechter Empfang", + "keine Verbindung", + "WLAN-Abdeckung", + "Netzwerk und Verbindungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/wlan", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/03_ad-gruppen-und-gruppenrichtlinien.json b/knowledge/03_ad-gruppen-und-gruppenrichtlinien.json index e5be493..3d73420 100644 --- a/knowledge/03_ad-gruppen-und-gruppenrichtlinien.json +++ b/knowledge/03_ad-gruppen-und-gruppenrichtlinien.json @@ -1,29 +1,29 @@ -{ - "id": "KAT-AD-GRUPPEN-UND-GRUPPENRICHTLINIEN-SELECT", - "title": "AD-Gruppen und Gruppenrichtlinien", - "text": "Auswahlziel: Benutzerkonten und Berechtigungen \u003e AD-Gruppen und Gruppenrichtlinien. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Mitgliedschaften in zentralen Active-Directory-Gruppen, Sicherheitsgruppen, Verteilergruppen mit AD-Bezug oder technische Gruppenrichtlinien geprüft, geändert oder neu eingerichtet werden sollen. Auch fehlerhafte Laufwerkszuordnungen oder zentrale Windows-Einstellungen durch GPO gehören hierher. Typische Ticketformulierungen sind: „Benutzer in AD-Gruppe aufnehmen“; „GPO wird nicht angewendet“; „Netzlaufwerk fehlt wegen Gruppenmitgliedschaft“; „Zentrale Windows-Richtlinie ändern“. Nicht auswählen, wenn es um eine fachliche Rolle innerhalb einer Anwendung, ein einzelnes vergessenes Kennwort oder eine lokale Einstellung an nur einem Arbeitsplatz ohne Richtlinienbezug geht. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei fachlichen Rollen ist Fachanwendungen zuständig; bei reinen Arbeitsplatzproblemen ohne zentralen Bezug zunächst der Support.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Benutzerkonten und Berechtigungen \u003e AD-Gruppen und Gruppenrichtlinien“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Benutzerkonten und Berechtigungen \u003e AD-Gruppen und Gruppenrichtlinien" - ], - "keywords": [ - "Active Directory", - "AD-Gruppe", - "Sicherheitsgruppe", - "Gruppenmitgliedschaft", - "GPO", - "Gruppenrichtlinie", - "Group Policy", - "OU", - "Laufwerkszuordnung", - "zentrale Richtlinie", - "AD-Gruppen und Gruppenrichtlinien", - "Benutzerkonten und Berechtigungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/benutzerkonten-und-berechtigungen/ad-gruppen-und-gruppenrichtlinien", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-AD-GRUPPEN-UND-GRUPPENRICHTLINIEN-SELECT", + "title": "AD-Gruppen und Gruppenrichtlinien", + "text": "Auswahlziel: Benutzerkonten und Berechtigungen \u003e AD-Gruppen und Gruppenrichtlinien. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Mitgliedschaften in zentralen Active-Directory-Gruppen, Sicherheitsgruppen, Verteilergruppen mit AD-Bezug oder technische Gruppenrichtlinien geprüft, geändert oder neu eingerichtet werden sollen. Auch fehlerhafte Laufwerkszuordnungen oder zentrale Windows-Einstellungen durch GPO gehören hierher. Typische Ticketformulierungen sind: „Benutzer in AD-Gruppe aufnehmen“; „GPO wird nicht angewendet“; „Netzlaufwerk fehlt wegen Gruppenmitgliedschaft“; „Zentrale Windows-Richtlinie ändern“. Nicht auswählen, wenn es um eine fachliche Rolle innerhalb einer Anwendung, ein einzelnes vergessenes Kennwort oder eine lokale Einstellung an nur einem Arbeitsplatz ohne Richtlinienbezug geht. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei fachlichen Rollen ist Fachanwendungen zuständig; bei reinen Arbeitsplatzproblemen ohne zentralen Bezug zunächst der Support.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Benutzerkonten und Berechtigungen \u003e AD-Gruppen und Gruppenrichtlinien“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Benutzerkonten und Berechtigungen \u003e AD-Gruppen und Gruppenrichtlinien" + ], + "keywords": [ + "Active Directory", + "AD-Gruppe", + "Sicherheitsgruppe", + "Gruppenmitgliedschaft", + "GPO", + "Gruppenrichtlinie", + "Group Policy", + "OU", + "Laufwerkszuordnung", + "zentrale Richtlinie", + "AD-Gruppen und Gruppenrichtlinien", + "Benutzerkonten und Berechtigungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/benutzerkonten-und-berechtigungen/ad-gruppen-und-gruppenrichtlinien", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/03_dateiablagen-und-netzlaufwerke.json b/knowledge/03_dateiablagen-und-netzlaufwerke.json index 7722cbe..1966361 100644 --- a/knowledge/03_dateiablagen-und-netzlaufwerke.json +++ b/knowledge/03_dateiablagen-und-netzlaufwerke.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-DATEIABLAGEN-UND-NETZLAUFWERKE-SELECT", - "title": "Dateiablagen und Netzlaufwerke", - "text": "Auswahlziel: Server, Backend und Plattformen \u003e Dateiablagen und Netzlaufwerke. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn zentrale Dateifreigaben, Netzlaufwerke, SMB-Ablagen oder Berechtigungsstrukturen nicht erreichbar sind, Speicherprobleme zeigen oder neu bereitgestellt werden sollen. Typische Ticketformulierungen sind: „Netzlaufwerk nicht erreichbar“; „Dateifreigabe anlegen“; „Ordnerberechtigung ändern“; „Speicherplatz auf Ablage voll“. Nicht auswählen, wenn nur eine lokale Datei beschädigt ist, eine Fachanwendung ihren Export nicht erzeugt oder lediglich eine Laufwerkszuordnung wegen fehlender AD-Gruppe fehlt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei gruppenbasierter Berechtigung kann zusätzlich Benutzerkonten \u003e AD-Gruppen relevant sein.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Dateiablagen und Netzlaufwerke“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Server, Backend und Plattformen \u003e Dateiablagen und Netzlaufwerke" - ], - "keywords": [ - "Netzlaufwerk", - "Dateifreigabe", - "Fileserver", - "SMB", - "Ordnerberechtigung", - "Laufwerk", - "Ablage", - "Speicherplatz", - "Dateiablagen und Netzlaufwerke", - "Server, Backend und Plattformen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/dateiablagen-und-netzlaufwerke", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-DATEIABLAGEN-UND-NETZLAUFWERKE-SELECT", + "title": "Dateiablagen und Netzlaufwerke", + "text": "Auswahlziel: Server, Backend und Plattformen \u003e Dateiablagen und Netzlaufwerke. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn zentrale Dateifreigaben, Netzlaufwerke, SMB-Ablagen oder Berechtigungsstrukturen nicht erreichbar sind, Speicherprobleme zeigen oder neu bereitgestellt werden sollen. Typische Ticketformulierungen sind: „Netzlaufwerk nicht erreichbar“; „Dateifreigabe anlegen“; „Ordnerberechtigung ändern“; „Speicherplatz auf Ablage voll“. Nicht auswählen, wenn nur eine lokale Datei beschädigt ist, eine Fachanwendung ihren Export nicht erzeugt oder lediglich eine Laufwerkszuordnung wegen fehlender AD-Gruppe fehlt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei gruppenbasierter Berechtigung kann zusätzlich Benutzerkonten \u003e AD-Gruppen relevant sein.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Dateiablagen und Netzlaufwerke“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Server, Backend und Plattformen \u003e Dateiablagen und Netzlaufwerke" + ], + "keywords": [ + "Netzlaufwerk", + "Dateifreigabe", + "Fileserver", + "SMB", + "Ordnerberechtigung", + "Laufwerk", + "Ablage", + "Speicherplatz", + "Dateiablagen und Netzlaufwerke", + "Server, Backend und Plattformen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/dateiablagen-und-netzlaufwerke", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/03_digitale-tafeln-und-prasentationstechnik.json b/knowledge/03_digitale-tafeln-und-prasentationstechnik.json index 97bd119..9f6d7ad 100644 --- a/knowledge/03_digitale-tafeln-und-prasentationstechnik.json +++ b/knowledge/03_digitale-tafeln-und-prasentationstechnik.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-DIGITALE-TAFELN-UND-PRASENTATIONSTECHNIK-SELECT", - "title": "Digitale Tafeln und Präsentationstechnik", - "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Digitale Tafeln und Präsentationstechnik. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn interaktive Tafeln, Displays, Beamer, Dokumentenkameras oder fest installierte Präsentationstechnik im Unterricht nicht funktioniert oder eingerichtet werden muss. Typische Ticketformulierungen sind: „Digitale Tafel reagiert nicht“; „Beamer im Klassenraum ohne Bild“; „Dokumentenkamera defekt“; „Interaktives Display kalibrieren“. Nicht auswählen, wenn nur ein normales Arbeitsplatzmonitorproblem, eine allgemeine Videokonferenz oder ein privates Endgerät betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Netzwerk- oder Backendursachen werden nach Erstprüfung an Infrastruktur weitergegeben.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Digitale Tafeln und Präsentationstechnik“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Schulspezifische IT-Systeme \u003e Digitale Tafeln und Präsentationstechnik" - ], - "keywords": [ - "digitale Tafel", - "Whiteboard", - "Smartboard", - "Beamer", - "Dokumentenkamera", - "interaktives Display", - "Klassenraumtechnik", - "Digitale Tafeln und Präsentationstechnik", - "Schulspezifische IT-Systeme" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/digitale-tafeln-und-prasentationstechnik", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-DIGITALE-TAFELN-UND-PRASENTATIONSTECHNIK-SELECT", + "title": "Digitale Tafeln und Präsentationstechnik", + "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Digitale Tafeln und Präsentationstechnik. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn interaktive Tafeln, Displays, Beamer, Dokumentenkameras oder fest installierte Präsentationstechnik im Unterricht nicht funktioniert oder eingerichtet werden muss. Typische Ticketformulierungen sind: „Digitale Tafel reagiert nicht“; „Beamer im Klassenraum ohne Bild“; „Dokumentenkamera defekt“; „Interaktives Display kalibrieren“. Nicht auswählen, wenn nur ein normales Arbeitsplatzmonitorproblem, eine allgemeine Videokonferenz oder ein privates Endgerät betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Netzwerk- oder Backendursachen werden nach Erstprüfung an Infrastruktur weitergegeben.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Digitale Tafeln und Präsentationstechnik“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Schulspezifische IT-Systeme \u003e Digitale Tafeln und Präsentationstechnik" + ], + "keywords": [ + "digitale Tafel", + "Whiteboard", + "Smartboard", + "Beamer", + "Dokumentenkamera", + "interaktives Display", + "Klassenraumtechnik", + "Digitale Tafeln und Präsentationstechnik", + "Schulspezifische IT-Systeme" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/digitale-tafeln-und-prasentationstechnik", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/03_fachanwendung-berechtigung.json b/knowledge/03_fachanwendung-berechtigung.json index 1457d4f..4b6ed79 100644 --- a/knowledge/03_fachanwendung-berechtigung.json +++ b/knowledge/03_fachanwendung-berechtigung.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-FACHANWENDUNG-BERECHTIGUNG-SELECT", - "title": "Fachanwendung – Berechtigung", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Berechtigung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Rollen, Rechte, Mandanten, Organisationseinheiten oder Funktionszugriffe innerhalb einer bestimmten Fachanwendung beantragt, geändert oder korrigiert werden sollen. Typische Ticketformulierungen sind: „Rolle Kassenverwalter vergeben“; „Zugriff auf Modul Personal“; „Mandant freischalten“; „Berechtigung im Fachverfahren fehlt“. Nicht auswählen, wenn das zentrale AD-Konto fehlt, das Kennwort gesperrt ist oder eine allgemeine AD-Gruppenmitgliedschaft ohne konkrete Anwendung betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Anwendung und genaue Soll-Rolle müssen genannt werden; Genehmigungswege bleiben unberührt.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Berechtigung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Berechtigung" - ], - "keywords": [ - "Fachanwendung Berechtigung", - "Rolle", - "Rechte", - "Freischaltung", - "Mandant", - "Modulzugriff", - "Benutzerrolle", - "Fachverfahren Zugriff", - "Fachanwendung – Berechtigung", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-berechtigung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-FACHANWENDUNG-BERECHTIGUNG-SELECT", + "title": "Fachanwendung – Berechtigung", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Berechtigung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Rollen, Rechte, Mandanten, Organisationseinheiten oder Funktionszugriffe innerhalb einer bestimmten Fachanwendung beantragt, geändert oder korrigiert werden sollen. Typische Ticketformulierungen sind: „Rolle Kassenverwalter vergeben“; „Zugriff auf Modul Personal“; „Mandant freischalten“; „Berechtigung im Fachverfahren fehlt“. Nicht auswählen, wenn das zentrale AD-Konto fehlt, das Kennwort gesperrt ist oder eine allgemeine AD-Gruppenmitgliedschaft ohne konkrete Anwendung betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Anwendung und genaue Soll-Rolle müssen genannt werden; Genehmigungswege bleiben unberührt.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Berechtigung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Berechtigung" + ], + "keywords": [ + "Fachanwendung Berechtigung", + "Rolle", + "Rechte", + "Freischaltung", + "Mandant", + "Modulzugriff", + "Benutzerrolle", + "Fachverfahren Zugriff", + "Fachanwendung – Berechtigung", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-berechtigung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/03_internetzugang.json b/knowledge/03_internetzugang.json index c361cf0..7610ee1 100644 --- a/knowledge/03_internetzugang.json +++ b/knowledge/03_internetzugang.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-INTERNETZUGANG-SELECT", - "title": "Internetzugang", - "text": "Auswahlziel: Netzwerk und Verbindungen \u003e Internetzugang. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Webseiten oder externe Dienste allgemein nicht erreichbar sind, der Internetzugang eines Standorts oder mehrerer Benutzer ausfällt oder auffällig langsam ist. Typische Ticketformulierungen sind: „Kein Internet im Gebäude“; „Externe Webseiten nicht erreichbar“; „Internetzugang sehr langsam“; „Mehrere Nutzer offline“. Nicht auswählen, wenn nur eine einzelne Anwendung gestört ist, ein VPN-Tunnel nicht verbindet oder eine konkrete Adresse durch die Firewall freigeschaltet werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Einzelne blockierte Ziele können eine Firewall-Thematik sein; flächige Ausfälle sind zu priorisieren.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e Internetzugang“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Netzwerk und Verbindungen \u003e Internetzugang" - ], - "keywords": [ - "Internet", - "Internetzugang", - "Webseiten nicht erreichbar", - "offline", - "WAN", - "Provider", - "Internetausfall", - "langsam", - "Netzwerk und Verbindungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/internetzugang", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-INTERNETZUGANG-SELECT", + "title": "Internetzugang", + "text": "Auswahlziel: Netzwerk und Verbindungen \u003e Internetzugang. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Webseiten oder externe Dienste allgemein nicht erreichbar sind, der Internetzugang eines Standorts oder mehrerer Benutzer ausfällt oder auffällig langsam ist. Typische Ticketformulierungen sind: „Kein Internet im Gebäude“; „Externe Webseiten nicht erreichbar“; „Internetzugang sehr langsam“; „Mehrere Nutzer offline“. Nicht auswählen, wenn nur eine einzelne Anwendung gestört ist, ein VPN-Tunnel nicht verbindet oder eine konkrete Adresse durch die Firewall freigeschaltet werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Einzelne blockierte Ziele können eine Firewall-Thematik sein; flächige Ausfälle sind zu priorisieren.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e Internetzugang“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Netzwerk und Verbindungen \u003e Internetzugang" + ], + "keywords": [ + "Internet", + "Internetzugang", + "Webseiten nicht erreichbar", + "offline", + "WAN", + "Provider", + "Internetausfall", + "langsam", + "Netzwerk und Verbindungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/internetzugang", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/03_lizenzbestellung.json b/knowledge/03_lizenzbestellung.json index 5acd6d3..86b2af4 100644 --- a/knowledge/03_lizenzbestellung.json +++ b/knowledge/03_lizenzbestellung.json @@ -1,25 +1,25 @@ -{ - "id": "KAT-LIZENZBESTELLUNG-SELECT", - "title": "Lizenzbestellung", - "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Lizenzbestellung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn zusätzliche Einzel-, Benutzer-, Geräte- oder Volumenlizenzen für bereits ausgewählte Produkte bestellt oder verlängert werden sollen. Typische Ticketformulierungen sind: „Zusätzliche Benutzerlizenz bestellen“; „Lizenz verlängern“; „Weitere Geräte lizenzieren“; „Volumenlizenz ergänzen“. Nicht auswählen, wenn nur die technische Aktivierung fehlschlägt, der gesamte Vertrag neu verhandelt oder der Lizenzbestand ausgewertet werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Produkt, Anzahl, Laufzeit, Kostenstelle und Genehmigung sollten angegeben werden.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Lizenzbestellung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Beschaffung, Verträge und Lizenzen \u003e Lizenzbestellung" - ], - "keywords": [ - "Lizenzbestellung", - "Lizenz bestellen", - "zusätzliche Lizenz", - "Seat", - "Subscription", - "Verlängerung", - "Volumenlizenz", - "Beschaffung, Verträge und Lizenzen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/lizenzbestellung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-LIZENZBESTELLUNG-SELECT", + "title": "Lizenzbestellung", + "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Lizenzbestellung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn zusätzliche Einzel-, Benutzer-, Geräte- oder Volumenlizenzen für bereits ausgewählte Produkte bestellt oder verlängert werden sollen. Typische Ticketformulierungen sind: „Zusätzliche Benutzerlizenz bestellen“; „Lizenz verlängern“; „Weitere Geräte lizenzieren“; „Volumenlizenz ergänzen“. Nicht auswählen, wenn nur die technische Aktivierung fehlschlägt, der gesamte Vertrag neu verhandelt oder der Lizenzbestand ausgewertet werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Produkt, Anzahl, Laufzeit, Kostenstelle und Genehmigung sollten angegeben werden.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Lizenzbestellung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Beschaffung, Verträge und Lizenzen \u003e Lizenzbestellung" + ], + "keywords": [ + "Lizenzbestellung", + "Lizenz bestellen", + "zusätzliche Lizenz", + "Seat", + "Subscription", + "Verlängerung", + "Volumenlizenz", + "Beschaffung, Verträge und Lizenzen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/lizenzbestellung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/03_multifunktionsgerat-und-kopierer.json b/knowledge/03_multifunktionsgerat-und-kopierer.json index 8ae249c..5128de0 100644 --- a/knowledge/03_multifunktionsgerat-und-kopierer.json +++ b/knowledge/03_multifunktionsgerat-und-kopierer.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-MULTIFUNKTIONSGERAT-UND-KOPIERER-SELECT", - "title": "Multifunktionsgerät und Kopierer", - "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Multifunktionsgerät und Kopierer. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Multifunktionsgerät oder Kopierer beim Drucken, Kopieren, Einzug, Bedienfeld oder Gerätebetrieb fehlerhaft ist. Typische Ticketformulierungen sind: „Kopierer zeigt Fehlercode“; „Dokumenteneinzug klemmt“; „MFP kopiert nicht“; „Bedienfeld reagiert nicht“. Nicht auswählen, wenn ausschließlich Scan-to-Mail, ein einzelner Arbeitsplatzdrucker oder eine Neubeschaffung betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Vertragspartner- oder Wartungseinsätze können durch Support koordiniert werden.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Multifunktionsgerät und Kopierer“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Drucken, Scannen und Kopieren \u003e Multifunktionsgerät und Kopierer" - ], - "keywords": [ - "Kopierer", - "Multifunktionsgerät", - "MFP", - "Kopieren", - "Dokumenteneinzug", - "Fehlercode", - "Bedienfeld", - "Multifunktionsgerät und Kopierer", - "Drucken, Scannen und Kopieren" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/multifunktionsgerat-und-kopierer", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-MULTIFUNKTIONSGERAT-UND-KOPIERER-SELECT", + "title": "Multifunktionsgerät und Kopierer", + "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Multifunktionsgerät und Kopierer. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Multifunktionsgerät oder Kopierer beim Drucken, Kopieren, Einzug, Bedienfeld oder Gerätebetrieb fehlerhaft ist. Typische Ticketformulierungen sind: „Kopierer zeigt Fehlercode“; „Dokumenteneinzug klemmt“; „MFP kopiert nicht“; „Bedienfeld reagiert nicht“. Nicht auswählen, wenn ausschließlich Scan-to-Mail, ein einzelner Arbeitsplatzdrucker oder eine Neubeschaffung betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Vertragspartner- oder Wartungseinsätze können durch Support koordiniert werden.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Multifunktionsgerät und Kopierer“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Drucken, Scannen und Kopieren \u003e Multifunktionsgerät und Kopierer" + ], + "keywords": [ + "Kopierer", + "Multifunktionsgerät", + "MFP", + "Kopieren", + "Dokumenteneinzug", + "Fehlercode", + "Bedienfeld", + "Multifunktionsgerät und Kopierer", + "Drucken, Scannen und Kopieren" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/multifunktionsgerat-und-kopierer", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/03_rufgruppe-und-weiterleitung.json b/knowledge/03_rufgruppe-und-weiterleitung.json index a56e673..8a82690 100644 --- a/knowledge/03_rufgruppe-und-weiterleitung.json +++ b/knowledge/03_rufgruppe-und-weiterleitung.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-RUFGRUPPE-UND-WEITERLEITUNG-SELECT", - "title": "Rufgruppe und Weiterleitung", - "text": "Auswahlziel: Telefonie und Kommunikation \u003e Rufgruppe und Weiterleitung. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn Rufgruppen, Sammelanschlüsse, Vertretungen, Anrufweiterleitungen, Zeitsteuerungen oder Erreichbarkeitsregeln eingerichtet oder geändert werden sollen. Typische Ticketformulierungen sind: „Rufumleitung für Urlaub“; „Mitarbeiter in Rufgruppe aufnehmen“; „Sammelruf ändern“; „Zeitsteuerung der Zentrale“. Nicht auswählen, wenn eine neue Rufnummer benötigt wird, das Telefon physisch defekt ist oder eine E-Mail-Weiterleitung gemeint ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Gewünschte Quell- und Zielnummer sowie Zeitraum müssen klar angegeben werden.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Rufgruppe und Weiterleitung“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Telefonie und Kommunikation \u003e Rufgruppe und Weiterleitung" - ], - "keywords": [ - "Rufgruppe", - "Weiterleitung", - "Rufumleitung", - "Sammelruf", - "Vertretung", - "Anrufweiterleitung", - "Zeitsteuerung", - "Erreichbarkeit", - "Rufgruppe und Weiterleitung", - "Telefonie und Kommunikation" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/rufgruppe-und-weiterleitung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-RUFGRUPPE-UND-WEITERLEITUNG-SELECT", + "title": "Rufgruppe und Weiterleitung", + "text": "Auswahlziel: Telefonie und Kommunikation \u003e Rufgruppe und Weiterleitung. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn Rufgruppen, Sammelanschlüsse, Vertretungen, Anrufweiterleitungen, Zeitsteuerungen oder Erreichbarkeitsregeln eingerichtet oder geändert werden sollen. Typische Ticketformulierungen sind: „Rufumleitung für Urlaub“; „Mitarbeiter in Rufgruppe aufnehmen“; „Sammelruf ändern“; „Zeitsteuerung der Zentrale“. Nicht auswählen, wenn eine neue Rufnummer benötigt wird, das Telefon physisch defekt ist oder eine E-Mail-Weiterleitung gemeint ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Gewünschte Quell- und Zielnummer sowie Zeitraum müssen klar angegeben werden.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Rufgruppe und Weiterleitung“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Telefonie und Kommunikation \u003e Rufgruppe und Weiterleitung" + ], + "keywords": [ + "Rufgruppe", + "Weiterleitung", + "Rufumleitung", + "Sammelruf", + "Vertretung", + "Anrufweiterleitung", + "Zeitsteuerung", + "Erreichbarkeit", + "Rufgruppe und Weiterleitung", + "Telefonie und Kommunikation" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/rufgruppe-und-weiterleitung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/03_sicherheitsvorfall.json b/knowledge/03_sicherheitsvorfall.json index c55f811..8e33a3a 100644 --- a/knowledge/03_sicherheitsvorfall.json +++ b/knowledge/03_sicherheitsvorfall.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-SICHERHEITSVORFALL-SELECT", - "title": "Sicherheitsvorfall", - "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Sicherheitsvorfall. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn ein tatsächlicher oder ernsthaft vermuteter unbefugter Zugriff, Datenabfluss, kompromittiertes Konto, Verlust sensibler Daten, aktive Attacke oder erhebliche Sicherheitsverletzung vorliegt. Typische Ticketformulierungen sind: „Konto möglicherweise übernommen“; „Unbefugter Zugriff festgestellt“; „Daten an falschen Empfänger“; „Aktiver Angriff“; „Dienstgerät mit sensiblen Daten verloren“. Nicht auswählen, wenn nur eine allgemeine Sicherheitsfrage, Schwachstellenmeldung ohne Ausnutzung oder verdächtige E-Mail ohne Interaktion vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Unmittelbar priorisieren und Leitung sowie erforderliche Datenschutz-/Informationssicherheitsstellen nach internen Meldewegen beteiligen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Sicherheitsvorfall“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "IT-Sicherheit und Datensicherung \u003e Sicherheitsvorfall" - ], - "keywords": [ - "Sicherheitsvorfall", - "Datenabfluss", - "kompromittiert", - "unbefugter Zugriff", - "Account übernommen", - "Cyberangriff", - "Datenverlust", - "Incident", - "Security Breach", - "IT-Sicherheit und Datensicherung" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/sicherheitsvorfall", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-SICHERHEITSVORFALL-SELECT", + "title": "Sicherheitsvorfall", + "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Sicherheitsvorfall. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn ein tatsächlicher oder ernsthaft vermuteter unbefugter Zugriff, Datenabfluss, kompromittiertes Konto, Verlust sensibler Daten, aktive Attacke oder erhebliche Sicherheitsverletzung vorliegt. Typische Ticketformulierungen sind: „Konto möglicherweise übernommen“; „Unbefugter Zugriff festgestellt“; „Daten an falschen Empfänger“; „Aktiver Angriff“; „Dienstgerät mit sensiblen Daten verloren“. Nicht auswählen, wenn nur eine allgemeine Sicherheitsfrage, Schwachstellenmeldung ohne Ausnutzung oder verdächtige E-Mail ohne Interaktion vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Unmittelbar priorisieren und Leitung sowie erforderliche Datenschutz-/Informationssicherheitsstellen nach internen Meldewegen beteiligen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Sicherheitsvorfall“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "IT-Sicherheit und Datensicherung \u003e Sicherheitsvorfall" + ], + "keywords": [ + "Sicherheitsvorfall", + "Datenabfluss", + "kompromittiert", + "unbefugter Zugriff", + "Account übernommen", + "Cyberangriff", + "Datenverlust", + "Incident", + "Security Breach", + "IT-Sicherheit und Datensicherung" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/sicherheitsvorfall", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/03_tastatur-maus-und-zubehor.json b/knowledge/03_tastatur-maus-und-zubehor.json index d96002f..89a6999 100644 --- a/knowledge/03_tastatur-maus-und-zubehor.json +++ b/knowledge/03_tastatur-maus-und-zubehor.json @@ -1,29 +1,29 @@ -{ - "id": "KAT-TASTATUR-MAUS-UND-ZUBEHOR-SELECT", - "title": "Tastatur, Maus und Zubehör", - "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Tastatur, Maus und Zubehör. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn Tastatur, Maus, Headset, Webcam, Netzteil, Adapter, Kabel oder sonstige Arbeitsplatzperipherie defekt, nicht erkannt oder nicht vorhanden ist. Typische Ticketformulierungen sind: „Maus reagiert nicht“; „Tastatur defekt“; „Webcam wird nicht erkannt“; „Headset ohne Ton“; „Netzteil fehlt“. Nicht auswählen, wenn ein komplettes Endgerät ausfällt, ein Telekommunikationsgerät betroffen ist oder eine Neubeschaffung außerhalb eines Ersatzfalls beantragt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Einfache Ersatzteile bearbeitet der Support; kostenpflichtige Neubeschaffungen können an Beschaffung übergeben werden.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Tastatur, Maus und Zubehör“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Arbeitsplatz und Endgeräte \u003e Tastatur, Maus und Zubehör" - ], - "keywords": [ - "Tastatur", - "Maus", - "Headset", - "Webcam", - "Netzteil", - "Adapter", - "Kabel", - "USB-Gerät", - "Peripherie", - "Zubehör", - "Tastatur, Maus und Zubehör", - "Arbeitsplatz und Endgeräte" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/tastatur-maus-und-zubehor", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-TASTATUR-MAUS-UND-ZUBEHOR-SELECT", + "title": "Tastatur, Maus und Zubehör", + "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Tastatur, Maus und Zubehör. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn Tastatur, Maus, Headset, Webcam, Netzteil, Adapter, Kabel oder sonstige Arbeitsplatzperipherie defekt, nicht erkannt oder nicht vorhanden ist. Typische Ticketformulierungen sind: „Maus reagiert nicht“; „Tastatur defekt“; „Webcam wird nicht erkannt“; „Headset ohne Ton“; „Netzteil fehlt“. Nicht auswählen, wenn ein komplettes Endgerät ausfällt, ein Telekommunikationsgerät betroffen ist oder eine Neubeschaffung außerhalb eines Ersatzfalls beantragt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Einfache Ersatzteile bearbeitet der Support; kostenpflichtige Neubeschaffungen können an Beschaffung übergeben werden.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Tastatur, Maus und Zubehör“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Arbeitsplatz und Endgeräte \u003e Tastatur, Maus und Zubehör" + ], + "keywords": [ + "Tastatur", + "Maus", + "Headset", + "Webcam", + "Netzteil", + "Adapter", + "Kabel", + "USB-Gerät", + "Peripherie", + "Zubehör", + "Tastatur, Maus und Zubehör", + "Arbeitsplatz und Endgeräte" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/tastatur-maus-und-zubehor", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/04_anwendungsberechtigung.json b/knowledge/04_anwendungsberechtigung.json index 12054c2..287cc42 100644 --- a/knowledge/04_anwendungsberechtigung.json +++ b/knowledge/04_anwendungsberechtigung.json @@ -1,30 +1,30 @@ -{ - "id": "KAT-ANWENDUNGSBERECHTIGUNG-SELECT", - "title": "Anwendungsberechtigung", - "text": "Auswahlziel: Benutzerkonten und Berechtigungen \u003e Anwendungsberechtigung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn ein Benutzer Zugriff, eine Rolle, ein Funktionsrecht oder eine organisatorische Zuordnung innerhalb einer konkreten Fachanwendung benötigt oder wenn eine vorhandene Berechtigung dort nicht korrekt wirkt. Typische Ticketformulierungen sind: „Rolle Sachbearbeitung fehlt“; „Kein Zugriff auf Modul Kasse“; „Berechtigung in Fachverfahren beantragen“; „Benutzer sieht falsche Organisationseinheit“. Nicht auswählen, wenn das zentrale Benutzerkonto selbst fehlt oder gesperrt ist; wenn eine AD-Gruppe oder GPO geändert werden soll; wenn der Zugriff technisch wegen Netzwerk, VPN oder Serverausfall scheitert. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Die betroffene Anwendung und die gewünschte Rolle sollten genannt werden. Technische Konten bleiben bei Infrastruktur und Backend.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Benutzerkonten und Berechtigungen \u003e Anwendungsberechtigung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Benutzerkonten und Berechtigungen \u003e Anwendungsberechtigung" - ], - "keywords": [ - "Berechtigung", - "Rolle", - "Zugriff", - "Freischaltung", - "Fachanwendung", - "Fachverfahren", - "Modul", - "Rechte", - "Benutzerrolle", - "Mandant", - "Organisationseinheit", - "Anwendungsberechtigung", - "Benutzerkonten und Berechtigungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/benutzerkonten-und-berechtigungen/anwendungsberechtigung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-ANWENDUNGSBERECHTIGUNG-SELECT", + "title": "Anwendungsberechtigung", + "text": "Auswahlziel: Benutzerkonten und Berechtigungen \u003e Anwendungsberechtigung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn ein Benutzer Zugriff, eine Rolle, ein Funktionsrecht oder eine organisatorische Zuordnung innerhalb einer konkreten Fachanwendung benötigt oder wenn eine vorhandene Berechtigung dort nicht korrekt wirkt. Typische Ticketformulierungen sind: „Rolle Sachbearbeitung fehlt“; „Kein Zugriff auf Modul Kasse“; „Berechtigung in Fachverfahren beantragen“; „Benutzer sieht falsche Organisationseinheit“. Nicht auswählen, wenn das zentrale Benutzerkonto selbst fehlt oder gesperrt ist; wenn eine AD-Gruppe oder GPO geändert werden soll; wenn der Zugriff technisch wegen Netzwerk, VPN oder Serverausfall scheitert. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Die betroffene Anwendung und die gewünschte Rolle sollten genannt werden. Technische Konten bleiben bei Infrastruktur und Backend.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Benutzerkonten und Berechtigungen \u003e Anwendungsberechtigung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Benutzerkonten und Berechtigungen \u003e Anwendungsberechtigung" + ], + "keywords": [ + "Berechtigung", + "Rolle", + "Zugriff", + "Freischaltung", + "Fachanwendung", + "Fachverfahren", + "Modul", + "Rechte", + "Benutzerrolle", + "Mandant", + "Organisationseinheit", + "Anwendungsberechtigung", + "Benutzerkonten und Berechtigungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/benutzerkonten-und-berechtigungen/anwendungsberechtigung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/04_computerraume.json b/knowledge/04_computerraume.json index 919d3dd..88bd2c5 100644 --- a/knowledge/04_computerraume.json +++ b/knowledge/04_computerraume.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-COMPUTERRAUME-SELECT", - "title": "Computerräume", - "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Computerräume. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn mehrere PCs, Peripheriegeräte, Anmeldungen oder die technische Ausstattung eines Computerraums betroffen sind oder der Raum neu eingerichtet werden soll. Typische Ticketformulierungen sind: „Mehrere PCs im Computerraum starten nicht“; „Computerraum neu ausstatten“; „Schüler können sich im Raum nicht anmelden“; „Raumsoftware verteilen“. Nicht auswählen, wenn nur ein einzelner Lehrerarbeitsplatz oder eine allgemeine Standortstörung vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Mehrgeräteprobleme sprechen oft für zentrale Richtlinien, Images oder Netzwerkursachen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Computerräume“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Schulspezifische IT-Systeme \u003e Computerräume" - ], - "keywords": [ - "Computerraum", - "PC-Raum", - "Informatikraum", - "Schüler-PC", - "Raumausstattung", - "mehrere Rechner", - "Unterrichtsraum", - "Computerräume", - "Schulspezifische IT-Systeme" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/computerraume", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-COMPUTERRAUME-SELECT", + "title": "Computerräume", + "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Computerräume. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn mehrere PCs, Peripheriegeräte, Anmeldungen oder die technische Ausstattung eines Computerraums betroffen sind oder der Raum neu eingerichtet werden soll. Typische Ticketformulierungen sind: „Mehrere PCs im Computerraum starten nicht“; „Computerraum neu ausstatten“; „Schüler können sich im Raum nicht anmelden“; „Raumsoftware verteilen“. Nicht auswählen, wenn nur ein einzelner Lehrerarbeitsplatz oder eine allgemeine Standortstörung vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Mehrgeräteprobleme sprechen oft für zentrale Richtlinien, Images oder Netzwerkursachen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Computerräume“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Schulspezifische IT-Systeme \u003e Computerräume" + ], + "keywords": [ + "Computerraum", + "PC-Raum", + "Informatikraum", + "Schüler-PC", + "Raumausstattung", + "mehrere Rechner", + "Unterrichtsraum", + "Computerräume", + "Schulspezifische IT-Systeme" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/computerraume", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/04_fachanwendung-konfiguration.json b/knowledge/04_fachanwendung-konfiguration.json index 3e68246..42ec045 100644 --- a/knowledge/04_fachanwendung-konfiguration.json +++ b/knowledge/04_fachanwendung-konfiguration.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-FACHANWENDUNG-KONFIGURATION-SELECT", - "title": "Fachanwendung – Konfiguration", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Konfiguration. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Parameter, Vorlagen, Masken, Stammdaten, Nummernkreise, Workflows oder organisatorische Einstellungen einer Fachanwendung angepasst werden sollen. Typische Ticketformulierungen sind: „Neue Vorlage hinterlegen“; „Workflow anpassen“; „Stammdaten konfigurieren“; „Nummernkreis ändern“. Nicht auswählen, wenn nur ein einzelner Benutzer eine lokale Einstellung benötigt, eine neue umfangreiche Funktion gefordert wird oder die technische Serverplattform geändert werden muss. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Größere funktionale Erweiterungen gehören zu Neue Anforderung; technische Plattformparameter zu Infrastruktur und Backend.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Konfiguration“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Konfiguration" - ], - "keywords": [ - "Konfiguration", - "Parameter", - "Vorlage", - "Stammdaten", - "Workflow", - "Maske anpassen", - "Nummernkreis", - "Einstellung Fachanwendung", - "Fachanwendung – Konfiguration", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-konfiguration", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-FACHANWENDUNG-KONFIGURATION-SELECT", + "title": "Fachanwendung – Konfiguration", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Konfiguration. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Parameter, Vorlagen, Masken, Stammdaten, Nummernkreise, Workflows oder organisatorische Einstellungen einer Fachanwendung angepasst werden sollen. Typische Ticketformulierungen sind: „Neue Vorlage hinterlegen“; „Workflow anpassen“; „Stammdaten konfigurieren“; „Nummernkreis ändern“. Nicht auswählen, wenn nur ein einzelner Benutzer eine lokale Einstellung benötigt, eine neue umfangreiche Funktion gefordert wird oder die technische Serverplattform geändert werden muss. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Größere funktionale Erweiterungen gehören zu Neue Anforderung; technische Plattformparameter zu Infrastruktur und Backend.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Konfiguration“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Konfiguration" + ], + "keywords": [ + "Konfiguration", + "Parameter", + "Vorlage", + "Stammdaten", + "Workflow", + "Maske anpassen", + "Nummernkreis", + "Einstellung Fachanwendung", + "Fachanwendung – Konfiguration", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-konfiguration", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/04_lizenzverwaltung.json b/knowledge/04_lizenzverwaltung.json index e418204..5c43d3b 100644 --- a/knowledge/04_lizenzverwaltung.json +++ b/knowledge/04_lizenzverwaltung.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-LIZENZVERWALTUNG-SELECT", - "title": "Lizenzverwaltung", - "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Lizenzverwaltung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn Lizenzbestände, Zuordnungen, Nutzungsrechte, Laufzeiten, Compliance oder verfügbare Kontingente dokumentiert und geprüft werden sollen. Typische Ticketformulierungen sind: „Lizenzbestand prüfen“; „Lizenz einem Benutzer zuordnen“; „Unterlizenzierung bewerten“; „Laufzeiten auswerten“. Nicht auswählen, wenn neue Lizenzen konkret bestellt oder eine technische Aktivierungsstörung behoben werden muss. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Zuweisung kann durch Fachteam erfolgen; kaufmännischer Bestand bleibt bei Beschaffung.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Lizenzverwaltung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Beschaffung, Verträge und Lizenzen \u003e Lizenzverwaltung" - ], - "keywords": [ - "Lizenzverwaltung", - "Lizenzbestand", - "Compliance", - "Nutzungsrecht", - "Lizenzzuordnung", - "Kontingent", - "Ablaufdatum", - "Asset Management", - "Beschaffung, Verträge und Lizenzen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/lizenzverwaltung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-LIZENZVERWALTUNG-SELECT", + "title": "Lizenzverwaltung", + "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Lizenzverwaltung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn Lizenzbestände, Zuordnungen, Nutzungsrechte, Laufzeiten, Compliance oder verfügbare Kontingente dokumentiert und geprüft werden sollen. Typische Ticketformulierungen sind: „Lizenzbestand prüfen“; „Lizenz einem Benutzer zuordnen“; „Unterlizenzierung bewerten“; „Laufzeiten auswerten“. Nicht auswählen, wenn neue Lizenzen konkret bestellt oder eine technische Aktivierungsstörung behoben werden muss. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Zuweisung kann durch Fachteam erfolgen; kaufmännischer Bestand bleibt bei Beschaffung.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Lizenzverwaltung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Beschaffung, Verträge und Lizenzen \u003e Lizenzverwaltung" + ], + "keywords": [ + "Lizenzverwaltung", + "Lizenzbestand", + "Compliance", + "Nutzungsrecht", + "Lizenzzuordnung", + "Kontingent", + "Ablaufdatum", + "Asset Management", + "Beschaffung, Verträge und Lizenzen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/lizenzverwaltung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/04_mobile-endgerate.json b/knowledge/04_mobile-endgerate.json index b75d679..4c9eeb5 100644 --- a/knowledge/04_mobile-endgerate.json +++ b/knowledge/04_mobile-endgerate.json @@ -1,29 +1,29 @@ -{ - "id": "KAT-MOBILE-ENDGERATE-SELECT", - "title": "Mobile Endgeräte", - "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Mobile Endgeräte. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein dienstliches Smartphone oder Tablet eingerichtet, zurückgesetzt, ausgetauscht oder bei einem Geräte-, App-, Synchronisations- oder lokalen Bedienproblem unterstützt werden muss. Typische Ticketformulierungen sind: „Diensthandy lässt sich nicht entsperren“; „Tablet synchronisiert nicht“; „Smartphone einrichten“; „Mobiles Gerät zurücksetzen“. Nicht auswählen, wenn es um Mobilfunktarif, SIM-Karte oder Rufnummer geht; wenn MFA lediglich auf ein neues Gerät übertragen werden muss; wenn das Gerät neu beschafft werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: SIM- und Vertragsfragen gehören zu Telefonie und Kommunikation \u003e Mobilfunk. Sicherheitsrelevanter Verlust ist zusätzlich als Sicherheitsvorfall zu behandeln.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Mobile Endgeräte“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Arbeitsplatz und Endgeräte \u003e Mobile Endgeräte" - ], - "keywords": [ - "Smartphone", - "Diensthandy", - "Tablet", - "Mobilgerät", - "iPhone", - "Android", - "iPad", - "Synchronisation", - "Geräteeinrichtung", - "Zurücksetzen", - "Mobile Endgeräte", - "Arbeitsplatz und Endgeräte" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/mobile-endgerate", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-MOBILE-ENDGERATE-SELECT", + "title": "Mobile Endgeräte", + "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Mobile Endgeräte. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein dienstliches Smartphone oder Tablet eingerichtet, zurückgesetzt, ausgetauscht oder bei einem Geräte-, App-, Synchronisations- oder lokalen Bedienproblem unterstützt werden muss. Typische Ticketformulierungen sind: „Diensthandy lässt sich nicht entsperren“; „Tablet synchronisiert nicht“; „Smartphone einrichten“; „Mobiles Gerät zurücksetzen“. Nicht auswählen, wenn es um Mobilfunktarif, SIM-Karte oder Rufnummer geht; wenn MFA lediglich auf ein neues Gerät übertragen werden muss; wenn das Gerät neu beschafft werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: SIM- und Vertragsfragen gehören zu Telefonie und Kommunikation \u003e Mobilfunk. Sicherheitsrelevanter Verlust ist zusätzlich als Sicherheitsvorfall zu behandeln.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Mobile Endgeräte“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Arbeitsplatz und Endgeräte \u003e Mobile Endgeräte" + ], + "keywords": [ + "Smartphone", + "Diensthandy", + "Tablet", + "Mobilgerät", + "iPhone", + "Android", + "iPad", + "Synchronisation", + "Geräteeinrichtung", + "Zurücksetzen", + "Mobile Endgeräte", + "Arbeitsplatz und Endgeräte" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/mobile-endgerate", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/04_mobilfunk.json b/knowledge/04_mobilfunk.json index 4422242..7565efe 100644 --- a/knowledge/04_mobilfunk.json +++ b/knowledge/04_mobilfunk.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-MOBILFUNK-SELECT", - "title": "Mobilfunk", - "text": "Auswahlziel: Telefonie und Kommunikation \u003e Mobilfunk. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn SIM-Karte, Mobilfunktarif, Mobilfunkvertrag, Empfang, Roaming, mobile Daten oder eine dienstliche Mobilfunkrufnummer betroffen sind. Typische Ticketformulierungen sind: „SIM-Karte gesperrt“; „Kein Mobilfunkempfang“; „Roaming freischalten“; „Mobilfunktarif ändern“; „Neue eSIM“. Nicht auswählen, wenn das Smartphone selbst defekt ist, MFA übertragen werden soll oder eine reine App-/Geräteeinrichtung ohne Mobilfunkbezug vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Kaufmännische Vertragsänderungen erfolgen in Abstimmung mit Beschaffung.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Mobilfunk“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Telefonie und Kommunikation \u003e Mobilfunk" - ], - "keywords": [ - "Mobilfunk", - "SIM-Karte", - "eSIM", - "Roaming", - "mobile Daten", - "Mobilfunkvertrag", - "Empfang", - "PIN", - "PUK", - "Handynummer", - "Telefonie und Kommunikation" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/mobilfunk", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-MOBILFUNK-SELECT", + "title": "Mobilfunk", + "text": "Auswahlziel: Telefonie und Kommunikation \u003e Mobilfunk. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn SIM-Karte, Mobilfunktarif, Mobilfunkvertrag, Empfang, Roaming, mobile Daten oder eine dienstliche Mobilfunkrufnummer betroffen sind. Typische Ticketformulierungen sind: „SIM-Karte gesperrt“; „Kein Mobilfunkempfang“; „Roaming freischalten“; „Mobilfunktarif ändern“; „Neue eSIM“. Nicht auswählen, wenn das Smartphone selbst defekt ist, MFA übertragen werden soll oder eine reine App-/Geräteeinrichtung ohne Mobilfunkbezug vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Kaufmännische Vertragsänderungen erfolgen in Abstimmung mit Beschaffung.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Mobilfunk“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Telefonie und Kommunikation \u003e Mobilfunk" + ], + "keywords": [ + "Mobilfunk", + "SIM-Karte", + "eSIM", + "Roaming", + "mobile Daten", + "Mobilfunkvertrag", + "Empfang", + "PIN", + "PUK", + "Handynummer", + "Telefonie und Kommunikation" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/mobilfunk", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/04_scanner.json b/knowledge/04_scanner.json index 2ae499d..7ed40ae 100644 --- a/knowledge/04_scanner.json +++ b/knowledge/04_scanner.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-SCANNER-SELECT", - "title": "Scanner", - "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Scanner. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Arbeitsplatz- oder Dokumentenscanner nicht erkannt wird, nicht scannt, Einzugsprobleme zeigt oder die lokale Scansoftware fehlerhaft ist. Typische Ticketformulierungen sind: „Scanner wird nicht erkannt“; „Dokumenteneinzug fehlerhaft“; „Scanprogramm startet nicht“; „Scandatei wird nicht erzeugt“. Nicht auswählen, wenn die zentrale Übertragung per Scan-to-Mail oder Scan-to-Folder scheitert oder ein Multifunktionsgerät insgesamt betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Übertragungsziele und zentrale Dienste werden in Scan-to-Mail und Scan-to-Folder erfasst.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Scanner“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Drucken, Scannen und Kopieren \u003e Scanner" - ], - "keywords": [ - "Scanner", - "Scannen", - "Dokumentenscanner", - "Einzug", - "Scanprogramm", - "TWAIN", - "WIA", - "Scanfehler", - "Drucken, Scannen und Kopieren" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/scanner", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-SCANNER-SELECT", + "title": "Scanner", + "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Scanner. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Arbeitsplatz- oder Dokumentenscanner nicht erkannt wird, nicht scannt, Einzugsprobleme zeigt oder die lokale Scansoftware fehlerhaft ist. Typische Ticketformulierungen sind: „Scanner wird nicht erkannt“; „Dokumenteneinzug fehlerhaft“; „Scanprogramm startet nicht“; „Scandatei wird nicht erzeugt“. Nicht auswählen, wenn die zentrale Übertragung per Scan-to-Mail oder Scan-to-Folder scheitert oder ein Multifunktionsgerät insgesamt betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Übertragungsziele und zentrale Dienste werden in Scan-to-Mail und Scan-to-Folder erfasst.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Scanner“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Drucken, Scannen und Kopieren \u003e Scanner" + ], + "keywords": [ + "Scanner", + "Scannen", + "Dokumentenscanner", + "Einzug", + "Scanprogramm", + "TWAIN", + "WIA", + "Scanfehler", + "Drucken, Scannen und Kopieren" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/scanner", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/04_schwachstelle.json b/knowledge/04_schwachstelle.json index d19e833..645af5d 100644 --- a/knowledge/04_schwachstelle.json +++ b/knowledge/04_schwachstelle.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-SCHWACHSTELLE-SELECT", - "title": "Schwachstelle", - "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Schwachstelle. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn eine bekannte oder vermutete Sicherheitslücke, unsichere Konfiguration, CVE, offene Angriffsfläche oder fehlende Härtung gemeldet, bewertet oder behoben werden soll. Typische Ticketformulierungen sind: „CVE betrifft Server“; „Unsichere TLS-Konfiguration“; „Offener Dienst entdeckt“; „System muss gehärtet werden“. Nicht auswählen, wenn bereits ein Angriff oder Datenabfluss stattgefunden hat, nur ein normales Update geplant ist oder Malware gefunden wurde. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei aktiver Ausnutzung wird daraus ein Sicherheitsvorfall; bei reinem Patchbedarf kann Sicherheitsupdate ergänzend passen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Schwachstelle“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "IT-Sicherheit und Datensicherung \u003e Schwachstelle" - ], - "keywords": [ - "Schwachstelle", - "CVE", - "Vulnerability", - "Sicherheitslücke", - "Härtung", - "Hardening", - "unsichere Konfiguration", - "Exposure", - "IT-Sicherheit und Datensicherung" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/schwachstelle", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-SCHWACHSTELLE-SELECT", + "title": "Schwachstelle", + "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Schwachstelle. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn eine bekannte oder vermutete Sicherheitslücke, unsichere Konfiguration, CVE, offene Angriffsfläche oder fehlende Härtung gemeldet, bewertet oder behoben werden soll. Typische Ticketformulierungen sind: „CVE betrifft Server“; „Unsichere TLS-Konfiguration“; „Offener Dienst entdeckt“; „System muss gehärtet werden“. Nicht auswählen, wenn bereits ein Angriff oder Datenabfluss stattgefunden hat, nur ein normales Update geplant ist oder Malware gefunden wurde. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei aktiver Ausnutzung wird daraus ein Sicherheitsvorfall; bei reinem Patchbedarf kann Sicherheitsupdate ergänzend passen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Schwachstelle“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "IT-Sicherheit und Datensicherung \u003e Schwachstelle" + ], + "keywords": [ + "Schwachstelle", + "CVE", + "Vulnerability", + "Sicherheitslücke", + "Härtung", + "Hardening", + "unsichere Konfiguration", + "Exposure", + "IT-Sicherheit und Datensicherung" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/schwachstelle", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/04_serverbetrieb.json b/knowledge/04_serverbetrieb.json index d0e9a9f..bfde3e5 100644 --- a/knowledge/04_serverbetrieb.json +++ b/knowledge/04_serverbetrieb.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-SERVERBETRIEB-SELECT", - "title": "Serverbetrieb", - "text": "Auswahlziel: Server, Backend und Plattformen \u003e Serverbetrieb. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn ein Windows- oder Linux-Server als Betriebssystemplattform ausfällt, gewartet, gepatcht, konfiguriert oder analysiert werden muss. Typische Ticketformulierungen sind: „Server nicht erreichbar“; „Linux-Dienst startet nicht“; „Windows Server patchen“; „Serverleistung analysieren“. Nicht auswählen, wenn ausschließlich eine darauf laufende Fachanwendung, virtuelle Maschine, Datenbank, Containerplattform oder ein Arbeitsplatz-PC betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Die konkrete Anwendungsebene wird getrennt klassifiziert; diese Kategorie betrifft die Serverplattform selbst.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Serverbetrieb“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Server, Backend und Plattformen \u003e Serverbetrieb" - ], - "keywords": [ - "Server", - "Windows Server", - "Linux Server", - "Dienst", - "Systemdienst", - "Patchen", - "Serverausfall", - "Betriebssystem Server", - "Serverbetrieb", - "Server, Backend und Plattformen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/serverbetrieb", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-SERVERBETRIEB-SELECT", + "title": "Serverbetrieb", + "text": "Auswahlziel: Server, Backend und Plattformen \u003e Serverbetrieb. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn ein Windows- oder Linux-Server als Betriebssystemplattform ausfällt, gewartet, gepatcht, konfiguriert oder analysiert werden muss. Typische Ticketformulierungen sind: „Server nicht erreichbar“; „Linux-Dienst startet nicht“; „Windows Server patchen“; „Serverleistung analysieren“. Nicht auswählen, wenn ausschließlich eine darauf laufende Fachanwendung, virtuelle Maschine, Datenbank, Containerplattform oder ein Arbeitsplatz-PC betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Die konkrete Anwendungsebene wird getrennt klassifiziert; diese Kategorie betrifft die Serverplattform selbst.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Serverbetrieb“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Server, Backend und Plattformen \u003e Serverbetrieb" + ], + "keywords": [ + "Server", + "Windows Server", + "Linux Server", + "Dienst", + "Systemdienst", + "Patchen", + "Serverausfall", + "Betriebssystem Server", + "Serverbetrieb", + "Server, Backend und Plattformen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/serverbetrieb", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/04_standortanbindung.json b/knowledge/04_standortanbindung.json index adde936..d024f47 100644 --- a/knowledge/04_standortanbindung.json +++ b/knowledge/04_standortanbindung.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-STANDORTANBINDUNG-SELECT", - "title": "Standortanbindung", - "text": "Auswahlziel: Netzwerk und Verbindungen \u003e Standortanbindung. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn eine Außenstelle, Schule, Verwaltungsstelle oder ein gesamtes Gebäude keine Verbindung zum kommunalen Netz oder Rechenzentrum hat beziehungsweise eine neue Standortverbindung benötigt. Typische Ticketformulierungen sind: „Außenstelle nicht erreichbar“; „Standortverbindung ausgefallen“; „Neue Liegenschaft anbinden“; „Gesamte Schule ohne Verwaltungsnetz“. Nicht auswählen, wenn nur ein einzelner Arbeitsplatz oder eine einzelne Netzwerkdose betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Standortausfälle betreffen meist mehrere Nutzer und sind höher zu priorisieren als Einzelplatzprobleme.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e Standortanbindung“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Netzwerk und Verbindungen \u003e Standortanbindung" - ], - "keywords": [ - "Standortanbindung", - "Außenstelle", - "Liegenschaft", - "Gebäude offline", - "Standleitung", - "WAN", - "Standortverbindung", - "MPLS", - "Glasfaser", - "Netzwerk und Verbindungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/standortanbindung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-STANDORTANBINDUNG-SELECT", + "title": "Standortanbindung", + "text": "Auswahlziel: Netzwerk und Verbindungen \u003e Standortanbindung. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn eine Außenstelle, Schule, Verwaltungsstelle oder ein gesamtes Gebäude keine Verbindung zum kommunalen Netz oder Rechenzentrum hat beziehungsweise eine neue Standortverbindung benötigt. Typische Ticketformulierungen sind: „Außenstelle nicht erreichbar“; „Standortverbindung ausgefallen“; „Neue Liegenschaft anbinden“; „Gesamte Schule ohne Verwaltungsnetz“. Nicht auswählen, wenn nur ein einzelner Arbeitsplatz oder eine einzelne Netzwerkdose betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Standortausfälle betreffen meist mehrere Nutzer und sind höher zu priorisieren als Einzelplatzprobleme.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e Standortanbindung“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Netzwerk und Verbindungen \u003e Standortanbindung" + ], + "keywords": [ + "Standortanbindung", + "Außenstelle", + "Liegenschaft", + "Gebäude offline", + "Standleitung", + "WAN", + "Standortverbindung", + "MPLS", + "Glasfaser", + "Netzwerk und Verbindungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/standortanbindung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/04_standorteroffnung-und-umzug.json b/knowledge/04_standorteroffnung-und-umzug.json index c19984e..5a2975f 100644 --- a/knowledge/04_standorteroffnung-und-umzug.json +++ b/knowledge/04_standorteroffnung-und-umzug.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-STANDORTEROFFNUNG-UND-UMZUG-SELECT", - "title": "Standorteröffnung und Umzug", - "text": "Auswahlziel: Projekte, Änderungen und neue Anforderungen \u003e Standorteröffnung und Umzug. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn eine neue Liegenschaft, Außenstelle, Schule oder ein größerer Gebäudebereich IT-seitig ausgestattet, angebunden oder vollständig umgezogen werden soll. Typische Ticketformulierungen sind: „Neue Außenstelle ausstatten“; „Verwaltungsbereich zieht um“; „Neues Gebäude ans Netz anbinden“; „Kompletter Standortwechsel“. Nicht auswählen, wenn nur ein einzelner Arbeitsplatz innerhalb eines Gebäudes umgesetzt oder eine bestehende Standortleitung gestört ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Die Leitung koordiniert; Infrastruktur, Support, Fachanwendungen, Telekommunikation und Beschaffung liefern Teilaufgaben.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Projekte, Änderungen und neue Anforderungen \u003e Standorteröffnung und Umzug“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Projekte, Änderungen und neue Anforderungen \u003e Standorteröffnung und Umzug" - ], - "keywords": [ - "Standorteröffnung", - "Standortumzug", - "neue Liegenschaft", - "Außenstelle", - "Gebäudeumzug", - "IT-Ausstattung Standort", - "Umzugsprojekt", - "Standorteröffnung und Umzug", - "Projekte, Änderungen und neue Anforderungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/projekte-anderungen-und-neue-anforderungen/standorteroffnung-und-umzug", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-STANDORTEROFFNUNG-UND-UMZUG-SELECT", + "title": "Standorteröffnung und Umzug", + "text": "Auswahlziel: Projekte, Änderungen und neue Anforderungen \u003e Standorteröffnung und Umzug. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn eine neue Liegenschaft, Außenstelle, Schule oder ein größerer Gebäudebereich IT-seitig ausgestattet, angebunden oder vollständig umgezogen werden soll. Typische Ticketformulierungen sind: „Neue Außenstelle ausstatten“; „Verwaltungsbereich zieht um“; „Neues Gebäude ans Netz anbinden“; „Kompletter Standortwechsel“. Nicht auswählen, wenn nur ein einzelner Arbeitsplatz innerhalb eines Gebäudes umgesetzt oder eine bestehende Standortleitung gestört ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Die Leitung koordiniert; Infrastruktur, Support, Fachanwendungen, Telekommunikation und Beschaffung liefern Teilaufgaben.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Projekte, Änderungen und neue Anforderungen \u003e Standorteröffnung und Umzug“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Projekte, Änderungen und neue Anforderungen \u003e Standorteröffnung und Umzug" + ], + "keywords": [ + "Standorteröffnung", + "Standortumzug", + "neue Liegenschaft", + "Außenstelle", + "Gebäudeumzug", + "IT-Ausstattung Standort", + "Umzugsprojekt", + "Standorteröffnung und Umzug", + "Projekte, Änderungen und neue Anforderungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/projekte-anderungen-und-neue-anforderungen/standorteroffnung-und-umzug", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/05_dienstliches-smartphone.json b/knowledge/05_dienstliches-smartphone.json index d544846..a88ab00 100644 --- a/knowledge/05_dienstliches-smartphone.json +++ b/knowledge/05_dienstliches-smartphone.json @@ -1,25 +1,25 @@ -{ - "id": "KAT-DIENSTLICHES-SMARTPHONE-SELECT", - "title": "Dienstliches Smartphone", - "text": "Auswahlziel: Telefonie und Kommunikation \u003e Dienstliches Smartphone. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein dienstliches Smartphone als Telekommunikationsgerät neu eingerichtet, getauscht, repariert oder für Telefonie, Kontakte und dienstliche Kommunikation konfiguriert werden muss. Typische Ticketformulierungen sind: „Diensthandy einrichten“; „Smartphone austauschen“; „Kontakte synchronisieren“; „Telefon-App funktioniert nicht“. Nicht auswählen, wenn ausschließlich SIM, Tarif oder Roaming betroffen ist; wenn ein Tablet ohne Telefoniefunktion oder ein MFA-Token im Mittelpunkt steht. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Allgemeine mobile Endgeräteprobleme können zunächst beim Support bleiben; Mobilfunkvertrag und SIM sind separat.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Dienstliches Smartphone“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Telefonie und Kommunikation \u003e Dienstliches Smartphone" - ], - "keywords": [ - "Dienstliches Smartphone", - "Diensthandy", - "Telefon-App", - "Kontakte", - "Smartphone einrichten", - "Handytausch", - "Mobiltelefon", - "Telefonie und Kommunikation" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/dienstliches-smartphone", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-DIENSTLICHES-SMARTPHONE-SELECT", + "title": "Dienstliches Smartphone", + "text": "Auswahlziel: Telefonie und Kommunikation \u003e Dienstliches Smartphone. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein dienstliches Smartphone als Telekommunikationsgerät neu eingerichtet, getauscht, repariert oder für Telefonie, Kontakte und dienstliche Kommunikation konfiguriert werden muss. Typische Ticketformulierungen sind: „Diensthandy einrichten“; „Smartphone austauschen“; „Kontakte synchronisieren“; „Telefon-App funktioniert nicht“. Nicht auswählen, wenn ausschließlich SIM, Tarif oder Roaming betroffen ist; wenn ein Tablet ohne Telefoniefunktion oder ein MFA-Token im Mittelpunkt steht. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Allgemeine mobile Endgeräteprobleme können zunächst beim Support bleiben; Mobilfunkvertrag und SIM sind separat.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Dienstliches Smartphone“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Telefonie und Kommunikation \u003e Dienstliches Smartphone" + ], + "keywords": [ + "Dienstliches Smartphone", + "Diensthandy", + "Telefon-App", + "Kontakte", + "Smartphone einrichten", + "Handytausch", + "Mobiltelefon", + "Telefonie und Kommunikation" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/dienstliches-smartphone", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/05_digitalisierungsvorhaben.json b/knowledge/05_digitalisierungsvorhaben.json index b5dfa19..e2dbc3c 100644 --- a/knowledge/05_digitalisierungsvorhaben.json +++ b/knowledge/05_digitalisierungsvorhaben.json @@ -1,25 +1,25 @@ -{ - "id": "KAT-DIGITALISIERUNGSVORHABEN-SELECT", - "title": "Digitalisierungsvorhaben", - "text": "Auswahlziel: Projekte, Änderungen und neue Anforderungen \u003e Digitalisierungsvorhaben. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn ein Verwaltungsprozess grundlegend digitalisiert, medienbruchfrei gestaltet oder durch mehrere IT-Komponenten neu unterstützt werden soll. Typische Ticketformulierungen sind: „Papierprozess digitalisieren“; „Digitalen Antrag einführen“; „Medienbruch beseitigen“; „End-to-End-Prozess neu gestalten“. Nicht auswählen, wenn lediglich eine kleine Funktion in einer bestehenden Anwendung ergänzt oder ein einzelnes Gerät beschafft wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Fachanwendungen übernimmt fachnahe Umsetzung; Leitung priorisiert und koordiniert organisationsübergreifend.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Projekte, Änderungen und neue Anforderungen \u003e Digitalisierungsvorhaben“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Projekte, Änderungen und neue Anforderungen \u003e Digitalisierungsvorhaben" - ], - "keywords": [ - "Digitalisierungsvorhaben", - "digitaler Prozess", - "Online-Antrag", - "medienbruchfrei", - "Prozessdigitalisierung", - "E-Government", - "Workflow", - "Projekte, Änderungen und neue Anforderungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/projekte-anderungen-und-neue-anforderungen/digitalisierungsvorhaben", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-DIGITALISIERUNGSVORHABEN-SELECT", + "title": "Digitalisierungsvorhaben", + "text": "Auswahlziel: Projekte, Änderungen und neue Anforderungen \u003e Digitalisierungsvorhaben. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn ein Verwaltungsprozess grundlegend digitalisiert, medienbruchfrei gestaltet oder durch mehrere IT-Komponenten neu unterstützt werden soll. Typische Ticketformulierungen sind: „Papierprozess digitalisieren“; „Digitalen Antrag einführen“; „Medienbruch beseitigen“; „End-to-End-Prozess neu gestalten“. Nicht auswählen, wenn lediglich eine kleine Funktion in einer bestehenden Anwendung ergänzt oder ein einzelnes Gerät beschafft wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Fachanwendungen übernimmt fachnahe Umsetzung; Leitung priorisiert und koordiniert organisationsübergreifend.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Projekte, Änderungen und neue Anforderungen \u003e Digitalisierungsvorhaben“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Projekte, Änderungen und neue Anforderungen \u003e Digitalisierungsvorhaben" + ], + "keywords": [ + "Digitalisierungsvorhaben", + "digitaler Prozess", + "Online-Antrag", + "medienbruchfrei", + "Prozessdigitalisierung", + "E-Government", + "Workflow", + "Projekte, Änderungen und neue Anforderungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/projekte-anderungen-und-neue-anforderungen/digitalisierungsvorhaben", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/05_druckertreiber.json b/knowledge/05_druckertreiber.json index d8b6445..b5f2105 100644 --- a/knowledge/05_druckertreiber.json +++ b/knowledge/05_druckertreiber.json @@ -1,25 +1,25 @@ -{ - "id": "KAT-DRUCKERTREIBER-SELECT", - "title": "Druckertreiber", - "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Druckertreiber. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Druckertreiber fehlt, fehlerhaft ist, aktualisiert werden muss oder falsche Papierfächer, Formate und Gerätefunktionen bereitstellt. Typische Ticketformulierungen sind: „Treiber lässt sich nicht installieren“; „Falsches Papierformat“; „Duplexoption fehlt“; „Druckertreiber aktualisieren“. Nicht auswählen, wenn der Drucker physisch defekt, das Netzwerk ausgefallen oder eine zentrale Druckserverplattform gestört ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Zentrale Treiberpakete können eine Abstimmung mit Infrastruktur und Backend erfordern.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Druckertreiber“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Drucken, Scannen und Kopieren \u003e Druckertreiber" - ], - "keywords": [ - "Druckertreiber", - "Treiber", - "Printer Driver", - "Duplex", - "Papierfach", - "Druckerinstallation", - "Treiberfehler", - "Drucken, Scannen und Kopieren" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/druckertreiber", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-DRUCKERTREIBER-SELECT", + "title": "Druckertreiber", + "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Druckertreiber. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Druckertreiber fehlt, fehlerhaft ist, aktualisiert werden muss oder falsche Papierfächer, Formate und Gerätefunktionen bereitstellt. Typische Ticketformulierungen sind: „Treiber lässt sich nicht installieren“; „Falsches Papierformat“; „Duplexoption fehlt“; „Druckertreiber aktualisieren“. Nicht auswählen, wenn der Drucker physisch defekt, das Netzwerk ausgefallen oder eine zentrale Druckserverplattform gestört ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Zentrale Treiberpakete können eine Abstimmung mit Infrastruktur und Backend erfordern.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Druckertreiber“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Drucken, Scannen und Kopieren \u003e Druckertreiber" + ], + "keywords": [ + "Druckertreiber", + "Treiber", + "Printer Driver", + "Duplex", + "Papierfach", + "Druckerinstallation", + "Treiberfehler", + "Drucken, Scannen und Kopieren" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/druckertreiber", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/05_fachanwendung-schnittstelle-und-datenaustausch.json b/knowledge/05_fachanwendung-schnittstelle-und-datenaustausch.json index 9784a76..81f6b0c 100644 --- a/knowledge/05_fachanwendung-schnittstelle-und-datenaustausch.json +++ b/knowledge/05_fachanwendung-schnittstelle-und-datenaustausch.json @@ -1,29 +1,29 @@ -{ - "id": "KAT-FACHANWENDUNG-SCHNITTSTELLE-UND-DATENAUSTAUSCH-SELECT", - "title": "Fachanwendung – Schnittstelle und Datenaustausch", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Schnittstelle und Datenaustausch. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Import, Export, Webservice, Dateiübergabe oder Datenaustausch zwischen einer Fachanwendung und einem anderen System fehlschlägt oder neu eingerichtet werden soll. Typische Ticketformulierungen sind: „Importdatei wird abgewiesen“; „Export kommt nicht im Zielsystem an“; „Schnittstelle liefert Fehler“; „Datenaustausch einrichten“. Nicht auswählen, wenn nur die Netzwerkverbindung eines Standorts gestört ist, ein allgemeiner Dateiablagefehler vorliegt oder eine rein manuelle Auswertung benötigt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei Firewall- oder Netzwerkfreischaltungen arbeitet Fachanwendungen mit Infrastruktur und Backend zusammen; fachliche Datenformate bleiben bei Fachanwendungen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Schnittstelle und Datenaustausch“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Schnittstelle und Datenaustausch" - ], - "keywords": [ - "Schnittstelle", - "Datenaustausch", - "Import", - "Export", - "Webservice", - "API", - "Dateiübergabe", - "Interface", - "Übertragung", - "Fremdsystem", - "Fachanwendung – Schnittstelle und Datenaustausch", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-schnittstelle-und-datenaustausch", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-FACHANWENDUNG-SCHNITTSTELLE-UND-DATENAUSTAUSCH-SELECT", + "title": "Fachanwendung – Schnittstelle und Datenaustausch", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Schnittstelle und Datenaustausch. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Import, Export, Webservice, Dateiübergabe oder Datenaustausch zwischen einer Fachanwendung und einem anderen System fehlschlägt oder neu eingerichtet werden soll. Typische Ticketformulierungen sind: „Importdatei wird abgewiesen“; „Export kommt nicht im Zielsystem an“; „Schnittstelle liefert Fehler“; „Datenaustausch einrichten“. Nicht auswählen, wenn nur die Netzwerkverbindung eines Standorts gestört ist, ein allgemeiner Dateiablagefehler vorliegt oder eine rein manuelle Auswertung benötigt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei Firewall- oder Netzwerkfreischaltungen arbeitet Fachanwendungen mit Infrastruktur und Backend zusammen; fachliche Datenformate bleiben bei Fachanwendungen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Schnittstelle und Datenaustausch“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Schnittstelle und Datenaustausch" + ], + "keywords": [ + "Schnittstelle", + "Datenaustausch", + "Import", + "Export", + "Webservice", + "API", + "Dateiübergabe", + "Interface", + "Übertragung", + "Fremdsystem", + "Fachanwendung – Schnittstelle und Datenaustausch", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-schnittstelle-und-datenaustausch", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/05_hypervisor.json b/knowledge/05_hypervisor.json index 217c7bc..c2c13ff 100644 --- a/knowledge/05_hypervisor.json +++ b/knowledge/05_hypervisor.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-HYPERVISOR-SELECT", - "title": "Hypervisor", - "text": "Auswahlziel: Server, Backend und Plattformen \u003e Hypervisor. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn die Virtualisierungsplattform, Hosts, Cluster, Ressourcenverwaltung oder zentrale Hypervisor-Funktionen gestört, erweitert oder gewartet werden müssen. Typische Ticketformulierungen sind: „VMware-Host gestört“; „Hyper-V-Cluster meldet Fehler“; „Virtualisierungshost warten“; „Clusterressourcen knapp“. Nicht auswählen, wenn nur eine einzelne virtuelle Maschine betroffen ist oder ein Container- beziehungsweise Kubernetes-Problem vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Einzelne VMs werden unter Virtuelle Maschinen erfasst; die Trägerschicht unter Hypervisor.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Hypervisor“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Server, Backend und Plattformen \u003e Hypervisor" - ], - "keywords": [ - "Hypervisor", - "VMware", - "vSphere", - "ESXi", - "Hyper-V", - "Virtualisierung", - "Host", - "Cluster", - "Proxmox", - "Server, Backend und Plattformen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/hypervisor", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-HYPERVISOR-SELECT", + "title": "Hypervisor", + "text": "Auswahlziel: Server, Backend und Plattformen \u003e Hypervisor. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn die Virtualisierungsplattform, Hosts, Cluster, Ressourcenverwaltung oder zentrale Hypervisor-Funktionen gestört, erweitert oder gewartet werden müssen. Typische Ticketformulierungen sind: „VMware-Host gestört“; „Hyper-V-Cluster meldet Fehler“; „Virtualisierungshost warten“; „Clusterressourcen knapp“. Nicht auswählen, wenn nur eine einzelne virtuelle Maschine betroffen ist oder ein Container- beziehungsweise Kubernetes-Problem vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Einzelne VMs werden unter Virtuelle Maschinen erfasst; die Trägerschicht unter Hypervisor.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Hypervisor“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Server, Backend und Plattformen \u003e Hypervisor" + ], + "keywords": [ + "Hypervisor", + "VMware", + "vSphere", + "ESXi", + "Hyper-V", + "Virtualisierung", + "Host", + "Cluster", + "Proxmox", + "Server, Backend und Plattformen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/hypervisor", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/05_remotezugriff-und-vpn.json b/knowledge/05_remotezugriff-und-vpn.json index dba98fb..ccf8ca6 100644 --- a/knowledge/05_remotezugriff-und-vpn.json +++ b/knowledge/05_remotezugriff-und-vpn.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-REMOTEZUGRIFF-UND-VPN-SELECT", - "title": "Remotezugriff und VPN", - "text": "Auswahlziel: Benutzerkonten und Berechtigungen \u003e Remotezugriff und VPN. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn ein neuer oder geänderter Fernzugriff auf interne Systeme benötigt wird, ein VPN-Zugang beantragt werden soll oder Zugriffsrechte für Homeoffice, Bereitschaft oder externe Administration eingerichtet werden müssen. Typische Ticketformulierungen sind: „VPN-Zugang beantragen“; „Homeoffice-Zugriff freischalten“; „Remotezugriff für Bereitschaft“; „Externer Dienstleister benötigt zeitlich begrenzten Zugang“. Nicht auswählen, wenn ein bereits eingerichteter VPN-Tunnel technisch nicht verbindet; dafür ist die Kategorie Netzwerk und Verbindungen \u003e VPN vorgesehen. Ein allgemeines Kennwortproblem gehört zu Kennwort zurücksetzen. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Diese Kategorie beschreibt die Berechtigung beziehungsweise Bereitstellung. Technische Verbindungsstörungen eines vorhandenen VPN werden unter Netzwerk \u003e VPN erfasst.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Benutzerkonten und Berechtigungen \u003e Remotezugriff und VPN“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Benutzerkonten und Berechtigungen \u003e Remotezugriff und VPN" - ], - "keywords": [ - "Remotezugriff", - "Fernzugriff", - "VPN-Zugang", - "Homeoffice", - "Remote Access", - "Zugriff von außen", - "Bereitschaft", - "externer Zugriff", - "Freischaltung VPN", - "Remotezugriff und VPN", - "Benutzerkonten und Berechtigungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/benutzerkonten-und-berechtigungen/remotezugriff-und-vpn", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-REMOTEZUGRIFF-UND-VPN-SELECT", + "title": "Remotezugriff und VPN", + "text": "Auswahlziel: Benutzerkonten und Berechtigungen \u003e Remotezugriff und VPN. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn ein neuer oder geänderter Fernzugriff auf interne Systeme benötigt wird, ein VPN-Zugang beantragt werden soll oder Zugriffsrechte für Homeoffice, Bereitschaft oder externe Administration eingerichtet werden müssen. Typische Ticketformulierungen sind: „VPN-Zugang beantragen“; „Homeoffice-Zugriff freischalten“; „Remotezugriff für Bereitschaft“; „Externer Dienstleister benötigt zeitlich begrenzten Zugang“. Nicht auswählen, wenn ein bereits eingerichteter VPN-Tunnel technisch nicht verbindet; dafür ist die Kategorie Netzwerk und Verbindungen \u003e VPN vorgesehen. Ein allgemeines Kennwortproblem gehört zu Kennwort zurücksetzen. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Diese Kategorie beschreibt die Berechtigung beziehungsweise Bereitstellung. Technische Verbindungsstörungen eines vorhandenen VPN werden unter Netzwerk \u003e VPN erfasst.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Benutzerkonten und Berechtigungen \u003e Remotezugriff und VPN“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Benutzerkonten und Berechtigungen \u003e Remotezugriff und VPN" + ], + "keywords": [ + "Remotezugriff", + "Fernzugriff", + "VPN-Zugang", + "Homeoffice", + "Remote Access", + "Zugriff von außen", + "Bereitschaft", + "externer Zugriff", + "Freischaltung VPN", + "Remotezugriff und VPN", + "Benutzerkonten und Berechtigungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/benutzerkonten-und-berechtigungen/remotezugriff-und-vpn", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/05_schuler-und-lehrkraftekonten.json b/knowledge/05_schuler-und-lehrkraftekonten.json index 8ae93ab..ba10cd3 100644 --- a/knowledge/05_schuler-und-lehrkraftekonten.json +++ b/knowledge/05_schuler-und-lehrkraftekonten.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-SCHULER-UND-LEHRKRAFTEKONTEN-SELECT", - "title": "Schüler- und Lehrkräftekonten", - "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Schüler- und Lehrkräftekonten. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn Konten für Schüler, Lehrkräfte oder schulische Gruppen angelegt, geändert, zurückgesetzt, synchronisiert oder deaktiviert werden müssen. Typische Ticketformulierungen sind: „Schülerpasswort zurücksetzen“; „Lehrkraftkonto anlegen“; „Klassenwechsel synchronisieren“; „Schülerkonto deaktivieren“. Nicht auswählen, wenn es um kommunale Verwaltungsaccounts, reine Fachanwendungsrollen oder Konten externer Anbieter ohne Schulbezug geht. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei zentralem AD- oder Identitätsplattformproblem arbeitet Team Schulen mit Infrastruktur und Backend zusammen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Schüler- und Lehrkräftekonten“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Schulspezifische IT-Systeme \u003e Schüler- und Lehrkräftekonten" - ], - "keywords": [ - "Schülerkonto", - "Lehrerkonto", - "Lehrkräftekonto", - "Schulaccount", - "Klassenkonto", - "Passwort Schule", - "Kontensynchronisation", - "Schüler- und Lehrkräftekonten", - "Schulspezifische IT-Systeme" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/schuler-und-lehrkraftekonten", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-SCHULER-UND-LEHRKRAFTEKONTEN-SELECT", + "title": "Schüler- und Lehrkräftekonten", + "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Schüler- und Lehrkräftekonten. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn Konten für Schüler, Lehrkräfte oder schulische Gruppen angelegt, geändert, zurückgesetzt, synchronisiert oder deaktiviert werden müssen. Typische Ticketformulierungen sind: „Schülerpasswort zurücksetzen“; „Lehrkraftkonto anlegen“; „Klassenwechsel synchronisieren“; „Schülerkonto deaktivieren“. Nicht auswählen, wenn es um kommunale Verwaltungsaccounts, reine Fachanwendungsrollen oder Konten externer Anbieter ohne Schulbezug geht. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei zentralem AD- oder Identitätsplattformproblem arbeitet Team Schulen mit Infrastruktur und Backend zusammen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Schüler- und Lehrkräftekonten“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Schulspezifische IT-Systeme \u003e Schüler- und Lehrkräftekonten" + ], + "keywords": [ + "Schülerkonto", + "Lehrerkonto", + "Lehrkräftekonto", + "Schulaccount", + "Klassenkonto", + "Passwort Schule", + "Kontensynchronisation", + "Schüler- und Lehrkräftekonten", + "Schulspezifische IT-Systeme" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/schuler-und-lehrkraftekonten", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/05_softwareinstallation-am-arbeitsplatz.json b/knowledge/05_softwareinstallation-am-arbeitsplatz.json index 27bbac2..0b03c12 100644 --- a/knowledge/05_softwareinstallation-am-arbeitsplatz.json +++ b/knowledge/05_softwareinstallation-am-arbeitsplatz.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-SOFTWAREINSTALLATION-AM-ARBEITSPLATZ-SELECT", - "title": "Softwareinstallation am Arbeitsplatz", - "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Softwareinstallation am Arbeitsplatz. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn freigegebene Standardsoftware auf einem einzelnen Arbeitsplatz installiert, aktualisiert, repariert oder deinstalliert werden soll und keine zentrale Plattformänderung erforderlich ist. Typische Ticketformulierungen sind: „PDF-Programm installieren“; „Freigegebene Software fehlt“; „Client-Anwendung neu installieren“; „Programm deinstallieren“. Nicht auswählen, wenn eine neue, bisher nicht freigegebene Software beschafft oder fachlich eingeführt werden soll; wenn ein Server, Container oder eine größere Clientverteilung betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Neue oder lizenzpflichtige Software wird zunächst über Beschaffung beziehungsweise Fachanwendungen geprüft.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Softwareinstallation am Arbeitsplatz“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Arbeitsplatz und Endgeräte \u003e Softwareinstallation am Arbeitsplatz" - ], - "keywords": [ - "Softwareinstallation", - "Programm installieren", - "Anwendung installieren", - "Client", - "Setup", - "Deinstallation", - "Standardsoftware", - "Software fehlt", - "Neuinstallation", - "Softwareinstallation am Arbeitsplatz", - "Arbeitsplatz und Endgeräte" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/softwareinstallation-am-arbeitsplatz", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-SOFTWAREINSTALLATION-AM-ARBEITSPLATZ-SELECT", + "title": "Softwareinstallation am Arbeitsplatz", + "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Softwareinstallation am Arbeitsplatz. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn freigegebene Standardsoftware auf einem einzelnen Arbeitsplatz installiert, aktualisiert, repariert oder deinstalliert werden soll und keine zentrale Plattformänderung erforderlich ist. Typische Ticketformulierungen sind: „PDF-Programm installieren“; „Freigegebene Software fehlt“; „Client-Anwendung neu installieren“; „Programm deinstallieren“. Nicht auswählen, wenn eine neue, bisher nicht freigegebene Software beschafft oder fachlich eingeführt werden soll; wenn ein Server, Container oder eine größere Clientverteilung betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Neue oder lizenzpflichtige Software wird zunächst über Beschaffung beziehungsweise Fachanwendungen geprüft.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Softwareinstallation am Arbeitsplatz“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Arbeitsplatz und Endgeräte \u003e Softwareinstallation am Arbeitsplatz" + ], + "keywords": [ + "Softwareinstallation", + "Programm installieren", + "Anwendung installieren", + "Client", + "Setup", + "Deinstallation", + "Standardsoftware", + "Software fehlt", + "Neuinstallation", + "Softwareinstallation am Arbeitsplatz", + "Arbeitsplatz und Endgeräte" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/softwareinstallation-am-arbeitsplatz", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/05_telekommunikationsvertrag.json b/knowledge/05_telekommunikationsvertrag.json index f2326c1..737ee8c 100644 --- a/knowledge/05_telekommunikationsvertrag.json +++ b/knowledge/05_telekommunikationsvertrag.json @@ -1,25 +1,25 @@ -{ - "id": "KAT-TELEKOMMUNIKATIONSVERTRAG-SELECT", - "title": "Telekommunikationsvertrag", - "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Telekommunikationsvertrag. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn Mobilfunk-, Festnetz-, Datenleitungs- oder sonstige Telekommunikationsverträge neu abgeschlossen, verlängert, angepasst oder gekündigt werden sollen. Typische Ticketformulierungen sind: „Mobilfunkvertrag verlängern“; „Festnetzvertrag kündigen“; „Datentarif anpassen“; „Providerangebot prüfen“. Nicht auswählen, wenn eine technische Telefonstörung, SIM-Sperre oder Rufumleitung betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Bedarfe werden mit Support und Infrastruktur abgestimmt.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Telekommunikationsvertrag“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Beschaffung, Verträge und Lizenzen \u003e Telekommunikationsvertrag" - ], - "keywords": [ - "Telekommunikationsvertrag", - "Mobilfunkvertrag", - "Festnetzvertrag", - "Providervertrag", - "Tarif", - "Vertragsverlängerung", - "Kündigung", - "Beschaffung, Verträge und Lizenzen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/telekommunikationsvertrag", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-TELEKOMMUNIKATIONSVERTRAG-SELECT", + "title": "Telekommunikationsvertrag", + "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Telekommunikationsvertrag. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn Mobilfunk-, Festnetz-, Datenleitungs- oder sonstige Telekommunikationsverträge neu abgeschlossen, verlängert, angepasst oder gekündigt werden sollen. Typische Ticketformulierungen sind: „Mobilfunkvertrag verlängern“; „Festnetzvertrag kündigen“; „Datentarif anpassen“; „Providerangebot prüfen“. Nicht auswählen, wenn eine technische Telefonstörung, SIM-Sperre oder Rufumleitung betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Bedarfe werden mit Support und Infrastruktur abgestimmt.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Telekommunikationsvertrag“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Beschaffung, Verträge und Lizenzen \u003e Telekommunikationsvertrag" + ], + "keywords": [ + "Telekommunikationsvertrag", + "Mobilfunkvertrag", + "Festnetzvertrag", + "Providervertrag", + "Tarif", + "Vertragsverlängerung", + "Kündigung", + "Beschaffung, Verträge und Lizenzen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/telekommunikationsvertrag", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/05_virenschutz.json b/knowledge/05_virenschutz.json index ca79ebb..2565444 100644 --- a/knowledge/05_virenschutz.json +++ b/knowledge/05_virenschutz.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-VIRENSCHUTZ-SELECT", - "title": "Virenschutz", - "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Virenschutz. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Virenscanner oder EDR-Agent nicht läuft, Signaturen veraltet sind, Richtlinien nicht greifen, ein Agent fehlt oder Quarantäne- und Ausnahmeregeln administriert werden müssen. Typische Ticketformulierungen sind: „Virenscanner nicht aktiv“; „Signaturen veraltet“; „EDR-Agent offline“; „Ausnahme prüfen“; „Quarantäne verwalten“. Nicht auswählen, wenn bereits Malware gefunden wurde oder ein allgemeines Betriebssystemupdate betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Ein konkreter Schadsoftwarefund wird unter Schadsoftware und Virenfund klassifiziert.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Virenschutz“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "IT-Sicherheit und Datensicherung \u003e Virenschutz" - ], - "keywords": [ - "Virenscanner", - "Antivirus", - "EDR", - "Signatur", - "Agent", - "Quarantäne", - "Ausnahme", - "Schutzstatus", - "Defender", - "Virenschutz", - "IT-Sicherheit und Datensicherung" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/virenschutz", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-VIRENSCHUTZ-SELECT", + "title": "Virenschutz", + "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Virenschutz. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Virenscanner oder EDR-Agent nicht läuft, Signaturen veraltet sind, Richtlinien nicht greifen, ein Agent fehlt oder Quarantäne- und Ausnahmeregeln administriert werden müssen. Typische Ticketformulierungen sind: „Virenscanner nicht aktiv“; „Signaturen veraltet“; „EDR-Agent offline“; „Ausnahme prüfen“; „Quarantäne verwalten“. Nicht auswählen, wenn bereits Malware gefunden wurde oder ein allgemeines Betriebssystemupdate betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Ein konkreter Schadsoftwarefund wird unter Schadsoftware und Virenfund klassifiziert.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Virenschutz“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "IT-Sicherheit und Datensicherung \u003e Virenschutz" + ], + "keywords": [ + "Virenscanner", + "Antivirus", + "EDR", + "Signatur", + "Agent", + "Quarantäne", + "Ausnahme", + "Schutzstatus", + "Defender", + "Virenschutz", + "IT-Sicherheit und Datensicherung" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/virenschutz", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/05_vpn.json b/knowledge/05_vpn.json index 6bbeda0..fc07aea 100644 --- a/knowledge/05_vpn.json +++ b/knowledge/05_vpn.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-VPN-SELECT", - "title": "VPN", - "text": "Auswahlziel: Netzwerk und Verbindungen \u003e VPN. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn ein bereits eingerichteter VPN-Tunnel technisch nicht aufgebaut wird, abbricht, keine internen Ziele erreicht oder eine Client- beziehungsweise Gateway-Fehlermeldung zeigt. Typische Ticketformulierungen sind: „VPN verbindet nicht“; „Tunnel bricht ab“; „Interne Laufwerke über VPN nicht erreichbar“; „VPN-Client meldet Fehler“. Nicht auswählen, wenn der VPN-Zugang erstmals beantragt oder berechtigt werden soll; dafür ist Benutzerkonten und Berechtigungen \u003e Remotezugriff und VPN vorgesehen. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Berechtigungsfragen und technische Störungen sind bewusst getrennt, um Fehlzuordnungen zu vermeiden.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e VPN“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Netzwerk und Verbindungen \u003e VPN" - ], - "keywords": [ - "VPN", - "Tunnel", - "VPN-Client", - "Remote Access", - "Verbindung von außen", - "Gateway", - "VPN Fehler", - "Homeoffice Verbindung", - "Netzwerk und Verbindungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/vpn", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-VPN-SELECT", + "title": "VPN", + "text": "Auswahlziel: Netzwerk und Verbindungen \u003e VPN. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn ein bereits eingerichteter VPN-Tunnel technisch nicht aufgebaut wird, abbricht, keine internen Ziele erreicht oder eine Client- beziehungsweise Gateway-Fehlermeldung zeigt. Typische Ticketformulierungen sind: „VPN verbindet nicht“; „Tunnel bricht ab“; „Interne Laufwerke über VPN nicht erreichbar“; „VPN-Client meldet Fehler“. Nicht auswählen, wenn der VPN-Zugang erstmals beantragt oder berechtigt werden soll; dafür ist Benutzerkonten und Berechtigungen \u003e Remotezugriff und VPN vorgesehen. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Berechtigungsfragen und technische Störungen sind bewusst getrennt, um Fehlzuordnungen zu vermeiden.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e VPN“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Netzwerk und Verbindungen \u003e VPN" + ], + "keywords": [ + "VPN", + "Tunnel", + "VPN-Client", + "Remote Access", + "Verbindung von außen", + "Gateway", + "VPN Fehler", + "Homeoffice Verbindung", + "Netzwerk und Verbindungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/vpn", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/06_betriebssystem-am-arbeitsplatz.json b/knowledge/06_betriebssystem-am-arbeitsplatz.json index 1a90a2e..a346ab5 100644 --- a/knowledge/06_betriebssystem-am-arbeitsplatz.json +++ b/knowledge/06_betriebssystem-am-arbeitsplatz.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-BETRIEBSSYSTEM-AM-ARBEITSPLATZ-SELECT", - "title": "Betriebssystem am Arbeitsplatz", - "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Betriebssystem am Arbeitsplatz. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn Windows oder ein anderes Client-Betriebssystem an einem einzelnen Arbeitsplatz lokale Fehler zeigt, Updates scheitern, Anmeldung am Gerät fehlerhaft ist oder Systemeinstellungen repariert werden müssen. Typische Ticketformulierungen sind: „Windows-Update schlägt fehl“; „Benutzerprofil defekt“; „Startmenü funktioniert nicht“; „Lokale Anmeldung fehlerhaft“. Nicht auswählen, wenn die Ursache eine zentrale Gruppenrichtlinie, das Active Directory, eine flächige Update-Störung oder ein Serverbetriebssystem ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Zentrale Richtlinien und mehrere gleichzeitig betroffene Clients werden an Infrastruktur und Backend übergeben.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Betriebssystem am Arbeitsplatz“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Arbeitsplatz und Endgeräte \u003e Betriebssystem am Arbeitsplatz" - ], - "keywords": [ - "Windows", - "Betriebssystem", - "Client", - "Windows Update", - "Benutzerprofil", - "Startmenü", - "lokale Anmeldung", - "Systemfehler", - "Treiberproblem", - "Betriebssystem am Arbeitsplatz", - "Arbeitsplatz und Endgeräte" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/betriebssystem-am-arbeitsplatz", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-BETRIEBSSYSTEM-AM-ARBEITSPLATZ-SELECT", + "title": "Betriebssystem am Arbeitsplatz", + "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Betriebssystem am Arbeitsplatz. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn Windows oder ein anderes Client-Betriebssystem an einem einzelnen Arbeitsplatz lokale Fehler zeigt, Updates scheitern, Anmeldung am Gerät fehlerhaft ist oder Systemeinstellungen repariert werden müssen. Typische Ticketformulierungen sind: „Windows-Update schlägt fehl“; „Benutzerprofil defekt“; „Startmenü funktioniert nicht“; „Lokale Anmeldung fehlerhaft“. Nicht auswählen, wenn die Ursache eine zentrale Gruppenrichtlinie, das Active Directory, eine flächige Update-Störung oder ein Serverbetriebssystem ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Zentrale Richtlinien und mehrere gleichzeitig betroffene Clients werden an Infrastruktur und Backend übergeben.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Betriebssystem am Arbeitsplatz“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Arbeitsplatz und Endgeräte \u003e Betriebssystem am Arbeitsplatz" + ], + "keywords": [ + "Windows", + "Betriebssystem", + "Client", + "Windows Update", + "Benutzerprofil", + "Startmenü", + "lokale Anmeldung", + "Systemfehler", + "Treiberproblem", + "Betriebssystem am Arbeitsplatz", + "Arbeitsplatz und Endgeräte" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/betriebssystem-am-arbeitsplatz", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/06_dns-und-dhcp.json b/knowledge/06_dns-und-dhcp.json index 437105e..d2910bf 100644 --- a/knowledge/06_dns-und-dhcp.json +++ b/knowledge/06_dns-und-dhcp.json @@ -1,29 +1,29 @@ -{ - "id": "KAT-DNS-UND-DHCP-SELECT", - "title": "DNS und DHCP", - "text": "Auswahlziel: Netzwerk und Verbindungen \u003e DNS und DHCP. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Namensauflösung, DNS-Einträge, IP-Adressvergabe, DHCP-Leases, Reservierungen oder damit verbundene zentrale Netzwerkfunktionen fehlerhaft sind oder geändert werden sollen. Typische Ticketformulierungen sind: „Hostname wird nicht aufgelöst“; „Falsche IP-Adresse“; „DHCP-Reservierung anlegen“; „DNS-Eintrag ändern“. Nicht auswählen, wenn lediglich ein Benutzer keine Internetverbindung hat, eine Firewallfreigabe benötigt wird oder eine Fachanwendung einen eigenen Namensfehler meldet. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Einzelne Symptome ohne technische Hinweise sollten zunächst unter LAN, WLAN oder Internetzugang eingeordnet werden.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e DNS und DHCP“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Netzwerk und Verbindungen \u003e DNS und DHCP" - ], - "keywords": [ - "DNS", - "DHCP", - "Namensauflösung", - "IP-Adresse", - "Lease", - "Reservierung", - "Hostname", - "A-Record", - "CNAME", - "IP-Vergabe", - "DNS und DHCP", - "Netzwerk und Verbindungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/dns-und-dhcp", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-DNS-UND-DHCP-SELECT", + "title": "DNS und DHCP", + "text": "Auswahlziel: Netzwerk und Verbindungen \u003e DNS und DHCP. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Namensauflösung, DNS-Einträge, IP-Adressvergabe, DHCP-Leases, Reservierungen oder damit verbundene zentrale Netzwerkfunktionen fehlerhaft sind oder geändert werden sollen. Typische Ticketformulierungen sind: „Hostname wird nicht aufgelöst“; „Falsche IP-Adresse“; „DHCP-Reservierung anlegen“; „DNS-Eintrag ändern“. Nicht auswählen, wenn lediglich ein Benutzer keine Internetverbindung hat, eine Firewallfreigabe benötigt wird oder eine Fachanwendung einen eigenen Namensfehler meldet. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Einzelne Symptome ohne technische Hinweise sollten zunächst unter LAN, WLAN oder Internetzugang eingeordnet werden.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e DNS und DHCP“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Netzwerk und Verbindungen \u003e DNS und DHCP" + ], + "keywords": [ + "DNS", + "DHCP", + "Namensauflösung", + "IP-Adresse", + "Lease", + "Reservierung", + "Hostname", + "A-Record", + "CNAME", + "IP-Vergabe", + "DNS und DHCP", + "Netzwerk und Verbindungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/dns-und-dhcp", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/06_fachanwendung-bericht-und-auswertung.json b/knowledge/06_fachanwendung-bericht-und-auswertung.json index 1b962f6..b8722b5 100644 --- a/knowledge/06_fachanwendung-bericht-und-auswertung.json +++ b/knowledge/06_fachanwendung-bericht-und-auswertung.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-FACHANWENDUNG-BERICHT-UND-AUSWERTUNG-SELECT", - "title": "Fachanwendung – Bericht und Auswertung", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Bericht und Auswertung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn ein vorhandener Bericht, eine Liste, Statistik, Druckausgabe oder fachliche Auswertung fehlerhaft ist, angepasst oder bereitgestellt werden soll. Typische Ticketformulierungen sind: „Bericht zeigt falsche Spalten“; „Statistik fehlt“; „Auswertung anpassen“; „Druckausgabe aus Fachverfahren fehlerhaft“. Nicht auswählen, wenn eine allgemeine Excel-Auswertung ohne Fachanwendungsbezug gemeint ist, ein Drucker physisch nicht druckt oder eine komplett neue Fachfunktion benötigt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Dateninhalt und Filterlogik gehören zu Fachanwendungen; physische Druckprobleme zum Support.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Bericht und Auswertung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Bericht und Auswertung" - ], - "keywords": [ - "Bericht", - "Auswertung", - "Statistik", - "Liste", - "Reporting", - "Druckausgabe", - "Abfrage", - "Kennzahl", - "Fachanwendung Bericht", - "Fachanwendung – Bericht und Auswertung", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-bericht-und-auswertung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-FACHANWENDUNG-BERICHT-UND-AUSWERTUNG-SELECT", + "title": "Fachanwendung – Bericht und Auswertung", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Bericht und Auswertung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn ein vorhandener Bericht, eine Liste, Statistik, Druckausgabe oder fachliche Auswertung fehlerhaft ist, angepasst oder bereitgestellt werden soll. Typische Ticketformulierungen sind: „Bericht zeigt falsche Spalten“; „Statistik fehlt“; „Auswertung anpassen“; „Druckausgabe aus Fachverfahren fehlerhaft“. Nicht auswählen, wenn eine allgemeine Excel-Auswertung ohne Fachanwendungsbezug gemeint ist, ein Drucker physisch nicht druckt oder eine komplett neue Fachfunktion benötigt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Dateninhalt und Filterlogik gehören zu Fachanwendungen; physische Druckprobleme zum Support.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Bericht und Auswertung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Bericht und Auswertung" + ], + "keywords": [ + "Bericht", + "Auswertung", + "Statistik", + "Liste", + "Reporting", + "Druckausgabe", + "Abfrage", + "Kennzahl", + "Fachanwendung Bericht", + "Fachanwendung – Bericht und Auswertung", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-bericht-und-auswertung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/06_mobile-schulgerate.json b/knowledge/06_mobile-schulgerate.json index 449a78f..4d7385a 100644 --- a/knowledge/06_mobile-schulgerate.json +++ b/knowledge/06_mobile-schulgerate.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-MOBILE-SCHULGERATE-SELECT", - "title": "Mobile Schulgeräte", - "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Mobile Schulgeräte. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn schulische Tablets, Notebooks, Leihgeräte oder Gerätekoffer eingerichtet, ausgegeben, zurückgenommen, repariert oder im Unterricht unterstützt werden müssen. Typische Ticketformulierungen sind: „Schüler-iPad defekt“; „Tablet-Koffer einrichten“; „Leihgerät zurücknehmen“; „Schulnotebook startet nicht“. Nicht auswählen, wenn ausschließlich das zentrale MDM, ein privates Gerät oder ein allgemeines Verwaltungs-Smartphone betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: MDM-Profil- und Plattformprobleme werden in Mobile-Device-Management für Schulen erfasst.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Mobile Schulgeräte“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Schulspezifische IT-Systeme \u003e Mobile Schulgeräte" - ], - "keywords": [ - "Schultablet", - "Schul-iPad", - "Leihgerät", - "Tablet-Koffer", - "Schulnotebook", - "mobiles Schulgerät", - "Schülergerät", - "Mobile Schulgeräte", - "Schulspezifische IT-Systeme" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/mobile-schulgerate", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-MOBILE-SCHULGERATE-SELECT", + "title": "Mobile Schulgeräte", + "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Mobile Schulgeräte. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn schulische Tablets, Notebooks, Leihgeräte oder Gerätekoffer eingerichtet, ausgegeben, zurückgenommen, repariert oder im Unterricht unterstützt werden müssen. Typische Ticketformulierungen sind: „Schüler-iPad defekt“; „Tablet-Koffer einrichten“; „Leihgerät zurücknehmen“; „Schulnotebook startet nicht“. Nicht auswählen, wenn ausschließlich das zentrale MDM, ein privates Gerät oder ein allgemeines Verwaltungs-Smartphone betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: MDM-Profil- und Plattformprobleme werden in Mobile-Device-Management für Schulen erfasst.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Mobile Schulgeräte“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Schulspezifische IT-Systeme \u003e Mobile Schulgeräte" + ], + "keywords": [ + "Schultablet", + "Schul-iPad", + "Leihgerät", + "Tablet-Koffer", + "Schulnotebook", + "mobiles Schulgerät", + "Schülergerät", + "Mobile Schulgeräte", + "Schulspezifische IT-Systeme" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/mobile-schulgerate", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/06_scan-to-mail-und-scan-to-folder.json b/knowledge/06_scan-to-mail-und-scan-to-folder.json index e5cc27a..354be22 100644 --- a/knowledge/06_scan-to-mail-und-scan-to-folder.json +++ b/knowledge/06_scan-to-mail-und-scan-to-folder.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-SCAN-TO-MAIL-UND-SCAN-TO-FOLDER-SELECT", - "title": "Scan-to-Mail und Scan-to-Folder", - "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Scan-to-Mail und Scan-to-Folder. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Scanner oder Multifunktionsgerät Scans nicht per E-Mail versendet oder nicht in einen zentralen Ordner ablegt, obwohl das Scannen selbst funktioniert. Typische Ticketformulierungen sind: „Scan kommt nicht per Mail an“; „Scan-to-Folder schlägt fehl“; „Zielordner nicht erreichbar“; „SMTP-Fehler am Kopierer“. Nicht auswählen, wenn das Gerät generell nicht scannt, das E-Mail-System organisationsweit ausfällt oder eine allgemeine Dateifreigabe gestört ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Support übernimmt Erstprüfung; Mail-, Netzwerk- und Dateidienste werden bei zentraler Ursache an Infrastruktur übergeben.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Scan-to-Mail und Scan-to-Folder“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Drucken, Scannen und Kopieren \u003e Scan-to-Mail und Scan-to-Folder" - ], - "keywords": [ - "Scan-to-Mail", - "Scan-to-Folder", - "SMTP", - "Zielordner", - "Scanversand", - "Netzwerkordner", - "Scannen per E-Mail", - "Scan-to-Mail und Scan-to-Folder", - "Drucken, Scannen und Kopieren" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/scan-to-mail-und-scan-to-folder", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-SCAN-TO-MAIL-UND-SCAN-TO-FOLDER-SELECT", + "title": "Scan-to-Mail und Scan-to-Folder", + "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Scan-to-Mail und Scan-to-Folder. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Scanner oder Multifunktionsgerät Scans nicht per E-Mail versendet oder nicht in einen zentralen Ordner ablegt, obwohl das Scannen selbst funktioniert. Typische Ticketformulierungen sind: „Scan kommt nicht per Mail an“; „Scan-to-Folder schlägt fehl“; „Zielordner nicht erreichbar“; „SMTP-Fehler am Kopierer“. Nicht auswählen, wenn das Gerät generell nicht scannt, das E-Mail-System organisationsweit ausfällt oder eine allgemeine Dateifreigabe gestört ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Support übernimmt Erstprüfung; Mail-, Netzwerk- und Dateidienste werden bei zentraler Ursache an Infrastruktur übergeben.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Scan-to-Mail und Scan-to-Folder“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Drucken, Scannen und Kopieren \u003e Scan-to-Mail und Scan-to-Folder" + ], + "keywords": [ + "Scan-to-Mail", + "Scan-to-Folder", + "SMTP", + "Zielordner", + "Scanversand", + "Netzwerkordner", + "Scannen per E-Mail", + "Scan-to-Mail und Scan-to-Folder", + "Drucken, Scannen und Kopieren" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/scan-to-mail-und-scan-to-folder", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/06_sicherheitsupdate.json b/knowledge/06_sicherheitsupdate.json index ef944da..e9d7993 100644 --- a/knowledge/06_sicherheitsupdate.json +++ b/knowledge/06_sicherheitsupdate.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-SICHERHEITSUPDATE-SELECT", - "title": "Sicherheitsupdate", - "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Sicherheitsupdate. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn ein sicherheitskritischer Patch, Herstellerhinweis oder dringendes Update bewertet, getestet und zeitnah auf Servern, Plattformen oder zentralen Komponenten ausgerollt werden soll. Typische Ticketformulierungen sind: „Kritischen Patch einspielen“; „Hersteller meldet Security Update“; „Zero-Day-Patch planen“; „Sicherheitsaktualisierung verteilen“. Nicht auswählen, wenn es um ein reguläres Fachanwendungsrelease, ein einzelnes Clientupdate ohne Sicherheitsbezug oder eine bereits ausgenutzte Schwachstelle geht. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei bestätigter aktiver Ausnutzung zusätzlich Sicherheitsvorfall wählen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Sicherheitsupdate“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "IT-Sicherheit und Datensicherung \u003e Sicherheitsupdate" - ], - "keywords": [ - "Sicherheitsupdate", - "Security Patch", - "kritischer Patch", - "Zero Day", - "CVE Patch", - "Update", - "Herstellerwarnung", - "Patchmanagement", - "IT-Sicherheit und Datensicherung" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/sicherheitsupdate", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-SICHERHEITSUPDATE-SELECT", + "title": "Sicherheitsupdate", + "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Sicherheitsupdate. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn ein sicherheitskritischer Patch, Herstellerhinweis oder dringendes Update bewertet, getestet und zeitnah auf Servern, Plattformen oder zentralen Komponenten ausgerollt werden soll. Typische Ticketformulierungen sind: „Kritischen Patch einspielen“; „Hersteller meldet Security Update“; „Zero-Day-Patch planen“; „Sicherheitsaktualisierung verteilen“. Nicht auswählen, wenn es um ein reguläres Fachanwendungsrelease, ein einzelnes Clientupdate ohne Sicherheitsbezug oder eine bereits ausgenutzte Schwachstelle geht. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei bestätigter aktiver Ausnutzung zusätzlich Sicherheitsvorfall wählen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Sicherheitsupdate“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "IT-Sicherheit und Datensicherung \u003e Sicherheitsupdate" + ], + "keywords": [ + "Sicherheitsupdate", + "Security Patch", + "kritischer Patch", + "Zero Day", + "CVE Patch", + "Update", + "Herstellerwarnung", + "Patchmanagement", + "IT-Sicherheit und Datensicherung" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/sicherheitsupdate", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/06_videokonferenz.json b/knowledge/06_videokonferenz.json index b3a1e19..fc5114f 100644 --- a/knowledge/06_videokonferenz.json +++ b/knowledge/06_videokonferenz.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-VIDEOKONFERENZ-SELECT", - "title": "Videokonferenz", - "text": "Auswahlziel: Telefonie und Kommunikation \u003e Videokonferenz. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn Teilnahme, Kamera, Mikrofon, Lautsprecher, Bildschirmfreigabe oder Bedienung bei einer Videokonferenz am Arbeitsplatz nicht funktioniert. Typische Ticketformulierungen sind: „Kamera in Besprechung nicht verfügbar“; „Mikrofon wird nicht erkannt“; „Keine Tonwiedergabe in Videokonferenz“; „Bildschirmfreigabe klappt nicht“. Nicht auswählen, wenn ein zentrales Konferenzsystem, Netzwerkstandort oder eine Fachanwendung ohne Konferenzbezug betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei vielen gleichzeitig Betroffenen oder zentralem Dienstausfall an Infrastruktur beziehungsweise zuständige Plattformbetreuung übergeben.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Videokonferenz“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Telefonie und Kommunikation \u003e Videokonferenz" - ], - "keywords": [ - "Videokonferenz", - "Kamera", - "Mikrofon", - "Besprechung", - "Meeting", - "Bildschirmfreigabe", - "Teams-Konferenz", - "Webex", - "Zoom", - "Telefonie und Kommunikation" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/videokonferenz", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-VIDEOKONFERENZ-SELECT", + "title": "Videokonferenz", + "text": "Auswahlziel: Telefonie und Kommunikation \u003e Videokonferenz. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn Teilnahme, Kamera, Mikrofon, Lautsprecher, Bildschirmfreigabe oder Bedienung bei einer Videokonferenz am Arbeitsplatz nicht funktioniert. Typische Ticketformulierungen sind: „Kamera in Besprechung nicht verfügbar“; „Mikrofon wird nicht erkannt“; „Keine Tonwiedergabe in Videokonferenz“; „Bildschirmfreigabe klappt nicht“. Nicht auswählen, wenn ein zentrales Konferenzsystem, Netzwerkstandort oder eine Fachanwendung ohne Konferenzbezug betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei vielen gleichzeitig Betroffenen oder zentralem Dienstausfall an Infrastruktur beziehungsweise zuständige Plattformbetreuung übergeben.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Videokonferenz“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Telefonie und Kommunikation \u003e Videokonferenz" + ], + "keywords": [ + "Videokonferenz", + "Kamera", + "Mikrofon", + "Besprechung", + "Meeting", + "Bildschirmfreigabe", + "Teams-Konferenz", + "Webex", + "Zoom", + "Telefonie und Kommunikation" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/videokonferenz", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/06_virtuelle-maschinen.json b/knowledge/06_virtuelle-maschinen.json index c177333..0440b40 100644 --- a/knowledge/06_virtuelle-maschinen.json +++ b/knowledge/06_virtuelle-maschinen.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-VIRTUELLE-MASCHINEN-SELECT", - "title": "Virtuelle Maschinen", - "text": "Auswahlziel: Server, Backend und Plattformen \u003e Virtuelle Maschinen. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn eine konkrete virtuelle Maschine neu bereitgestellt, geändert, vergrößert, geklont, gestartet, wiederhergestellt oder bei einem VM-spezifischen Fehler bearbeitet werden soll. Typische Ticketformulierungen sind: „Neue VM bereitstellen“; „Virtuelle Maschine startet nicht“; „RAM oder CPU erhöhen“; „VM klonen“. Nicht auswählen, wenn der gesamte Hypervisor-Cluster betroffen ist, nur die Anwendung in der VM fehlerhaft ist oder ein Container benötigt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Betriebssystem- und Anwendungsprobleme innerhalb der VM sind getrennt zu betrachten.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Virtuelle Maschinen“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Server, Backend und Plattformen \u003e Virtuelle Maschinen" - ], - "keywords": [ - "virtuelle Maschine", - "VM", - "vCPU", - "virtueller Server", - "VM bereitstellen", - "VM startet nicht", - "Snapshot", - "Ressourcen erhöhen", - "Virtuelle Maschinen", - "Server, Backend und Plattformen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/virtuelle-maschinen", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-VIRTUELLE-MASCHINEN-SELECT", + "title": "Virtuelle Maschinen", + "text": "Auswahlziel: Server, Backend und Plattformen \u003e Virtuelle Maschinen. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn eine konkrete virtuelle Maschine neu bereitgestellt, geändert, vergrößert, geklont, gestartet, wiederhergestellt oder bei einem VM-spezifischen Fehler bearbeitet werden soll. Typische Ticketformulierungen sind: „Neue VM bereitstellen“; „Virtuelle Maschine startet nicht“; „RAM oder CPU erhöhen“; „VM klonen“. Nicht auswählen, wenn der gesamte Hypervisor-Cluster betroffen ist, nur die Anwendung in der VM fehlerhaft ist oder ein Container benötigt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Betriebssystem- und Anwendungsprobleme innerhalb der VM sind getrennt zu betrachten.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Virtuelle Maschinen“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Server, Backend und Plattformen \u003e Virtuelle Maschinen" + ], + "keywords": [ + "virtuelle Maschine", + "VM", + "vCPU", + "virtueller Server", + "VM bereitstellen", + "VM startet nicht", + "Snapshot", + "Ressourcen erhöhen", + "Virtuelle Maschinen", + "Server, Backend und Plattformen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/virtuelle-maschinen", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/06_wartungs-und-supportvertrag.json b/knowledge/06_wartungs-und-supportvertrag.json index 201324a..445f7a0 100644 --- a/knowledge/06_wartungs-und-supportvertrag.json +++ b/knowledge/06_wartungs-und-supportvertrag.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-WARTUNGS-UND-SUPPORTVERTRAG-SELECT", - "title": "Wartungs- und Supportvertrag", - "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Wartungs- und Supportvertrag. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn Wartungs-, Pflege-, Hersteller- oder Supportverträge abgeschlossen, verlängert, angepasst, geprüft oder gekündigt werden sollen. Typische Ticketformulierungen sind: „Wartungsvertrag verlängern“; „Herstellersupport beauftragen“; „Pflegevertrag prüfen“; „Supportvertrag kündigen“. Nicht auswählen, wenn ein konkreter technischer Supportfall beim Hersteller eröffnet oder eine Rechnung ohne Vertragsänderung geprüft wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Leistungsumfang, Laufzeit, Kündigungsfrist und zuständiges Fachteam sind zu dokumentieren.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Wartungs- und Supportvertrag“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Beschaffung, Verträge und Lizenzen \u003e Wartungs- und Supportvertrag" - ], - "keywords": [ - "Wartungsvertrag", - "Supportvertrag", - "Pflegevertrag", - "Herstellersupport", - "Verlängerung", - "Kündigung", - "SLA", - "Maintenance", - "Wartungs- und Supportvertrag", - "Beschaffung, Verträge und Lizenzen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/wartungs-und-supportvertrag", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-WARTUNGS-UND-SUPPORTVERTRAG-SELECT", + "title": "Wartungs- und Supportvertrag", + "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Wartungs- und Supportvertrag. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn Wartungs-, Pflege-, Hersteller- oder Supportverträge abgeschlossen, verlängert, angepasst, geprüft oder gekündigt werden sollen. Typische Ticketformulierungen sind: „Wartungsvertrag verlängern“; „Herstellersupport beauftragen“; „Pflegevertrag prüfen“; „Supportvertrag kündigen“. Nicht auswählen, wenn ein konkreter technischer Supportfall beim Hersteller eröffnet oder eine Rechnung ohne Vertragsänderung geprüft wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Leistungsumfang, Laufzeit, Kündigungsfrist und zuständiges Fachteam sind zu dokumentieren.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Wartungs- und Supportvertrag“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Beschaffung, Verträge und Lizenzen \u003e Wartungs- und Supportvertrag" + ], + "keywords": [ + "Wartungsvertrag", + "Supportvertrag", + "Pflegevertrag", + "Herstellersupport", + "Verlängerung", + "Kündigung", + "SLA", + "Maintenance", + "Wartungs- und Supportvertrag", + "Beschaffung, Verträge und Lizenzen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/wartungs-und-supportvertrag", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/07_angebot-und-wirtschaftlichkeitsprufung.json b/knowledge/07_angebot-und-wirtschaftlichkeitsprufung.json index e957d84..8691689 100644 --- a/knowledge/07_angebot-und-wirtschaftlichkeitsprufung.json +++ b/knowledge/07_angebot-und-wirtschaftlichkeitsprufung.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-ANGEBOT-UND-WIRTSCHAFTLICHKEITSPRUFUNG-SELECT", - "title": "Angebot und Wirtschaftlichkeitsprüfung", - "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Angebot und Wirtschaftlichkeitsprüfung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn Angebote eingeholt, Preise verglichen, Wirtschaftlichkeit bewertet, Vergabevermerke vorbereitet oder Beschaffungsvarianten kaufmännisch geprüft werden sollen. Typische Ticketformulierungen sind: „Drei Angebote vergleichen“; „Wirtschaftlichkeitsbetrachtung erstellen“; „Vergabe vorbereiten“; „Kostenvarianten bewerten“. Nicht auswählen, wenn die technische Produktauswahl allein im Vordergrund steht oder bereits eine Rechnung zur Zahlung vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Bewertung erfolgt durch das zuständige Fachteam; kaufmännische und vergaberechtliche Bewertung durch Beschaffung.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Angebot und Wirtschaftlichkeitsprüfung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Beschaffung, Verträge und Lizenzen \u003e Angebot und Wirtschaftlichkeitsprüfung" - ], - "keywords": [ - "Angebot", - "Preisvergleich", - "Wirtschaftlichkeit", - "Vergabe", - "Vergabevermerk", - "Kostenvergleich", - "Markterkundung", - "Angebot und Wirtschaftlichkeitsprüfung", - "Beschaffung, Verträge und Lizenzen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/angebot-und-wirtschaftlichkeitsprufung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-ANGEBOT-UND-WIRTSCHAFTLICHKEITSPRUFUNG-SELECT", + "title": "Angebot und Wirtschaftlichkeitsprüfung", + "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Angebot und Wirtschaftlichkeitsprüfung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn Angebote eingeholt, Preise verglichen, Wirtschaftlichkeit bewertet, Vergabevermerke vorbereitet oder Beschaffungsvarianten kaufmännisch geprüft werden sollen. Typische Ticketformulierungen sind: „Drei Angebote vergleichen“; „Wirtschaftlichkeitsbetrachtung erstellen“; „Vergabe vorbereiten“; „Kostenvarianten bewerten“. Nicht auswählen, wenn die technische Produktauswahl allein im Vordergrund steht oder bereits eine Rechnung zur Zahlung vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Bewertung erfolgt durch das zuständige Fachteam; kaufmännische und vergaberechtliche Bewertung durch Beschaffung.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Angebot und Wirtschaftlichkeitsprüfung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Beschaffung, Verträge und Lizenzen \u003e Angebot und Wirtschaftlichkeitsprüfung" + ], + "keywords": [ + "Angebot", + "Preisvergleich", + "Wirtschaftlichkeit", + "Vergabe", + "Vergabevermerk", + "Kostenvergleich", + "Markterkundung", + "Angebot und Wirtschaftlichkeitsprüfung", + "Beschaffung, Verträge und Lizenzen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/angebot-und-wirtschaftlichkeitsprufung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/07_datensicherung.json b/knowledge/07_datensicherung.json index 644da16..46470f6 100644 --- a/knowledge/07_datensicherung.json +++ b/knowledge/07_datensicherung.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-DATENSICHERUNG-SELECT", - "title": "Datensicherung", - "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Datensicherung. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Backup-Jobs fehlschlagen, Sicherungspläne, Aufbewahrung, Sicherungsziele, Kapazitäten oder Backup-Konzepte eingerichtet, geändert oder geprüft werden müssen. Typische Ticketformulierungen sind: „Backup fehlgeschlagen“; „Sicherungsjob rot“; „Aufbewahrung ändern“; „Neues System in Datensicherung aufnehmen“. Nicht auswählen, wenn konkrete Daten wiederhergestellt werden sollen oder eine Fachanwendung lediglich keine Exportdatei erzeugt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Wiederherstellungsanforderungen werden getrennt unter Datenwiederherstellung erfasst.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Datensicherung“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "IT-Sicherheit und Datensicherung \u003e Datensicherung" - ], - "keywords": [ - "Datensicherung", - "Backup", - "Sicherungsjob", - "Aufbewahrung", - "Retention", - "Backupziel", - "Sicherungskonzept", - "Backup fehlgeschlagen", - "IT-Sicherheit und Datensicherung" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/datensicherung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-DATENSICHERUNG-SELECT", + "title": "Datensicherung", + "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Datensicherung. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Backup-Jobs fehlschlagen, Sicherungspläne, Aufbewahrung, Sicherungsziele, Kapazitäten oder Backup-Konzepte eingerichtet, geändert oder geprüft werden müssen. Typische Ticketformulierungen sind: „Backup fehlgeschlagen“; „Sicherungsjob rot“; „Aufbewahrung ändern“; „Neues System in Datensicherung aufnehmen“. Nicht auswählen, wenn konkrete Daten wiederhergestellt werden sollen oder eine Fachanwendung lediglich keine Exportdatei erzeugt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Wiederherstellungsanforderungen werden getrennt unter Datenwiederherstellung erfasst.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Datensicherung“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "IT-Sicherheit und Datensicherung \u003e Datensicherung" + ], + "keywords": [ + "Datensicherung", + "Backup", + "Sicherungsjob", + "Aufbewahrung", + "Retention", + "Backupziel", + "Sicherungskonzept", + "Backup fehlgeschlagen", + "IT-Sicherheit und Datensicherung" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/datensicherung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/07_fachanwendung-update-und-release.json b/knowledge/07_fachanwendung-update-und-release.json index c1f2c50..25c1a21 100644 --- a/knowledge/07_fachanwendung-update-und-release.json +++ b/knowledge/07_fachanwendung-update-und-release.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-FACHANWENDUNG-UPDATE-UND-RELEASE-SELECT", - "title": "Fachanwendung – Update und Release", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Update und Release. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn ein Update, Patch, Releasewechsel oder Versionsupgrade einer Fachanwendung geplant, getestet, freigegeben oder nachbereitet werden soll. Typische Ticketformulierungen sind: „Neue Fachverfahrensversion testen“; „Release einspielen“; „Herstellerupdate planen“; „Patch der Anwendung freigeben“. Nicht auswählen, wenn nur ein Windows-Clientupdate scheitert, ein Sicherheitspatch der Serverplattform betroffen ist oder eine neue Anwendung erstmals eingeführt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Server- und Datenbankarbeiten werden mit Infrastruktur und Backend abgestimmt.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Update und Release“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Update und Release" - ], - "keywords": [ - "Update", - "Release", - "Version", - "Patch", - "Upgrade", - "Herstellerupdate", - "Testsystem", - "Freigabe", - "Fachanwendung aktualisieren", - "Fachanwendung – Update und Release", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-update-und-release", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-FACHANWENDUNG-UPDATE-UND-RELEASE-SELECT", + "title": "Fachanwendung – Update und Release", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Update und Release. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn ein Update, Patch, Releasewechsel oder Versionsupgrade einer Fachanwendung geplant, getestet, freigegeben oder nachbereitet werden soll. Typische Ticketformulierungen sind: „Neue Fachverfahrensversion testen“; „Release einspielen“; „Herstellerupdate planen“; „Patch der Anwendung freigeben“. Nicht auswählen, wenn nur ein Windows-Clientupdate scheitert, ein Sicherheitspatch der Serverplattform betroffen ist oder eine neue Anwendung erstmals eingeführt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Server- und Datenbankarbeiten werden mit Infrastruktur und Backend abgestimmt.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Update und Release“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Update und Release" + ], + "keywords": [ + "Update", + "Release", + "Version", + "Patch", + "Upgrade", + "Herstellerupdate", + "Testsystem", + "Freigabe", + "Fachanwendung aktualisieren", + "Fachanwendung – Update und Release", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-update-und-release", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/07_firewall-freischaltung.json b/knowledge/07_firewall-freischaltung.json index ece4875..cca99b4 100644 --- a/knowledge/07_firewall-freischaltung.json +++ b/knowledge/07_firewall-freischaltung.json @@ -1,29 +1,29 @@ -{ - "id": "KAT-FIREWALL-FREISCHALTUNG-SELECT", - "title": "Firewall-Freischaltung", - "text": "Auswahlziel: Netzwerk und Verbindungen \u003e Firewall-Freischaltung. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn eine konkrete Netzwerkkommunikation durch Firewallregeln erlaubt, geändert, zeitlich begrenzt oder geprüft werden soll, typischerweise mit Quelle, Ziel, Port und Protokoll. Typische Ticketformulierungen sind: „Port 443 zu Zielsystem freischalten“; „Firewall blockiert Anwendung“; „Kommunikation zwischen Netzen erlauben“; „Externer Dienst benötigt Zugriff“. Nicht auswählen, wenn ein allgemeiner Internetausfall, ein VPN-Berechtigungsantrag oder eine fachliche Schnittstellenkonfiguration ohne Firewallbezug vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Unklare pauschale Freischaltungen sind nicht ausreichend; Sicherheitsprüfung und Minimalprinzip gelten.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e Firewall-Freischaltung“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Netzwerk und Verbindungen \u003e Firewall-Freischaltung" - ], - "keywords": [ - "Firewall", - "Freischaltung", - "Port", - "Quelle", - "Ziel", - "Protokoll", - "Regel", - "blockiert", - "Netzwerkfreigabe", - "Whitelist", - "Firewall-Freischaltung", - "Netzwerk und Verbindungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/firewall-freischaltung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-FIREWALL-FREISCHALTUNG-SELECT", + "title": "Firewall-Freischaltung", + "text": "Auswahlziel: Netzwerk und Verbindungen \u003e Firewall-Freischaltung. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn eine konkrete Netzwerkkommunikation durch Firewallregeln erlaubt, geändert, zeitlich begrenzt oder geprüft werden soll, typischerweise mit Quelle, Ziel, Port und Protokoll. Typische Ticketformulierungen sind: „Port 443 zu Zielsystem freischalten“; „Firewall blockiert Anwendung“; „Kommunikation zwischen Netzen erlauben“; „Externer Dienst benötigt Zugriff“. Nicht auswählen, wenn ein allgemeiner Internetausfall, ein VPN-Berechtigungsantrag oder eine fachliche Schnittstellenkonfiguration ohne Firewallbezug vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Unklare pauschale Freischaltungen sind nicht ausreichend; Sicherheitsprüfung und Minimalprinzip gelten.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e Firewall-Freischaltung“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Netzwerk und Verbindungen \u003e Firewall-Freischaltung" + ], + "keywords": [ + "Firewall", + "Freischaltung", + "Port", + "Quelle", + "Ziel", + "Protokoll", + "Regel", + "blockiert", + "Netzwerkfreigabe", + "Whitelist", + "Firewall-Freischaltung", + "Netzwerk und Verbindungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/firewall-freischaltung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/07_funktions-und-sammelpostfacher.json b/knowledge/07_funktions-und-sammelpostfacher.json index e32edfc..f8527b5 100644 --- a/knowledge/07_funktions-und-sammelpostfacher.json +++ b/knowledge/07_funktions-und-sammelpostfacher.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-FUNKTIONS-UND-SAMMELPOSTFACHER-SELECT", - "title": "Funktions- und Sammelpostfächer", - "text": "Auswahlziel: Benutzerkonten und Berechtigungen \u003e Funktions- und Sammelpostfächer. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn ein Funktionspostfach, Sammelpostfach, Team-Postfach oder gemeinsam genutztes Postfach neu angelegt, geändert, umbenannt, berechtigt oder außer Betrieb genommen werden soll. Typische Ticketformulierungen sind: „Postfach info@ anlegen“; „Zugriff auf gemeinsames Postfach“; „Funktionspostfach umbenennen“; „Sammelpostfach schließen“. Nicht auswählen, wenn der Outlook-Client lokal nicht startet, eine einzelne E-Mail nicht zugestellt wird oder eine Verteilerliste ohne Postfachbezug gemeint ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Lokale Outlook-Probleme gehören zu Microsoft Office \u003e Outlook-Client; serverseitige Mailstörungen zu Infrastruktur und Backend.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Benutzerkonten und Berechtigungen \u003e Funktions- und Sammelpostfächer“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Benutzerkonten und Berechtigungen \u003e Funktions- und Sammelpostfächer" - ], - "keywords": [ - "Funktionspostfach", - "Sammelpostfach", - "gemeinsames Postfach", - "Shared Mailbox", - "Team-Postfach", - "Postfachberechtigung", - "Senden als", - "Postfach anlegen", - "Funktions- und Sammelpostfächer", - "Benutzerkonten und Berechtigungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/benutzerkonten-und-berechtigungen/funktions-und-sammelpostfacher", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-FUNKTIONS-UND-SAMMELPOSTFACHER-SELECT", + "title": "Funktions- und Sammelpostfächer", + "text": "Auswahlziel: Benutzerkonten und Berechtigungen \u003e Funktions- und Sammelpostfächer. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn ein Funktionspostfach, Sammelpostfach, Team-Postfach oder gemeinsam genutztes Postfach neu angelegt, geändert, umbenannt, berechtigt oder außer Betrieb genommen werden soll. Typische Ticketformulierungen sind: „Postfach info@ anlegen“; „Zugriff auf gemeinsames Postfach“; „Funktionspostfach umbenennen“; „Sammelpostfach schließen“. Nicht auswählen, wenn der Outlook-Client lokal nicht startet, eine einzelne E-Mail nicht zugestellt wird oder eine Verteilerliste ohne Postfachbezug gemeint ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Lokale Outlook-Probleme gehören zu Microsoft Office \u003e Outlook-Client; serverseitige Mailstörungen zu Infrastruktur und Backend.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Benutzerkonten und Berechtigungen \u003e Funktions- und Sammelpostfächer“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Benutzerkonten und Berechtigungen \u003e Funktions- und Sammelpostfächer" + ], + "keywords": [ + "Funktionspostfach", + "Sammelpostfach", + "gemeinsames Postfach", + "Shared Mailbox", + "Team-Postfach", + "Postfachberechtigung", + "Senden als", + "Postfach anlegen", + "Funktions- und Sammelpostfächer", + "Benutzerkonten und Berechtigungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/benutzerkonten-und-berechtigungen/funktions-und-sammelpostfacher", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/07_geratewechsel-und-umzug.json b/knowledge/07_geratewechsel-und-umzug.json index cae96af..81fc8a2 100644 --- a/knowledge/07_geratewechsel-und-umzug.json +++ b/knowledge/07_geratewechsel-und-umzug.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-GERATEWECHSEL-UND-UMZUG-SELECT", - "title": "Gerätewechsel und Umzug", - "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Gerätewechsel und Umzug. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn vorhandene IT-Arbeitsmittel bei Arbeitsplatzwechsel, Raumumzug, Stellenwechsel oder Gerätetausch umgesetzt, neu angeschlossen, migriert oder ausgetauscht werden müssen. Typische Ticketformulierungen sind: „Arbeitsplatz in anderes Büro umziehen“; „Notebook gegen Ersatzgerät tauschen“; „PC und Monitore umsetzen“; „Daten auf Austauschgerät übernehmen“. Nicht auswählen, wenn neue zusätzliche Hardware beschafft werden soll, ein kompletter Standort umzieht oder nur ein technischer Defekt ohne Umzug vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Größere Standortumzüge werden als Projekt beziehungsweise Standorteröffnung oder Umzug geplant.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Gerätewechsel und Umzug“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Arbeitsplatz und Endgeräte \u003e Gerätewechsel und Umzug" - ], - "keywords": [ - "Umzug", - "Arbeitsplatzwechsel", - "Gerätewechsel", - "Gerätetausch", - "Austauschgerät", - "Bürowechsel", - "Umsetzen", - "Migration Arbeitsplatz", - "Gerätewechsel und Umzug", - "Arbeitsplatz und Endgeräte" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/geratewechsel-und-umzug", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-GERATEWECHSEL-UND-UMZUG-SELECT", + "title": "Gerätewechsel und Umzug", + "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Gerätewechsel und Umzug. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn vorhandene IT-Arbeitsmittel bei Arbeitsplatzwechsel, Raumumzug, Stellenwechsel oder Gerätetausch umgesetzt, neu angeschlossen, migriert oder ausgetauscht werden müssen. Typische Ticketformulierungen sind: „Arbeitsplatz in anderes Büro umziehen“; „Notebook gegen Ersatzgerät tauschen“; „PC und Monitore umsetzen“; „Daten auf Austauschgerät übernehmen“. Nicht auswählen, wenn neue zusätzliche Hardware beschafft werden soll, ein kompletter Standort umzieht oder nur ein technischer Defekt ohne Umzug vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Größere Standortumzüge werden als Projekt beziehungsweise Standorteröffnung oder Umzug geplant.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Gerätewechsel und Umzug“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Arbeitsplatz und Endgeräte \u003e Gerätewechsel und Umzug" + ], + "keywords": [ + "Umzug", + "Arbeitsplatzwechsel", + "Gerätewechsel", + "Gerätetausch", + "Austauschgerät", + "Bürowechsel", + "Umsetzen", + "Migration Arbeitsplatz", + "Gerätewechsel und Umzug", + "Arbeitsplatz und Endgeräte" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/geratewechsel-und-umzug", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/07_mobile-device-management-fur-schulen.json b/knowledge/07_mobile-device-management-fur-schulen.json index 7f41da8..d86d9f7 100644 --- a/knowledge/07_mobile-device-management-fur-schulen.json +++ b/knowledge/07_mobile-device-management-fur-schulen.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-MOBILE-DEVICE-MANAGEMENT-FUR-SCHULEN-SELECT", - "title": "Mobile-Device-Management für Schulen", - "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Mobile-Device-Management für Schulen. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn die zentrale Verwaltung schulischer mobiler Geräte, MDM-Profile, App-Verteilung, Gerätegruppen, Richtlinien oder Enrollment betroffen ist. Typische Ticketformulierungen sind: „MDM-Profil wird nicht installiert“; „App an Schülergeräte verteilen“; „Gerät aus MDM entfernen“; „Enrollment schlägt fehl“. Nicht auswählen, wenn nur ein einzelnes Gerät physisch defekt ist oder ein allgemeines kommunales Smartphone eingerichtet werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Plattformbetrieb kann mittelfristig an Infrastruktur und Backend übergehen; schulfachliche Gerätezuordnung bleibt zu klären.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Mobile-Device-Management für Schulen“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Schulspezifische IT-Systeme \u003e Mobile-Device-Management für Schulen" - ], - "keywords": [ - "MDM", - "Mobile Device Management", - "Enrollment", - "Geräteprofil", - "App-Verteilung", - "Schülergeräte verwalten", - "Gerätegruppe", - "DEP", - "Mobile-Device-Management für Schulen", - "Schulspezifische IT-Systeme" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/mobile-device-management-fur-schulen", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-MOBILE-DEVICE-MANAGEMENT-FUR-SCHULEN-SELECT", + "title": "Mobile-Device-Management für Schulen", + "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Mobile-Device-Management für Schulen. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn die zentrale Verwaltung schulischer mobiler Geräte, MDM-Profile, App-Verteilung, Gerätegruppen, Richtlinien oder Enrollment betroffen ist. Typische Ticketformulierungen sind: „MDM-Profil wird nicht installiert“; „App an Schülergeräte verteilen“; „Gerät aus MDM entfernen“; „Enrollment schlägt fehl“. Nicht auswählen, wenn nur ein einzelnes Gerät physisch defekt ist oder ein allgemeines kommunales Smartphone eingerichtet werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Plattformbetrieb kann mittelfristig an Infrastruktur und Backend übergehen; schulfachliche Gerätezuordnung bleibt zu klären.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Mobile-Device-Management für Schulen“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Schulspezifische IT-Systeme \u003e Mobile-Device-Management für Schulen" + ], + "keywords": [ + "MDM", + "Mobile Device Management", + "Enrollment", + "Geräteprofil", + "App-Verteilung", + "Schülergeräte verwalten", + "Gerätegruppe", + "DEP", + "Mobile-Device-Management für Schulen", + "Schulspezifische IT-Systeme" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/mobile-device-management-fur-schulen", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/07_projektunterstutzung.json b/knowledge/07_projektunterstutzung.json index b22a03b..80a574a 100644 --- a/knowledge/07_projektunterstutzung.json +++ b/knowledge/07_projektunterstutzung.json @@ -1,25 +1,25 @@ -{ - "id": "KAT-PROJEKTUNTERSTUTZUNG-SELECT", - "title": "Projektunterstützung", - "text": "Auswahlziel: Projekte, Änderungen und neue Anforderungen \u003e Projektunterstützung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn die IT in einem fachbereichsgeführten Projekt beraten, mitarbeiten, Aufwände schätzen, technische Teilaufgaben übernehmen oder feste Ressourcen bereitstellen soll. Typische Ticketformulierungen sind: „IT-Mitarbeit im Bauprojekt“; „Technische Beratung für Fachprojekt“; „Aufwandsschätzung benötigt“; „IT-Ressource für Projekt anfragen“. Nicht auswählen, wenn die IT selbst das Projekt führt, eine normale Störung bearbeitet oder nur eine einzelne Standardleistung bestellt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Leitung priorisiert Ressourcen und benennt das zuständige Fachteam.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Projekte, Änderungen und neue Anforderungen \u003e Projektunterstützung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Projekte, Änderungen und neue Anforderungen \u003e Projektunterstützung" - ], - "keywords": [ - "Projektunterstützung", - "Mitarbeit Projekt", - "IT-Beratung", - "Ressource", - "Aufwandsschätzung", - "Teilprojekt", - "Projektanfrage", - "Projekte, Änderungen und neue Anforderungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/projekte-anderungen-und-neue-anforderungen/projektunterstutzung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-PROJEKTUNTERSTUTZUNG-SELECT", + "title": "Projektunterstützung", + "text": "Auswahlziel: Projekte, Änderungen und neue Anforderungen \u003e Projektunterstützung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn die IT in einem fachbereichsgeführten Projekt beraten, mitarbeiten, Aufwände schätzen, technische Teilaufgaben übernehmen oder feste Ressourcen bereitstellen soll. Typische Ticketformulierungen sind: „IT-Mitarbeit im Bauprojekt“; „Technische Beratung für Fachprojekt“; „Aufwandsschätzung benötigt“; „IT-Ressource für Projekt anfragen“. Nicht auswählen, wenn die IT selbst das Projekt führt, eine normale Störung bearbeitet oder nur eine einzelne Standardleistung bestellt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Leitung priorisiert Ressourcen und benennt das zuständige Fachteam.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Projekte, Änderungen und neue Anforderungen \u003e Projektunterstützung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Projekte, Änderungen und neue Anforderungen \u003e Projektunterstützung" + ], + "keywords": [ + "Projektunterstützung", + "Mitarbeit Projekt", + "IT-Beratung", + "Ressource", + "Aufwandsschätzung", + "Teilprojekt", + "Projektanfrage", + "Projekte, Änderungen und neue Anforderungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/projekte-anderungen-und-neue-anforderungen/projektunterstutzung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/07_storage.json b/knowledge/07_storage.json index d0b00ae..6784fb9 100644 --- a/knowledge/07_storage.json +++ b/knowledge/07_storage.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-STORAGE-SELECT", - "title": "Storage", - "text": "Auswahlziel: Server, Backend und Plattformen \u003e Storage. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn zentrale Speicherplattformen, SAN, NAS, Volumes, LUNs, Kapazitäten, Performance oder Storage-Replikation betroffen sind. Typische Ticketformulierungen sind: „Storage-Kapazität erweitern“; „SAN meldet Fehler“; „Volume nicht verfügbar“; „Speicherperformance schlecht“. Nicht auswählen, wenn nur eine Dateifreigabe, eine lokale Festplatte oder der Speicherplatz einer einzelnen Anwendung betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Dateidienste und virtuelle Maschinen können Folgeprobleme zeigen, die Ursache bleibt jedoch Storage.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Storage“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Server, Backend und Plattformen \u003e Storage" - ], - "keywords": [ - "Storage", - "SAN", - "NAS", - "LUN", - "Volume", - "Speicherplattform", - "Kapazität", - "IOPS", - "Speicherarray", - "Server, Backend und Plattformen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/storage", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-STORAGE-SELECT", + "title": "Storage", + "text": "Auswahlziel: Server, Backend und Plattformen \u003e Storage. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn zentrale Speicherplattformen, SAN, NAS, Volumes, LUNs, Kapazitäten, Performance oder Storage-Replikation betroffen sind. Typische Ticketformulierungen sind: „Storage-Kapazität erweitern“; „SAN meldet Fehler“; „Volume nicht verfügbar“; „Speicherperformance schlecht“. Nicht auswählen, wenn nur eine Dateifreigabe, eine lokale Festplatte oder der Speicherplatz einer einzelnen Anwendung betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Dateidienste und virtuelle Maschinen können Folgeprobleme zeigen, die Ursache bleibt jedoch Storage.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Storage“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Server, Backend und Plattformen \u003e Storage" + ], + "keywords": [ + "Storage", + "SAN", + "NAS", + "LUN", + "Volume", + "Speicherplattform", + "Kapazität", + "IOPS", + "Speicherarray", + "Server, Backend und Plattformen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/storage", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/07_telefonkonferenz-und-softphone.json b/knowledge/07_telefonkonferenz-und-softphone.json index 2b2f7be..b2cbf63 100644 --- a/knowledge/07_telefonkonferenz-und-softphone.json +++ b/knowledge/07_telefonkonferenz-und-softphone.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-TELEFONKONFERENZ-UND-SOFTPHONE-SELECT", - "title": "Telefonkonferenz und Softphone", - "text": "Auswahlziel: Telefonie und Kommunikation \u003e Telefonkonferenz und Softphone. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Softphone, PC-Telefonie, Headset-Telefonie oder eine Telefonkonferenz nicht funktioniert, eingerichtet oder bedient werden muss. Typische Ticketformulierungen sind: „Softphone meldet nicht an“; „Telefonkonferenz einrichten“; „Kein Ton im PC-Telefon“; „Headset im Softphone nicht auswählbar“. Nicht auswählen, wenn ein physisches Festnetztelefon, eine Rufnummernverwaltung oder eine reine Videokonferenz betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei zentralen VoIP- oder Plattformstörungen Infrastruktur und Backend beteiligen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Telefonkonferenz und Softphone“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Telefonie und Kommunikation \u003e Telefonkonferenz und Softphone" - ], - "keywords": [ - "Softphone", - "Telefonkonferenz", - "PC-Telefonie", - "VoIP-Client", - "Headset", - "Konferenznummer", - "Audioanruf", - "Telefonkonferenz und Softphone", - "Telefonie und Kommunikation" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/telefonkonferenz-und-softphone", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-TELEFONKONFERENZ-UND-SOFTPHONE-SELECT", + "title": "Telefonkonferenz und Softphone", + "text": "Auswahlziel: Telefonie und Kommunikation \u003e Telefonkonferenz und Softphone. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Softphone, PC-Telefonie, Headset-Telefonie oder eine Telefonkonferenz nicht funktioniert, eingerichtet oder bedient werden muss. Typische Ticketformulierungen sind: „Softphone meldet nicht an“; „Telefonkonferenz einrichten“; „Kein Ton im PC-Telefon“; „Headset im Softphone nicht auswählbar“. Nicht auswählen, wenn ein physisches Festnetztelefon, eine Rufnummernverwaltung oder eine reine Videokonferenz betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei zentralen VoIP- oder Plattformstörungen Infrastruktur und Backend beteiligen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Telefonkonferenz und Softphone“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Telefonie und Kommunikation \u003e Telefonkonferenz und Softphone" + ], + "keywords": [ + "Softphone", + "Telefonkonferenz", + "PC-Telefonie", + "VoIP-Client", + "Headset", + "Konferenznummer", + "Audioanruf", + "Telefonkonferenz und Softphone", + "Telefonie und Kommunikation" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/telefonkonferenz-und-softphone", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/07_verbrauchsmaterial.json b/knowledge/07_verbrauchsmaterial.json index f55b35b..1a55de3 100644 --- a/knowledge/07_verbrauchsmaterial.json +++ b/knowledge/07_verbrauchsmaterial.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-VERBRAUCHSMATERIAL-SELECT", - "title": "Verbrauchsmaterial", - "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Verbrauchsmaterial. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn Toner, Trommel, Resttonerbehälter, Heftklammern oder anderes Verbrauchsmaterial für Drucker und Kopierer benötigt oder als leer gemeldet wird. Typische Ticketformulierungen sind: „Toner leer“; „Neue Trommel benötigt“; „Resttonerbehälter voll“; „Verbrauchsmaterial bestellen“. Nicht auswählen, wenn das Gerät trotz vorhandenem Material defekt ist oder eine allgemeine Hardwarebeschaffung ansteht. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bestellprozess und Lagerhaltung können je nach Organisation bei Support oder Beschaffung liegen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Verbrauchsmaterial“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Drucken, Scannen und Kopieren \u003e Verbrauchsmaterial" - ], - "keywords": [ - "Toner", - "Trommel", - "Verbrauchsmaterial", - "Resttoner", - "Kartusche", - "Druckerzubehör", - "leer", - "bestellen", - "Drucken, Scannen und Kopieren" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/verbrauchsmaterial", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-VERBRAUCHSMATERIAL-SELECT", + "title": "Verbrauchsmaterial", + "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Verbrauchsmaterial. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn Toner, Trommel, Resttonerbehälter, Heftklammern oder anderes Verbrauchsmaterial für Drucker und Kopierer benötigt oder als leer gemeldet wird. Typische Ticketformulierungen sind: „Toner leer“; „Neue Trommel benötigt“; „Resttonerbehälter voll“; „Verbrauchsmaterial bestellen“. Nicht auswählen, wenn das Gerät trotz vorhandenem Material defekt ist oder eine allgemeine Hardwarebeschaffung ansteht. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bestellprozess und Lagerhaltung können je nach Organisation bei Support oder Beschaffung liegen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Verbrauchsmaterial“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Drucken, Scannen und Kopieren \u003e Verbrauchsmaterial" + ], + "keywords": [ + "Toner", + "Trommel", + "Verbrauchsmaterial", + "Resttoner", + "Kartusche", + "Druckerzubehör", + "leer", + "bestellen", + "Drucken, Scannen und Kopieren" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/verbrauchsmaterial", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/08_datenbanken-plattformbetrieb.json b/knowledge/08_datenbanken-plattformbetrieb.json index 124c309..508e615 100644 --- a/knowledge/08_datenbanken-plattformbetrieb.json +++ b/knowledge/08_datenbanken-plattformbetrieb.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-DATENBANKEN-PLATTFORMBETRIEB-SELECT", - "title": "Datenbanken – Plattformbetrieb", - "text": "Auswahlziel: Server, Backend und Plattformen \u003e Datenbanken – Plattformbetrieb. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn der zentrale Datenbankdienst, die Datenbankinstanz, Verfügbarkeit, Sicherung, Performance oder technische Administration von SQL-Plattformen betroffen ist. Typische Ticketformulierungen sind: „SQL-Server nicht erreichbar“; „Datenbankinstanz langsam“; „Backup der Datenbank fehlerhaft“; „Neue Datenbank technisch bereitstellen“. Nicht auswählen, wenn fachliche Daten korrigiert, Berichte angepasst oder anwendungsspezifische Tabelleninhalte verändert werden sollen. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Fachliches Datenmodell und Datenkorrekturen liegen bei Fachanwendungen; Plattformbetrieb bei Infrastruktur.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Datenbanken – Plattformbetrieb“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Server, Backend und Plattformen \u003e Datenbanken – Plattformbetrieb" - ], - "keywords": [ - "Datenbank", - "SQL", - "SQL Server", - "PostgreSQL", - "Oracle", - "MySQL", - "Instanz", - "DB-Backup", - "Datenbankperformance", - "Datenbanken – Plattformbetrieb", - "Server, Backend und Plattformen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/datenbanken-plattformbetrieb", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-DATENBANKEN-PLATTFORMBETRIEB-SELECT", + "title": "Datenbanken – Plattformbetrieb", + "text": "Auswahlziel: Server, Backend und Plattformen \u003e Datenbanken – Plattformbetrieb. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn der zentrale Datenbankdienst, die Datenbankinstanz, Verfügbarkeit, Sicherung, Performance oder technische Administration von SQL-Plattformen betroffen ist. Typische Ticketformulierungen sind: „SQL-Server nicht erreichbar“; „Datenbankinstanz langsam“; „Backup der Datenbank fehlerhaft“; „Neue Datenbank technisch bereitstellen“. Nicht auswählen, wenn fachliche Daten korrigiert, Berichte angepasst oder anwendungsspezifische Tabelleninhalte verändert werden sollen. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Fachliches Datenmodell und Datenkorrekturen liegen bei Fachanwendungen; Plattformbetrieb bei Infrastruktur.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Datenbanken – Plattformbetrieb“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Server, Backend und Plattformen \u003e Datenbanken – Plattformbetrieb" + ], + "keywords": [ + "Datenbank", + "SQL", + "SQL Server", + "PostgreSQL", + "Oracle", + "MySQL", + "Instanz", + "DB-Backup", + "Datenbankperformance", + "Datenbanken – Plattformbetrieb", + "Server, Backend und Plattformen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/datenbanken-plattformbetrieb", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/08_datenwiederherstellung.json b/knowledge/08_datenwiederherstellung.json index 1b35e77..e465957 100644 --- a/knowledge/08_datenwiederherstellung.json +++ b/knowledge/08_datenwiederherstellung.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-DATENWIEDERHERSTELLUNG-SELECT", - "title": "Datenwiederherstellung", - "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Datenwiederherstellung. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn gelöschte, beschädigte oder verlorene Dateien, Verzeichnisse, Datenbanken, virtuelle Maschinen oder Systeme aus einer vorhandenen Sicherung wiederhergestellt werden sollen. Typische Ticketformulierungen sind: „Gelöschten Ordner wiederherstellen“; „Datei aus Backup zurückholen“; „VM-Restore“; „Datenbank auf Zeitpunkt zurücksetzen“. Nicht auswählen, wenn nur geprüft werden soll, ob Sicherungen laufen, oder wenn die Daten fachlich innerhalb einer Anwendung korrigiert werden müssen. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Betroffenes Objekt, Pfad, gewünschter Zeitpunkt und Dringlichkeit müssen möglichst genau angegeben werden.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Datenwiederherstellung“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "IT-Sicherheit und Datensicherung \u003e Datenwiederherstellung" - ], - "keywords": [ - "Wiederherstellung", - "Restore", - "gelöscht", - "Datei zurückholen", - "Backup einspielen", - "Recovery", - "Point-in-Time", - "Daten verloren", - "Datenwiederherstellung", - "IT-Sicherheit und Datensicherung" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/datenwiederherstellung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-DATENWIEDERHERSTELLUNG-SELECT", + "title": "Datenwiederherstellung", + "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Datenwiederherstellung. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn gelöschte, beschädigte oder verlorene Dateien, Verzeichnisse, Datenbanken, virtuelle Maschinen oder Systeme aus einer vorhandenen Sicherung wiederhergestellt werden sollen. Typische Ticketformulierungen sind: „Gelöschten Ordner wiederherstellen“; „Datei aus Backup zurückholen“; „VM-Restore“; „Datenbank auf Zeitpunkt zurücksetzen“. Nicht auswählen, wenn nur geprüft werden soll, ob Sicherungen laufen, oder wenn die Daten fachlich innerhalb einer Anwendung korrigiert werden müssen. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Betroffenes Objekt, Pfad, gewünschter Zeitpunkt und Dringlichkeit müssen möglichst genau angegeben werden.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Datenwiederherstellung“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "IT-Sicherheit und Datensicherung \u003e Datenwiederherstellung" + ], + "keywords": [ + "Wiederherstellung", + "Restore", + "gelöscht", + "Datei zurückholen", + "Backup einspielen", + "Recovery", + "Point-in-Time", + "Daten verloren", + "Datenwiederherstellung", + "IT-Sicherheit und Datensicherung" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/datenwiederherstellung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/08_fachanwendung-neue-anforderung.json b/knowledge/08_fachanwendung-neue-anforderung.json index 13854a6..60bc134 100644 --- a/knowledge/08_fachanwendung-neue-anforderung.json +++ b/knowledge/08_fachanwendung-neue-anforderung.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-FACHANWENDUNG-NEUE-ANFORDERUNG-SELECT", - "title": "Fachanwendung – Neue Anforderung", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Neue Anforderung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn eine bestehende Fachanwendung funktional erweitert, ein neuer Prozess abgebildet, ein zusätzliches Modul eingeführt oder eine bislang nicht vorhandene fachliche Funktion umgesetzt werden soll. Typische Ticketformulierungen sind: „Neues Formular im Fachverfahren“; „Zusätzliches Modul benötigt“; „Prozess soll digital abgebildet werden“; „Funktionserweiterung anfragen“. Nicht auswählen, wenn lediglich eine vorhandene Funktion gestört ist, eine kleine Parametrierung ausreicht oder eine komplett neue Anwendung beschafft und eingeführt werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Umfang, Nutzen, betroffene Organisation und Priorität sollten dokumentiert werden; größere Vorhaben können in ein Projekt überführt werden.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Neue Anforderung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Neue Anforderung" - ], - "keywords": [ - "neue Anforderung", - "Erweiterung", - "Feature", - "neue Funktion", - "zusätzliches Modul", - "Change Request", - "Anpassungswunsch", - "Prozess digitalisieren", - "Fachanwendung – Neue Anforderung", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-neue-anforderung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-FACHANWENDUNG-NEUE-ANFORDERUNG-SELECT", + "title": "Fachanwendung – Neue Anforderung", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Neue Anforderung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn eine bestehende Fachanwendung funktional erweitert, ein neuer Prozess abgebildet, ein zusätzliches Modul eingeführt oder eine bislang nicht vorhandene fachliche Funktion umgesetzt werden soll. Typische Ticketformulierungen sind: „Neues Formular im Fachverfahren“; „Zusätzliches Modul benötigt“; „Prozess soll digital abgebildet werden“; „Funktionserweiterung anfragen“. Nicht auswählen, wenn lediglich eine vorhandene Funktion gestört ist, eine kleine Parametrierung ausreicht oder eine komplett neue Anwendung beschafft und eingeführt werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Umfang, Nutzen, betroffene Organisation und Priorität sollten dokumentiert werden; größere Vorhaben können in ein Projekt überführt werden.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Neue Anforderung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Neue Anforderung" + ], + "keywords": [ + "neue Anforderung", + "Erweiterung", + "Feature", + "neue Funktion", + "zusätzliches Modul", + "Change Request", + "Anpassungswunsch", + "Prozess digitalisieren", + "Fachanwendung – Neue Anforderung", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-neue-anforderung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/08_hardware-neubeschaffung.json b/knowledge/08_hardware-neubeschaffung.json index f3bd445..04a8dae 100644 --- a/knowledge/08_hardware-neubeschaffung.json +++ b/knowledge/08_hardware-neubeschaffung.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-HARDWARE-NEUBESCHAFFUNG-SELECT", - "title": "Hardware-Neubeschaffung", - "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Hardware-Neubeschaffung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn ein neuer oder zusätzlicher PC, ein Notebook, Monitor, Tablet, Zubehör oder sonstige Arbeitsplatzhardware bestellt werden soll und kein einfacher Austausch eines defekten Bestandsgeräts vorliegt. Typische Ticketformulierungen sind: „Neues Notebook für neue Stelle“; „Zusätzlichen Monitor bestellen“; „Arbeitsplatz vollständig ausstatten“; „Spezialhardware beschaffen“. Nicht auswählen, wenn ein vorhandenes Gerät nur repariert oder umgesetzt werden soll; wenn Telekommunikationshardware oder ein neues Drucksystem beschafft wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Das zuständige Fachteam prüft technische Anforderungen; Bestellung, Budget und Vergabe liegen bei Beschaffung.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Hardware-Neubeschaffung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Arbeitsplatz und Endgeräte \u003e Hardware-Neubeschaffung" - ], - "keywords": [ - "Hardware beschaffen", - "Neubeschaffung", - "Bestellung", - "neuer PC", - "neues Notebook", - "zusätzlicher Monitor", - "Arbeitsplatzausstattung", - "Kauf", - "Hardware-Neubeschaffung", - "Arbeitsplatz und Endgeräte" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/hardware-neubeschaffung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-HARDWARE-NEUBESCHAFFUNG-SELECT", + "title": "Hardware-Neubeschaffung", + "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Hardware-Neubeschaffung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn ein neuer oder zusätzlicher PC, ein Notebook, Monitor, Tablet, Zubehör oder sonstige Arbeitsplatzhardware bestellt werden soll und kein einfacher Austausch eines defekten Bestandsgeräts vorliegt. Typische Ticketformulierungen sind: „Neues Notebook für neue Stelle“; „Zusätzlichen Monitor bestellen“; „Arbeitsplatz vollständig ausstatten“; „Spezialhardware beschaffen“. Nicht auswählen, wenn ein vorhandenes Gerät nur repariert oder umgesetzt werden soll; wenn Telekommunikationshardware oder ein neues Drucksystem beschafft wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Das zuständige Fachteam prüft technische Anforderungen; Bestellung, Budget und Vergabe liegen bei Beschaffung.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Hardware-Neubeschaffung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Arbeitsplatz und Endgeräte \u003e Hardware-Neubeschaffung" + ], + "keywords": [ + "Hardware beschaffen", + "Neubeschaffung", + "Bestellung", + "neuer PC", + "neues Notebook", + "zusätzlicher Monitor", + "Arbeitsplatzausstattung", + "Kauf", + "Hardware-Neubeschaffung", + "Arbeitsplatz und Endgeräte" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/hardware-neubeschaffung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/08_netzwerksegment-und-vlan.json b/knowledge/08_netzwerksegment-und-vlan.json index 3af3047..1801f72 100644 --- a/knowledge/08_netzwerksegment-und-vlan.json +++ b/knowledge/08_netzwerksegment-und-vlan.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-NETZWERKSEGMENT-UND-VLAN-SELECT", - "title": "Netzwerksegment und VLAN", - "text": "Auswahlziel: Netzwerk und Verbindungen \u003e Netzwerksegment und VLAN. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn VLANs, Netzsegmente, Subnetze, logische Trennungen oder Portzuordnungen neu eingerichtet, geändert oder analysiert werden sollen. Typische Ticketformulierungen sind: „Gerät in anderes VLAN verschieben“; „Neues Netzsegment anlegen“; „Port falschem VLAN zugeordnet“; „Subnetz für neues System“. Nicht auswählen, wenn nur eine einzelne Netzwerkdose ohne Verbindung ist oder eine Firewallregel zwischen bestehenden Segmenten fehlt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Sicherheitsrelevante Segmentierungsentscheidungen sind mit IT-Sicherheit und Leitung abzustimmen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e Netzwerksegment und VLAN“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Netzwerk und Verbindungen \u003e Netzwerksegment und VLAN" - ], - "keywords": [ - "VLAN", - "Netzsegment", - "Subnetz", - "Segmentierung", - "Switchport", - "Portzuordnung", - "Netztrennung", - "IP-Netz", - "Netzwerksegment und VLAN", - "Netzwerk und Verbindungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/netzwerksegment-und-vlan", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-NETZWERKSEGMENT-UND-VLAN-SELECT", + "title": "Netzwerksegment und VLAN", + "text": "Auswahlziel: Netzwerk und Verbindungen \u003e Netzwerksegment und VLAN. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn VLANs, Netzsegmente, Subnetze, logische Trennungen oder Portzuordnungen neu eingerichtet, geändert oder analysiert werden sollen. Typische Ticketformulierungen sind: „Gerät in anderes VLAN verschieben“; „Neues Netzsegment anlegen“; „Port falschem VLAN zugeordnet“; „Subnetz für neues System“. Nicht auswählen, wenn nur eine einzelne Netzwerkdose ohne Verbindung ist oder eine Firewallregel zwischen bestehenden Segmenten fehlt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Sicherheitsrelevante Segmentierungsentscheidungen sind mit IT-Sicherheit und Leitung abzustimmen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e Netzwerksegment und VLAN“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Netzwerk und Verbindungen \u003e Netzwerksegment und VLAN" + ], + "keywords": [ + "VLAN", + "Netzsegment", + "Subnetz", + "Segmentierung", + "Switchport", + "Portzuordnung", + "Netztrennung", + "IP-Netz", + "Netzwerksegment und VLAN", + "Netzwerk und Verbindungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/netzwerksegment-und-vlan", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/08_neues-drucksystem.json b/knowledge/08_neues-drucksystem.json index 3fb0b7a..5f7011c 100644 --- a/knowledge/08_neues-drucksystem.json +++ b/knowledge/08_neues-drucksystem.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-NEUES-DRUCKSYSTEM-SELECT", - "title": "Neues Drucksystem", - "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Neues Drucksystem. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn ein neuer Drucker, Kopierer, Scannerverbund oder ein standortweites Druckkonzept beschafft, ersetzt oder geplant werden soll. Typische Ticketformulierungen sind: „Neuen Kopierer beschaffen“; „Druckerkonzept für Standort“; „Zusätzlichen Netzwerkdrucker bestellen“; „Altgerät ersetzen“. Nicht auswählen, wenn nur ein vorhandenes Gerät gestört ist, ein Treiber fehlt oder Verbrauchsmaterial benötigt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Anforderungen werden gemeinsam mit Support und Infrastruktur bewertet; Kauf und Vertrag liegen bei Beschaffung.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Neues Drucksystem“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Drucken, Scannen und Kopieren \u003e Neues Drucksystem" - ], - "keywords": [ - "neues Drucksystem", - "Drucker beschaffen", - "Kopierer beschaffen", - "Druckkonzept", - "Neugerät", - "Ausschreibung Drucker", - "MFP kaufen", - "Neues Drucksystem", - "Drucken, Scannen und Kopieren" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/neues-drucksystem", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-NEUES-DRUCKSYSTEM-SELECT", + "title": "Neues Drucksystem", + "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Neues Drucksystem. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn ein neuer Drucker, Kopierer, Scannerverbund oder ein standortweites Druckkonzept beschafft, ersetzt oder geplant werden soll. Typische Ticketformulierungen sind: „Neuen Kopierer beschaffen“; „Druckerkonzept für Standort“; „Zusätzlichen Netzwerkdrucker bestellen“; „Altgerät ersetzen“. Nicht auswählen, wenn nur ein vorhandenes Gerät gestört ist, ein Treiber fehlt oder Verbrauchsmaterial benötigt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Anforderungen werden gemeinsam mit Support und Infrastruktur bewertet; Kauf und Vertrag liegen bei Beschaffung.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Neues Drucksystem“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Drucken, Scannen und Kopieren \u003e Neues Drucksystem" + ], + "keywords": [ + "neues Drucksystem", + "Drucker beschaffen", + "Kopierer beschaffen", + "Druckkonzept", + "Neugerät", + "Ausschreibung Drucker", + "MFP kaufen", + "Neues Drucksystem", + "Drucken, Scannen und Kopieren" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/neues-drucksystem", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/08_padagogische-lernplattformen.json b/knowledge/08_padagogische-lernplattformen.json index cd47311..c594c3a 100644 --- a/knowledge/08_padagogische-lernplattformen.json +++ b/knowledge/08_padagogische-lernplattformen.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-PADAGOGISCHE-LERNPLATTFORMEN-SELECT", - "title": "Pädagogische Lernplattformen", - "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Pädagogische Lernplattformen. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn Lernmanagementsysteme, digitale Klassenräume, Kursräume, Unterrichtsplattformen oder deren schulische Nutzung und Zugänge betroffen sind. Typische Ticketformulierungen sind: „Kursraum nicht sichtbar“; „Lernplattform nicht erreichbar“; „Schüler kann Aufgabe nicht abgeben“; „Klasse in Plattform anlegen“. Nicht auswählen, wenn eine allgemeine Microsoft-Office-Funktion, reine Netzstörung oder Schulverwaltungsanwendung gemeint ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei zentralem Hosting- oder Netzwerkproblem wird an Infrastruktur übergeben; fachliche Nutzung bleibt bei Team Schulen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Pädagogische Lernplattformen“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Schulspezifische IT-Systeme \u003e Pädagogische Lernplattformen" - ], - "keywords": [ - "Lernplattform", - "LMS", - "Moodle", - "digitaler Klassenraum", - "Kurs", - "Aufgabe", - "Unterrichtsplattform", - "Schülerzugang", - "Pädagogische Lernplattformen", - "Schulspezifische IT-Systeme" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/padagogische-lernplattformen", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-PADAGOGISCHE-LERNPLATTFORMEN-SELECT", + "title": "Pädagogische Lernplattformen", + "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Pädagogische Lernplattformen. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn Lernmanagementsysteme, digitale Klassenräume, Kursräume, Unterrichtsplattformen oder deren schulische Nutzung und Zugänge betroffen sind. Typische Ticketformulierungen sind: „Kursraum nicht sichtbar“; „Lernplattform nicht erreichbar“; „Schüler kann Aufgabe nicht abgeben“; „Klasse in Plattform anlegen“. Nicht auswählen, wenn eine allgemeine Microsoft-Office-Funktion, reine Netzstörung oder Schulverwaltungsanwendung gemeint ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei zentralem Hosting- oder Netzwerkproblem wird an Infrastruktur übergeben; fachliche Nutzung bleibt bei Team Schulen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Pädagogische Lernplattformen“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Schulspezifische IT-Systeme \u003e Pädagogische Lernplattformen" + ], + "keywords": [ + "Lernplattform", + "LMS", + "Moodle", + "digitaler Klassenraum", + "Kurs", + "Aufgabe", + "Unterrichtsplattform", + "Schülerzugang", + "Pädagogische Lernplattformen", + "Schulspezifische IT-Systeme" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/padagogische-lernplattformen", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/08_rechnung-und-kostenstelle.json b/knowledge/08_rechnung-und-kostenstelle.json index 2d7e6c0..878092c 100644 --- a/knowledge/08_rechnung-und-kostenstelle.json +++ b/knowledge/08_rechnung-und-kostenstelle.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-RECHNUNG-UND-KOSTENSTELLE-SELECT", - "title": "Rechnung und Kostenstelle", - "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Rechnung und Kostenstelle. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn eine IT-Rechnung geprüft, sachlich zugeordnet, kontiert, beanstandet oder einer Kostenstelle, Bestellung oder einem Vertrag zugeordnet werden muss. Typische Ticketformulierungen sind: „Rechnung prüfen“; „Kostenstelle korrigieren“; „Bestellbezug fehlt“; „Falscher Rechnungsbetrag“. Nicht auswählen, wenn ein Angebot vorliegt, eine Lizenz erst bestellt werden soll oder ein technisches Problem mit dem gelieferten Produkt besteht. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Leistungsbestätigung kann durch das zuständige Fachteam erforderlich sein.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Rechnung und Kostenstelle“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Beschaffung, Verträge und Lizenzen \u003e Rechnung und Kostenstelle" - ], - "keywords": [ - "Rechnung", - "Kostenstelle", - "Kontierung", - "Bestellnummer", - "Rechnungsprüfung", - "Zahlung", - "Gutschrift", - "Betrag falsch", - "Rechnung und Kostenstelle", - "Beschaffung, Verträge und Lizenzen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/rechnung-und-kostenstelle", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-RECHNUNG-UND-KOSTENSTELLE-SELECT", + "title": "Rechnung und Kostenstelle", + "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Rechnung und Kostenstelle. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn eine IT-Rechnung geprüft, sachlich zugeordnet, kontiert, beanstandet oder einer Kostenstelle, Bestellung oder einem Vertrag zugeordnet werden muss. Typische Ticketformulierungen sind: „Rechnung prüfen“; „Kostenstelle korrigieren“; „Bestellbezug fehlt“; „Falscher Rechnungsbetrag“. Nicht auswählen, wenn ein Angebot vorliegt, eine Lizenz erst bestellt werden soll oder ein technisches Problem mit dem gelieferten Produkt besteht. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Leistungsbestätigung kann durch das zuständige Fachteam erforderlich sein.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Rechnung und Kostenstelle“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Beschaffung, Verträge und Lizenzen \u003e Rechnung und Kostenstelle" + ], + "keywords": [ + "Rechnung", + "Kostenstelle", + "Kontierung", + "Bestellnummer", + "Rechnungsprüfung", + "Zahlung", + "Gutschrift", + "Betrag falsch", + "Rechnung und Kostenstelle", + "Beschaffung, Verträge und Lizenzen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/rechnung-und-kostenstelle", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/08_telekommunikationsbeschaffung.json b/knowledge/08_telekommunikationsbeschaffung.json index 527a9e1..e29c72a 100644 --- a/knowledge/08_telekommunikationsbeschaffung.json +++ b/knowledge/08_telekommunikationsbeschaffung.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-TELEKOMMUNIKATIONSBESCHAFFUNG-SELECT", - "title": "Telekommunikationsbeschaffung", - "text": "Auswahlziel: Telefonie und Kommunikation \u003e Telekommunikationsbeschaffung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn neue Telefone, Smartphones, SIM-Karten, Mobilfunkverträge, Headsets oder andere Telekommunikationsleistungen beschafft, verlängert oder wirtschaftlich bewertet werden sollen. Typische Ticketformulierungen sind: „Neues Diensthandy bestellen“; „Mobilfunkvertrag abschließen“; „Telefone für neue Arbeitsplätze beschaffen“; „Headsets in größerer Stückzahl kaufen“. Nicht auswählen, wenn ein vorhandenes Gerät nur defekt ist, eine Rufumleitung geändert oder eine technische Störung behoben werden muss. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Spezifikation erfolgt gemeinsam mit Support und Telekommunikation.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Telekommunikationsbeschaffung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Telefonie und Kommunikation \u003e Telekommunikationsbeschaffung" - ], - "keywords": [ - "Telekommunikation Beschaffung", - "Telefon bestellen", - "Diensthandy bestellen", - "Mobilfunkvertrag", - "SIM bestellen", - "Headset beschaffen", - "Kauf", - "Telekommunikationsbeschaffung", - "Telefonie und Kommunikation" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/telekommunikationsbeschaffung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-TELEKOMMUNIKATIONSBESCHAFFUNG-SELECT", + "title": "Telekommunikationsbeschaffung", + "text": "Auswahlziel: Telefonie und Kommunikation \u003e Telekommunikationsbeschaffung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn neue Telefone, Smartphones, SIM-Karten, Mobilfunkverträge, Headsets oder andere Telekommunikationsleistungen beschafft, verlängert oder wirtschaftlich bewertet werden sollen. Typische Ticketformulierungen sind: „Neues Diensthandy bestellen“; „Mobilfunkvertrag abschließen“; „Telefone für neue Arbeitsplätze beschaffen“; „Headsets in größerer Stückzahl kaufen“. Nicht auswählen, wenn ein vorhandenes Gerät nur defekt ist, eine Rufumleitung geändert oder eine technische Störung behoben werden muss. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Spezifikation erfolgt gemeinsam mit Support und Telekommunikation.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Telekommunikationsbeschaffung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Telefonie und Kommunikation \u003e Telekommunikationsbeschaffung" + ], + "keywords": [ + "Telekommunikation Beschaffung", + "Telefon bestellen", + "Diensthandy bestellen", + "Mobilfunkvertrag", + "SIM bestellen", + "Headset beschaffen", + "Kauf", + "Telekommunikationsbeschaffung", + "Telefonie und Kommunikation" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/telekommunikationsbeschaffung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/09_container-und-docker.json b/knowledge/09_container-und-docker.json index bbe929b..315b237 100644 --- a/knowledge/09_container-und-docker.json +++ b/knowledge/09_container-und-docker.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-CONTAINER-UND-DOCKER-SELECT", - "title": "Container und Docker", - "text": "Auswahlziel: Server, Backend und Plattformen \u003e Container und Docker. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Docker-Hosts, Container, Images, Registries, Compose-Stacks oder containerbasierte Laufzeitumgebungen bereitgestellt, gewartet oder analysiert werden sollen. Typische Ticketformulierungen sind: „Docker-Container startet nicht“; „Image bereitstellen“; „Registry-Zugriff fehlerhaft“; „Compose-Stack deployen“. Nicht auswählen, wenn ein Kubernetes-Cluster betroffen ist, eine klassische VM benötigt wird oder nur die Fachanwendung im Container fachlich fehlerhaft ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei Sicherheitslücken in Images ist zusätzlich IT-Sicherheit relevant.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Container und Docker“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Server, Backend und Plattformen \u003e Container und Docker" - ], - "keywords": [ - "Docker", - "Container", - "Image", - "Registry", - "Docker Compose", - "Container Runtime", - "Container startet nicht", - "Containerplattform", - "Container und Docker", - "Server, Backend und Plattformen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/container-und-docker", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-CONTAINER-UND-DOCKER-SELECT", + "title": "Container und Docker", + "text": "Auswahlziel: Server, Backend und Plattformen \u003e Container und Docker. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Docker-Hosts, Container, Images, Registries, Compose-Stacks oder containerbasierte Laufzeitumgebungen bereitgestellt, gewartet oder analysiert werden sollen. Typische Ticketformulierungen sind: „Docker-Container startet nicht“; „Image bereitstellen“; „Registry-Zugriff fehlerhaft“; „Compose-Stack deployen“. Nicht auswählen, wenn ein Kubernetes-Cluster betroffen ist, eine klassische VM benötigt wird oder nur die Fachanwendung im Container fachlich fehlerhaft ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei Sicherheitslücken in Images ist zusätzlich IT-Sicherheit relevant.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Container und Docker“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Server, Backend und Plattformen \u003e Container und Docker" + ], + "keywords": [ + "Docker", + "Container", + "Image", + "Registry", + "Docker Compose", + "Container Runtime", + "Container startet nicht", + "Containerplattform", + "Container und Docker", + "Server, Backend und Plattformen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/container-und-docker", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/09_fachanwendung-einfuhrung-einer-anwendung.json b/knowledge/09_fachanwendung-einfuhrung-einer-anwendung.json index 6fa0cb4..1baecc2 100644 --- a/knowledge/09_fachanwendung-einfuhrung-einer-anwendung.json +++ b/knowledge/09_fachanwendung-einfuhrung-einer-anwendung.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-FACHANWENDUNG-EINFUHRUNG-EINER-ANWENDUNG-SELECT", - "title": "Fachanwendung – Einführung einer Anwendung", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Einführung einer Anwendung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn ein neues Fachverfahren oder eine neue fachliche Software ausgewählt, beschafft, konfiguriert, migriert, getestet, geschult und produktiv eingeführt werden soll. Typische Ticketformulierungen sind: „Neues Fachverfahren einführen“; „Migration auf neue Anwendung“; „Pilotbetrieb einer Fachsoftware“; „Ablösung des Altsystems“. Nicht auswählen, wenn eine bestehende Anwendung nur aktualisiert oder erweitert wird oder lediglich Standardsoftware an einem Arbeitsplatz installiert werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Beschaffung, Datenschutz, Informationssicherheit und Infrastruktur sind je nach Umfang als Schnittstellen einzubeziehen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Einführung einer Anwendung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Einführung einer Anwendung" - ], - "keywords": [ - "Einführung", - "neue Anwendung", - "neues Fachverfahren", - "Migration", - "Ablösung", - "Rollout", - "Pilot", - "Implementierung", - "Projekt Fachsoftware", - "Fachanwendung – Einführung einer Anwendung", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-einfuhrung-einer-anwendung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-FACHANWENDUNG-EINFUHRUNG-EINER-ANWENDUNG-SELECT", + "title": "Fachanwendung – Einführung einer Anwendung", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Einführung einer Anwendung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn ein neues Fachverfahren oder eine neue fachliche Software ausgewählt, beschafft, konfiguriert, migriert, getestet, geschult und produktiv eingeführt werden soll. Typische Ticketformulierungen sind: „Neues Fachverfahren einführen“; „Migration auf neue Anwendung“; „Pilotbetrieb einer Fachsoftware“; „Ablösung des Altsystems“. Nicht auswählen, wenn eine bestehende Anwendung nur aktualisiert oder erweitert wird oder lediglich Standardsoftware an einem Arbeitsplatz installiert werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Beschaffung, Datenschutz, Informationssicherheit und Infrastruktur sind je nach Umfang als Schnittstellen einzubeziehen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Einführung einer Anwendung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Einführung einer Anwendung" + ], + "keywords": [ + "Einführung", + "neue Anwendung", + "neues Fachverfahren", + "Migration", + "Ablösung", + "Rollout", + "Pilot", + "Implementierung", + "Projekt Fachsoftware", + "Fachanwendung – Einführung einer Anwendung", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-einfuhrung-einer-anwendung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/09_inventarisierung.json b/knowledge/09_inventarisierung.json index bb0e995..f555e56 100644 --- a/knowledge/09_inventarisierung.json +++ b/knowledge/09_inventarisierung.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-INVENTARISIERUNG-SELECT", - "title": "Inventarisierung", - "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Inventarisierung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn ein IT-Asset neu inventarisiert, einer Person oder einem Standort zugeordnet, umgebucht, korrigiert oder im Bestand dokumentiert werden soll. Typische Ticketformulierungen sind: „Inventarnummer anlegen“; „Gerät anderem Standort zuordnen“; „Asset-Daten korrigieren“; „Bestand übernehmen“. Nicht auswählen, wenn ein Gerät physisch zurückgegeben, technisch repariert oder endgültig entsorgt werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Physische Rücknahme und Datenlöschung liegen beim Support; kaufmännischer Asset-Status bei Beschaffung.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Inventarisierung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Beschaffung, Verträge und Lizenzen \u003e Inventarisierung" - ], - "keywords": [ - "Inventarisierung", - "Inventarnummer", - "Asset", - "Bestand", - "Gerätezuordnung", - "Umbuchung", - "Standortzuordnung", - "Anlagegut", - "Beschaffung, Verträge und Lizenzen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/inventarisierung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-INVENTARISIERUNG-SELECT", + "title": "Inventarisierung", + "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Inventarisierung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn ein IT-Asset neu inventarisiert, einer Person oder einem Standort zugeordnet, umgebucht, korrigiert oder im Bestand dokumentiert werden soll. Typische Ticketformulierungen sind: „Inventarnummer anlegen“; „Gerät anderem Standort zuordnen“; „Asset-Daten korrigieren“; „Bestand übernehmen“. Nicht auswählen, wenn ein Gerät physisch zurückgegeben, technisch repariert oder endgültig entsorgt werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Physische Rücknahme und Datenlöschung liegen beim Support; kaufmännischer Asset-Status bei Beschaffung.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Inventarisierung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Beschaffung, Verträge und Lizenzen \u003e Inventarisierung" + ], + "keywords": [ + "Inventarisierung", + "Inventarnummer", + "Asset", + "Bestand", + "Gerätezuordnung", + "Umbuchung", + "Standortzuordnung", + "Anlagegut", + "Beschaffung, Verträge und Lizenzen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/inventarisierung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/09_protokollierung-und-auswertung.json b/knowledge/09_protokollierung-und-auswertung.json index 3165588..f7dd353 100644 --- a/knowledge/09_protokollierung-und-auswertung.json +++ b/knowledge/09_protokollierung-und-auswertung.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-PROTOKOLLIERUNG-UND-AUSWERTUNG-SELECT", - "title": "Protokollierung und Auswertung", - "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Protokollierung und Auswertung. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn technische Logs, Auditdaten, zentrale Protokollierung, SIEM-Auswertung, Nachvollziehbarkeit oder sicherheitsbezogene Ereignisanalyse benötigt oder gestört sind. Typische Ticketformulierungen sind: „Logdaten für Analyse bereitstellen“; „Auditprotokoll fehlt“; „SIEM-Regel anpassen“; „Anmeldeereignisse auswerten“. Nicht auswählen, wenn nur eine fachliche Statistik, ein normaler Monitoring-Check oder ein konkreter aktiver Sicherheitsvorfall ohne Analyseauftrag gemeldet wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Datenschutz, Zweckbindung und Aufbewahrungsregeln sind bei personenbezogenen Protokollen zu beachten.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Protokollierung und Auswertung“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "IT-Sicherheit und Datensicherung \u003e Protokollierung und Auswertung" - ], - "keywords": [ - "Protokollierung", - "Log", - "Audit", - "SIEM", - "Ereignisprotokoll", - "Event Log", - "Nachvollziehbarkeit", - "Loganalyse", - "Security Event", - "Protokollierung und Auswertung", - "IT-Sicherheit und Datensicherung" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/protokollierung-und-auswertung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-PROTOKOLLIERUNG-UND-AUSWERTUNG-SELECT", + "title": "Protokollierung und Auswertung", + "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Protokollierung und Auswertung. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn technische Logs, Auditdaten, zentrale Protokollierung, SIEM-Auswertung, Nachvollziehbarkeit oder sicherheitsbezogene Ereignisanalyse benötigt oder gestört sind. Typische Ticketformulierungen sind: „Logdaten für Analyse bereitstellen“; „Auditprotokoll fehlt“; „SIEM-Regel anpassen“; „Anmeldeereignisse auswerten“. Nicht auswählen, wenn nur eine fachliche Statistik, ein normaler Monitoring-Check oder ein konkreter aktiver Sicherheitsvorfall ohne Analyseauftrag gemeldet wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Datenschutz, Zweckbindung und Aufbewahrungsregeln sind bei personenbezogenen Protokollen zu beachten.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Protokollierung und Auswertung“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "IT-Sicherheit und Datensicherung \u003e Protokollierung und Auswertung" + ], + "keywords": [ + "Protokollierung", + "Log", + "Audit", + "SIEM", + "Ereignisprotokoll", + "Event Log", + "Nachvollziehbarkeit", + "Loganalyse", + "Security Event", + "Protokollierung und Auswertung", + "IT-Sicherheit und Datensicherung" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/protokollierung-und-auswertung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/09_prufungs-und-klausursysteme.json b/knowledge/09_prufungs-und-klausursysteme.json index 64b32d0..a9bd528 100644 --- a/knowledge/09_prufungs-und-klausursysteme.json +++ b/knowledge/09_prufungs-und-klausursysteme.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-PRUFUNGS-UND-KLAUSURSYSTEME-SELECT", - "title": "Prüfungs- und Klausursysteme", - "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Prüfungs- und Klausursysteme. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn digitale Prüfungsumgebungen, Klausurclients, Prüfungsaccounts, sichere Browser, Prüfungsnetz oder technische Vorbereitung einer digitalen Prüfung betroffen sind. Typische Ticketformulierungen sind: „Prüfungsbrowser startet nicht“; „Klausuraccount fehlt“; „Digitale Prüfung vorbereiten“; „Prüfungsnetz gestört“. Nicht auswählen, wenn eine normale Lernplattformaufgabe oder allgemeine Computerraumstörung ohne Prüfungsbezug vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Tickets mit unmittelbarem Prüfungstermin sind zeitkritisch und entsprechend zu priorisieren.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Prüfungs- und Klausursysteme“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Schulspezifische IT-Systeme \u003e Prüfungs- und Klausursysteme" - ], - "keywords": [ - "Prüfungssystem", - "Klausursystem", - "Prüfungsbrowser", - "Safe Exam Browser", - "digitale Prüfung", - "Klausuraccount", - "Prüfungsnetz", - "Prüfungs- und Klausursysteme", - "Schulspezifische IT-Systeme" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/prufungs-und-klausursysteme", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-PRUFUNGS-UND-KLAUSURSYSTEME-SELECT", + "title": "Prüfungs- und Klausursysteme", + "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Prüfungs- und Klausursysteme. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn digitale Prüfungsumgebungen, Klausurclients, Prüfungsaccounts, sichere Browser, Prüfungsnetz oder technische Vorbereitung einer digitalen Prüfung betroffen sind. Typische Ticketformulierungen sind: „Prüfungsbrowser startet nicht“; „Klausuraccount fehlt“; „Digitale Prüfung vorbereiten“; „Prüfungsnetz gestört“. Nicht auswählen, wenn eine normale Lernplattformaufgabe oder allgemeine Computerraumstörung ohne Prüfungsbezug vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Tickets mit unmittelbarem Prüfungstermin sind zeitkritisch und entsprechend zu priorisieren.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Prüfungs- und Klausursysteme“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Schulspezifische IT-Systeme \u003e Prüfungs- und Klausursysteme" + ], + "keywords": [ + "Prüfungssystem", + "Klausursystem", + "Prüfungsbrowser", + "Safe Exam Browser", + "digitale Prüfung", + "Klausuraccount", + "Prüfungsnetz", + "Prüfungs- und Klausursysteme", + "Schulspezifische IT-Systeme" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/prufungs-und-klausursysteme", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/09_ruckgabe-und-aussonderung.json b/knowledge/09_ruckgabe-und-aussonderung.json index 3762192..0d35a19 100644 --- a/knowledge/09_ruckgabe-und-aussonderung.json +++ b/knowledge/09_ruckgabe-und-aussonderung.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-RUCKGABE-UND-AUSSONDERUNG-SELECT", - "title": "Rückgabe und Aussonderung", - "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Rückgabe und Aussonderung. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn Geräte bei Austritt, Austausch oder Bestandsbereinigung zurückgegeben, inventarisch abgeglichen, datenschutzgerecht gelöscht, eingelagert, wiederverwendet oder ausgesondert werden sollen. Typische Ticketformulierungen sind: „Notebook bei Austritt zurückgeben“; „Altgerät aussondern“; „Datenträger vor Entsorgung löschen“; „Gerät ins Lager zurücknehmen“. Nicht auswählen, wenn ein Gerät lediglich defekt ist und weiter genutzt werden soll; wenn nur eine Rechnung oder Inventarnummer korrigiert wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Beschaffung wird beteiligt, wenn Inventarstatus, Verwertung oder kaufmännische Aussonderung zu ändern sind.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Rückgabe und Aussonderung“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Arbeitsplatz und Endgeräte \u003e Rückgabe und Aussonderung" - ], - "keywords": [ - "Rückgabe", - "Aussonderung", - "Altgerät", - "Entsorgung", - "Gerät zurückgeben", - "Austritt", - "Daten löschen", - "Wiederverwendung", - "Inventar", - "Rückgabe und Aussonderung", - "Arbeitsplatz und Endgeräte" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/ruckgabe-und-aussonderung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-RUCKGABE-UND-AUSSONDERUNG-SELECT", + "title": "Rückgabe und Aussonderung", + "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Rückgabe und Aussonderung. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn Geräte bei Austritt, Austausch oder Bestandsbereinigung zurückgegeben, inventarisch abgeglichen, datenschutzgerecht gelöscht, eingelagert, wiederverwendet oder ausgesondert werden sollen. Typische Ticketformulierungen sind: „Notebook bei Austritt zurückgeben“; „Altgerät aussondern“; „Datenträger vor Entsorgung löschen“; „Gerät ins Lager zurücknehmen“. Nicht auswählen, wenn ein Gerät lediglich defekt ist und weiter genutzt werden soll; wenn nur eine Rechnung oder Inventarnummer korrigiert wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Beschaffung wird beteiligt, wenn Inventarstatus, Verwertung oder kaufmännische Aussonderung zu ändern sind.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Rückgabe und Aussonderung“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Arbeitsplatz und Endgeräte \u003e Rückgabe und Aussonderung" + ], + "keywords": [ + "Rückgabe", + "Aussonderung", + "Altgerät", + "Entsorgung", + "Gerät zurückgeben", + "Austritt", + "Daten löschen", + "Wiederverwendung", + "Inventar", + "Rückgabe und Aussonderung", + "Arbeitsplatz und Endgeräte" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/ruckgabe-und-aussonderung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/10_haushalts-und-budgetplanung.json b/knowledge/10_haushalts-und-budgetplanung.json index 773ccfd..d930b6e 100644 --- a/knowledge/10_haushalts-und-budgetplanung.json +++ b/knowledge/10_haushalts-und-budgetplanung.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-HAUSHALTS-UND-BUDGETPLANUNG-SELECT", - "title": "Haushalts- und Budgetplanung", - "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Haushalts- und Budgetplanung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn IT-Budgets, Haushaltsansätze, Mittelbedarfe, Verpflichtungsermächtigungen, Kostenprognosen oder mehrjährige Finanzplanungen erstellt und abgestimmt werden sollen. Typische Ticketformulierungen sind: „Budget für nächstes Jahr planen“; „Mittelbedarf melden“; „Kostenprognose erstellen“; „Haushaltsansatz für IT-Projekt“. Nicht auswählen, wenn es um eine einzelne Bestellung, Rechnung oder technische Projektplanung ohne Budgetbezug geht. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Fachteams liefern Bedarfe und technische Mengen; Leitung und kaufmännische Stelle konsolidieren.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Haushalts- und Budgetplanung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Beschaffung, Verträge und Lizenzen \u003e Haushalts- und Budgetplanung" - ], - "keywords": [ - "Haushalt", - "Budget", - "Mittelbedarf", - "Finanzplanung", - "Kostenprognose", - "Haushaltsansatz", - "Budgetplanung", - "Investitionsplanung", - "Haushalts- und Budgetplanung", - "Beschaffung, Verträge und Lizenzen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/haushalts-und-budgetplanung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-HAUSHALTS-UND-BUDGETPLANUNG-SELECT", + "title": "Haushalts- und Budgetplanung", + "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Haushalts- und Budgetplanung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn IT-Budgets, Haushaltsansätze, Mittelbedarfe, Verpflichtungsermächtigungen, Kostenprognosen oder mehrjährige Finanzplanungen erstellt und abgestimmt werden sollen. Typische Ticketformulierungen sind: „Budget für nächstes Jahr planen“; „Mittelbedarf melden“; „Kostenprognose erstellen“; „Haushaltsansatz für IT-Projekt“. Nicht auswählen, wenn es um eine einzelne Bestellung, Rechnung oder technische Projektplanung ohne Budgetbezug geht. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Fachteams liefern Bedarfe und technische Mengen; Leitung und kaufmännische Stelle konsolidieren.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Haushalts- und Budgetplanung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Beschaffung, Verträge und Lizenzen \u003e Haushalts- und Budgetplanung" + ], + "keywords": [ + "Haushalt", + "Budget", + "Mittelbedarf", + "Finanzplanung", + "Kostenprognose", + "Haushaltsansatz", + "Budgetplanung", + "Investitionsplanung", + "Haushalts- und Budgetplanung", + "Beschaffung, Verträge und Lizenzen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/haushalts-und-budgetplanung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/10_kubernetes.json b/knowledge/10_kubernetes.json index f9217eb..7523284 100644 --- a/knowledge/10_kubernetes.json +++ b/knowledge/10_kubernetes.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-KUBERNETES-SELECT", - "title": "Kubernetes", - "text": "Auswahlziel: Server, Backend und Plattformen \u003e Kubernetes. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Kubernetes-Cluster, Nodes, Namespaces, Deployments, Services, Ingress, Pods oder clusterbezogene Plattformdienste betroffen sind. Typische Ticketformulierungen sind: „Pod startet nicht“; „Deployment fehlerhaft“; „Namespace anlegen“; „Kubernetes-Cluster erweitern“. Nicht auswählen, wenn nur ein einzelner Docker-Host ohne Kubernetes, eine klassische VM oder eine fachliche Anwendung ohne Clusterbezug gemeint ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Anwendungsdeployment kann mit DevOps zusammenhängen; Clusterbetrieb bleibt in dieser Kategorie.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Kubernetes“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Server, Backend und Plattformen \u003e Kubernetes" - ], - "keywords": [ - "Kubernetes", - "K8s", - "Pod", - "Deployment", - "Namespace", - "Ingress", - "Service", - "Node", - "Cluster", - "Server, Backend und Plattformen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/kubernetes", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-KUBERNETES-SELECT", + "title": "Kubernetes", + "text": "Auswahlziel: Server, Backend und Plattformen \u003e Kubernetes. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Kubernetes-Cluster, Nodes, Namespaces, Deployments, Services, Ingress, Pods oder clusterbezogene Plattformdienste betroffen sind. Typische Ticketformulierungen sind: „Pod startet nicht“; „Deployment fehlerhaft“; „Namespace anlegen“; „Kubernetes-Cluster erweitern“. Nicht auswählen, wenn nur ein einzelner Docker-Host ohne Kubernetes, eine klassische VM oder eine fachliche Anwendung ohne Clusterbezug gemeint ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Anwendungsdeployment kann mit DevOps zusammenhängen; Clusterbetrieb bleibt in dieser Kategorie.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Kubernetes“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Server, Backend und Plattformen \u003e Kubernetes" + ], + "keywords": [ + "Kubernetes", + "K8s", + "Pod", + "Deployment", + "Namespace", + "Ingress", + "Service", + "Node", + "Cluster", + "Server, Backend und Plattformen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/kubernetes", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/10_microsoft-word.json b/knowledge/10_microsoft-word.json index 33d4c7f..ee8ab7b 100644 --- a/knowledge/10_microsoft-word.json +++ b/knowledge/10_microsoft-word.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-MICROSOFT-WORD-SELECT", - "title": "Microsoft Word", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Microsoft Word. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Microsoft Word bei Dokumentbearbeitung, Formatierung, Feldern, Inhaltsverzeichnissen, Dokumentenschutz oder programmspezifischen Funktionen fehlerhaft arbeitet oder Unterstützung benötigt wird. Typische Ticketformulierungen sind: „Word-Dokument lässt sich nicht bearbeiten“; „Formatierung springt“; „Inhaltsverzeichnis aktualisiert nicht“; „Word stürzt bei Dokument ab“. Nicht auswählen, wenn eine allgemeine Office-Installation fehlt, eine organisationsweite Vorlage geändert werden soll, ein Makro betroffen ist oder das Betriebssystem selbst fehlerhaft ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Vorlagen, Makros und Serienbriefe besitzen eigene Kategorien, wenn diese der eigentliche Kern des Tickets sind.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Microsoft Word“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Microsoft Word" - ], - "keywords": [ - "Word", - "Microsoft Word", - "DOCX", - "Dokument", - "Formatierung", - "Inhaltsverzeichnis", - "Serienbrief", - "Textverarbeitung", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/microsoft-word", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-MICROSOFT-WORD-SELECT", + "title": "Microsoft Word", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Microsoft Word. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Microsoft Word bei Dokumentbearbeitung, Formatierung, Feldern, Inhaltsverzeichnissen, Dokumentenschutz oder programmspezifischen Funktionen fehlerhaft arbeitet oder Unterstützung benötigt wird. Typische Ticketformulierungen sind: „Word-Dokument lässt sich nicht bearbeiten“; „Formatierung springt“; „Inhaltsverzeichnis aktualisiert nicht“; „Word stürzt bei Dokument ab“. Nicht auswählen, wenn eine allgemeine Office-Installation fehlt, eine organisationsweite Vorlage geändert werden soll, ein Makro betroffen ist oder das Betriebssystem selbst fehlerhaft ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Vorlagen, Makros und Serienbriefe besitzen eigene Kategorien, wenn diese der eigentliche Kern des Tickets sind.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Microsoft Word“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Microsoft Word" + ], + "keywords": [ + "Word", + "Microsoft Word", + "DOCX", + "Dokument", + "Formatierung", + "Inhaltsverzeichnis", + "Serienbrief", + "Textverarbeitung", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/microsoft-word", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/10_schulverwaltungsanwendungen.json b/knowledge/10_schulverwaltungsanwendungen.json index 5b3d1dd..30a72ff 100644 --- a/knowledge/10_schulverwaltungsanwendungen.json +++ b/knowledge/10_schulverwaltungsanwendungen.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-SCHULVERWALTUNGSANWENDUNGEN-SELECT", - "title": "Schulverwaltungsanwendungen", - "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Schulverwaltungsanwendungen. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn eine speziell in Schulen eingesetzte Verwaltungsanwendung für Stundenplan, Schülerverwaltung, Zeugnisse, Vertretung oder Schulorganisation gestört ist oder Unterstützung benötigt wird. Typische Ticketformulierungen sind: „Zeugnisprogramm zeigt Fehler“; „Stundenplananwendung funktioniert nicht“; „Schülerverwaltungssoftware gestört“; „Vertretungsplan synchronisiert nicht“. Nicht auswählen, wenn eine allgemeine kommunale Fachanwendung, Lernplattform oder reine Netzwerkstörung betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Fachanwendungs-Know-how kann mittelfristig an Fachanwendungen überführt werden; zentrale Plattformursachen an Infrastruktur.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Schulverwaltungsanwendungen“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Schulspezifische IT-Systeme \u003e Schulverwaltungsanwendungen" - ], - "keywords": [ - "Schulverwaltungsanwendung", - "Schülerverwaltung", - "Zeugnisprogramm", - "Stundenplan", - "Vertretungsplan", - "Schulsoftware", - "Schulorganisation", - "Schulverwaltungsanwendungen", - "Schulspezifische IT-Systeme" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/schulverwaltungsanwendungen", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-SCHULVERWALTUNGSANWENDUNGEN-SELECT", + "title": "Schulverwaltungsanwendungen", + "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Schulverwaltungsanwendungen. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn eine speziell in Schulen eingesetzte Verwaltungsanwendung für Stundenplan, Schülerverwaltung, Zeugnisse, Vertretung oder Schulorganisation gestört ist oder Unterstützung benötigt wird. Typische Ticketformulierungen sind: „Zeugnisprogramm zeigt Fehler“; „Stundenplananwendung funktioniert nicht“; „Schülerverwaltungssoftware gestört“; „Vertretungsplan synchronisiert nicht“. Nicht auswählen, wenn eine allgemeine kommunale Fachanwendung, Lernplattform oder reine Netzwerkstörung betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Fachanwendungs-Know-how kann mittelfristig an Fachanwendungen überführt werden; zentrale Plattformursachen an Infrastruktur.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Schulverwaltungsanwendungen“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Schulspezifische IT-Systeme \u003e Schulverwaltungsanwendungen" + ], + "keywords": [ + "Schulverwaltungsanwendung", + "Schülerverwaltung", + "Zeugnisprogramm", + "Stundenplan", + "Vertretungsplan", + "Schulsoftware", + "Schulorganisation", + "Schulverwaltungsanwendungen", + "Schulspezifische IT-Systeme" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/schulverwaltungsanwendungen", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/11_devops-und-automatisierung.json b/knowledge/11_devops-und-automatisierung.json index 6e894a6..c75b8ea 100644 --- a/knowledge/11_devops-und-automatisierung.json +++ b/knowledge/11_devops-und-automatisierung.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-DEVOPS-UND-AUTOMATISIERUNG-SELECT", - "title": "DevOps und Automatisierung", - "text": "Auswahlziel: Server, Backend und Plattformen \u003e DevOps und Automatisierung. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn CI/CD-Pipelines, automatisierte Deployments, Infrastrukturcode, Konfigurationsmanagement, Build-Prozesse oder technische Automatisierungen erstellt oder gestört sind. Typische Ticketformulierungen sind: „Pipeline schlägt fehl“; „Deployment automatisieren“; „Ansible-Playbook anpassen“; „Infrastructure as Code bereitstellen“. Nicht auswählen, wenn nur ein Kubernetes-Pod ausfällt, eine normale Softwareinstallation am Arbeitsplatz benötigt wird oder ein fachlicher Workflow innerhalb einer Anwendung gemeint ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Fachanwendungsbezogene Releaseentscheidungen liegen bei Fachanwendungen; technische Delivery-Plattform bei Infrastruktur.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e DevOps und Automatisierung“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Server, Backend und Plattformen \u003e DevOps und Automatisierung" - ], - "keywords": [ - "DevOps", - "CI/CD", - "Pipeline", - "Deployment", - "Ansible", - "Terraform", - "Infrastructure as Code", - "Automation", - "Build", - "DevOps und Automatisierung", - "Server, Backend und Plattformen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/devops-und-automatisierung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-DEVOPS-UND-AUTOMATISIERUNG-SELECT", + "title": "DevOps und Automatisierung", + "text": "Auswahlziel: Server, Backend und Plattformen \u003e DevOps und Automatisierung. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn CI/CD-Pipelines, automatisierte Deployments, Infrastrukturcode, Konfigurationsmanagement, Build-Prozesse oder technische Automatisierungen erstellt oder gestört sind. Typische Ticketformulierungen sind: „Pipeline schlägt fehl“; „Deployment automatisieren“; „Ansible-Playbook anpassen“; „Infrastructure as Code bereitstellen“. Nicht auswählen, wenn nur ein Kubernetes-Pod ausfällt, eine normale Softwareinstallation am Arbeitsplatz benötigt wird oder ein fachlicher Workflow innerhalb einer Anwendung gemeint ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Fachanwendungsbezogene Releaseentscheidungen liegen bei Fachanwendungen; technische Delivery-Plattform bei Infrastruktur.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e DevOps und Automatisierung“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Server, Backend und Plattformen \u003e DevOps und Automatisierung" + ], + "keywords": [ + "DevOps", + "CI/CD", + "Pipeline", + "Deployment", + "Ansible", + "Terraform", + "Infrastructure as Code", + "Automation", + "Build", + "DevOps und Automatisierung", + "Server, Backend und Plattformen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/devops-und-automatisierung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/11_microsoft-excel.json b/knowledge/11_microsoft-excel.json index 63f02e6..3ea430b 100644 --- a/knowledge/11_microsoft-excel.json +++ b/knowledge/11_microsoft-excel.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-MICROSOFT-EXCEL-SELECT", - "title": "Microsoft Excel", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Microsoft Excel. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Microsoft Excel bei Tabellen, Formeln, Pivot-Auswertungen, Datenimporten oder programmspezifischen Funktionen fehlerhaft arbeitet oder fachnahe Unterstützung benötigt wird. Typische Ticketformulierungen sind: „Excel-Formel funktioniert nicht“; „Datei öffnet fehlerhaft“; „Pivot-Tabelle aktualisiert nicht“; „Excel stürzt ab“. Nicht auswählen, wenn ein Add-in oder Makro die Ursache ist, eine Fachanwendung exportiert nicht korrekt oder die Datei nur wegen fehlender Berechtigung auf einer Ablage nicht geöffnet werden kann. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Makro- und Add-in-Probleme werden in der eigenen Office-Kategorie erfasst.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Microsoft Excel“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Microsoft Excel" - ], - "keywords": [ - "Excel", - "Microsoft Excel", - "XLSX", - "Tabelle", - "Formel", - "Pivot", - "Arbeitsmappe", - "Tabellenkalkulation", - "CSV", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/microsoft-excel", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-MICROSOFT-EXCEL-SELECT", + "title": "Microsoft Excel", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Microsoft Excel. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Microsoft Excel bei Tabellen, Formeln, Pivot-Auswertungen, Datenimporten oder programmspezifischen Funktionen fehlerhaft arbeitet oder fachnahe Unterstützung benötigt wird. Typische Ticketformulierungen sind: „Excel-Formel funktioniert nicht“; „Datei öffnet fehlerhaft“; „Pivot-Tabelle aktualisiert nicht“; „Excel stürzt ab“. Nicht auswählen, wenn ein Add-in oder Makro die Ursache ist, eine Fachanwendung exportiert nicht korrekt oder die Datei nur wegen fehlender Berechtigung auf einer Ablage nicht geöffnet werden kann. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Makro- und Add-in-Probleme werden in der eigenen Office-Kategorie erfasst.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Microsoft Excel“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Microsoft Excel" + ], + "keywords": [ + "Excel", + "Microsoft Excel", + "XLSX", + "Tabelle", + "Formel", + "Pivot", + "Arbeitsmappe", + "Tabellenkalkulation", + "CSV", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/microsoft-excel", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/12_microsoft-powerpoint.json b/knowledge/12_microsoft-powerpoint.json index 9a22ef7..0c4fbd5 100644 --- a/knowledge/12_microsoft-powerpoint.json +++ b/knowledge/12_microsoft-powerpoint.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-MICROSOFT-POWERPOINT-SELECT", - "title": "Microsoft PowerPoint", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Microsoft PowerPoint. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Microsoft PowerPoint bei Präsentationen, Folienlayouts, Medien, Referentenansicht oder programmspezifischen Funktionen nicht korrekt arbeitet oder Unterstützung benötigt wird. Typische Ticketformulierungen sind: „Präsentation lässt sich nicht starten“; „Video in Folie spielt nicht“; „Folienlayout fehlerhaft“; „PowerPoint stürzt ab“. Nicht auswählen, wenn Beamer, Monitor oder Videokonferenztechnik physisch nicht funktioniert oder eine zentrale Office-Installation fehlt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Physische Anzeige- und Konferenzprobleme bleiben beim Support.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Microsoft PowerPoint“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Microsoft PowerPoint" - ], - "keywords": [ - "PowerPoint", - "Microsoft PowerPoint", - "PPTX", - "Präsentation", - "Folie", - "Referentenansicht", - "Layout", - "Bildschirmpräsentation", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/microsoft-powerpoint", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-MICROSOFT-POWERPOINT-SELECT", + "title": "Microsoft PowerPoint", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Microsoft PowerPoint. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Microsoft PowerPoint bei Präsentationen, Folienlayouts, Medien, Referentenansicht oder programmspezifischen Funktionen nicht korrekt arbeitet oder Unterstützung benötigt wird. Typische Ticketformulierungen sind: „Präsentation lässt sich nicht starten“; „Video in Folie spielt nicht“; „Folienlayout fehlerhaft“; „PowerPoint stürzt ab“. Nicht auswählen, wenn Beamer, Monitor oder Videokonferenztechnik physisch nicht funktioniert oder eine zentrale Office-Installation fehlt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Physische Anzeige- und Konferenzprobleme bleiben beim Support.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Microsoft PowerPoint“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Microsoft PowerPoint" + ], + "keywords": [ + "PowerPoint", + "Microsoft PowerPoint", + "PPTX", + "Präsentation", + "Folie", + "Referentenansicht", + "Layout", + "Bildschirmpräsentation", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/microsoft-powerpoint", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/12_monitoring.json b/knowledge/12_monitoring.json index 65fe22d..73e5c44 100644 --- a/knowledge/12_monitoring.json +++ b/knowledge/12_monitoring.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-MONITORING-SELECT", - "title": "Monitoring", - "text": "Auswahlziel: Server, Backend und Plattformen \u003e Monitoring. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn technische Überwachung, Checks, Alarmierung, Dashboards, Schwellwerte oder Benachrichtigungen für Infrastruktur und Plattformen eingerichtet oder fehlerhaft sind. Typische Ticketformulierungen sind: „Monitoring-Check hinzufügen“; „Alarm wird nicht ausgelöst“; „Schwellwert anpassen“; „Infrastruktur-Dashboard erstellen“. Nicht auswählen, wenn ein konkreter Dienst bereits ausgefallen ist und die Behebung im Vordergrund steht oder eine fachliche Statistik benötigt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Ein Alarm ist ein Hinweis; die eigentliche Störung kann zusätzlich in ihrer fachlich passenden Kategorie erfasst werden.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Monitoring“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Server, Backend und Plattformen \u003e Monitoring" - ], - "keywords": [ - "Monitoring", - "Überwachung", - "Alarmierung", - "Check", - "Schwellwert", - "Dashboard", - "Alert", - "Metrik", - "Nagios", - "Zabbix", - "Server, Backend und Plattformen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/monitoring", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-MONITORING-SELECT", + "title": "Monitoring", + "text": "Auswahlziel: Server, Backend und Plattformen \u003e Monitoring. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn technische Überwachung, Checks, Alarmierung, Dashboards, Schwellwerte oder Benachrichtigungen für Infrastruktur und Plattformen eingerichtet oder fehlerhaft sind. Typische Ticketformulierungen sind: „Monitoring-Check hinzufügen“; „Alarm wird nicht ausgelöst“; „Schwellwert anpassen“; „Infrastruktur-Dashboard erstellen“. Nicht auswählen, wenn ein konkreter Dienst bereits ausgefallen ist und die Behebung im Vordergrund steht oder eine fachliche Statistik benötigt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Ein Alarm ist ein Hinweis; die eigentliche Störung kann zusätzlich in ihrer fachlich passenden Kategorie erfasst werden.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Monitoring“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Server, Backend und Plattformen \u003e Monitoring" + ], + "keywords": [ + "Monitoring", + "Überwachung", + "Alarmierung", + "Check", + "Schwellwert", + "Dashboard", + "Alert", + "Metrik", + "Nagios", + "Zabbix", + "Server, Backend und Plattformen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/monitoring", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/13_outlook-client.json b/knowledge/13_outlook-client.json index 47ca578..adbdeac 100644 --- a/knowledge/13_outlook-client.json +++ b/knowledge/13_outlook-client.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-OUTLOOK-CLIENT-SELECT", - "title": "Outlook-Client", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Outlook-Client. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn die lokale Outlook-Anwendung nicht startet, Profile oder Ansichten fehlerhaft sind, Suche, Kalenderdarstellung, Signatur oder lokale Outlook-Funktionen nicht korrekt arbeiten. Typische Ticketformulierungen sind: „Outlook startet nicht“; „Outlook-Profil defekt“; „Suche findet nichts“; „Kalenderansicht fehlerhaft“; „Signatur fehlt“. Nicht auswählen, wenn das Postfach serverseitig nicht erreichbar ist, E-Mails organisationsweit nicht zugestellt werden, ein Funktionspostfach beantragt wird oder nur das Kennwort gesperrt ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Serverseitige Mail- und Postfachstörungen werden an Infrastruktur und Backend übergeben.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Outlook-Client“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Outlook-Client" - ], - "keywords": [ - "Outlook", - "Outlook-Client", - "Profil", - "Outlook Suche", - "Kalenderansicht", - "Signatur", - "OST", - "PST", - "lokales Outlook", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/outlook-client", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-OUTLOOK-CLIENT-SELECT", + "title": "Outlook-Client", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Outlook-Client. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn die lokale Outlook-Anwendung nicht startet, Profile oder Ansichten fehlerhaft sind, Suche, Kalenderdarstellung, Signatur oder lokale Outlook-Funktionen nicht korrekt arbeiten. Typische Ticketformulierungen sind: „Outlook startet nicht“; „Outlook-Profil defekt“; „Suche findet nichts“; „Kalenderansicht fehlerhaft“; „Signatur fehlt“. Nicht auswählen, wenn das Postfach serverseitig nicht erreichbar ist, E-Mails organisationsweit nicht zugestellt werden, ein Funktionspostfach beantragt wird oder nur das Kennwort gesperrt ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Serverseitige Mail- und Postfachstörungen werden an Infrastruktur und Backend übergeben.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Outlook-Client“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Outlook-Client" + ], + "keywords": [ + "Outlook", + "Outlook-Client", + "Profil", + "Outlook Suche", + "Kalenderansicht", + "Signatur", + "OST", + "PST", + "lokales Outlook", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/outlook-client", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/14_office-vorlagen.json b/knowledge/14_office-vorlagen.json index d2673c5..20c16f8 100644 --- a/knowledge/14_office-vorlagen.json +++ b/knowledge/14_office-vorlagen.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-OFFICE-VORLAGEN-SELECT", - "title": "Office-Vorlagen", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Office-Vorlagen. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn zentrale Word-, Excel- oder PowerPoint-Vorlagen erstellt, geändert, verteilt oder korrigiert werden sollen, einschließlich Briefkopf, Layout, Textbausteinen und Organisationsvorgaben. Typische Ticketformulierungen sind: „Briefvorlage anpassen“; „Neues Corporate-Design-Layout“; „Vorlage wird nicht geladen“; „Textbaustein zentral ändern“. Nicht auswählen, wenn nur ein einzelnes Dokument formatiert werden soll, ein Makro fehlerhaft ist oder ein Drucker die Vorlage nicht ausgibt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Inhaltliche Freigaben durch zuständige Organisationseinheiten bleiben erforderlich.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Office-Vorlagen“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Office-Vorlagen" - ], - "keywords": [ - "Office-Vorlage", - "Word-Vorlage", - "Excel-Vorlage", - "PowerPoint-Vorlage", - "Briefkopf", - "Template", - "Textbaustein", - "Corporate Design", - "Office-Vorlagen", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/office-vorlagen", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-OFFICE-VORLAGEN-SELECT", + "title": "Office-Vorlagen", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Office-Vorlagen. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn zentrale Word-, Excel- oder PowerPoint-Vorlagen erstellt, geändert, verteilt oder korrigiert werden sollen, einschließlich Briefkopf, Layout, Textbausteinen und Organisationsvorgaben. Typische Ticketformulierungen sind: „Briefvorlage anpassen“; „Neues Corporate-Design-Layout“; „Vorlage wird nicht geladen“; „Textbaustein zentral ändern“. Nicht auswählen, wenn nur ein einzelnes Dokument formatiert werden soll, ein Makro fehlerhaft ist oder ein Drucker die Vorlage nicht ausgibt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Inhaltliche Freigaben durch zuständige Organisationseinheiten bleiben erforderlich.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Office-Vorlagen“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Office-Vorlagen" + ], + "keywords": [ + "Office-Vorlage", + "Word-Vorlage", + "Excel-Vorlage", + "PowerPoint-Vorlage", + "Briefkopf", + "Template", + "Textbaustein", + "Corporate Design", + "Office-Vorlagen", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/office-vorlagen", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/15_office-add-ins-und-makros.json b/knowledge/15_office-add-ins-und-makros.json index d40f08f..51f1197 100644 --- a/knowledge/15_office-add-ins-und-makros.json +++ b/knowledge/15_office-add-ins-und-makros.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-OFFICE-ADD-INS-UND-MAKROS-SELECT", - "title": "Office-Add-ins und Makros", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Office-Add-ins und Makros. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn ein Office-Add-in, COM-Add-in, VBA-Makro oder automatisierter Office-Ablauf installiert, freigegeben, repariert oder angepasst werden soll. Typische Ticketformulierungen sind: „Excel-Makro läuft nicht“; „Outlook-Add-in fehlt“; „VBA-Fehler“; „COM-Add-in deaktiviert“. Nicht auswählen, wenn die Basisanwendung Word, Excel, PowerPoint oder Outlook ohne Add-in-Bezug fehlerhaft ist oder neue allgemeine Software beschafft werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Sicherheitsbewertung und Signierung können Infrastruktur und Backend beziehungsweise IT-Sicherheit einbeziehen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Office-Add-ins und Makros“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Office-Add-ins und Makros" - ], - "keywords": [ - "Add-in", - "Plugin", - "Makro", - "VBA", - "COM-Add-in", - "Office-Erweiterung", - "Automatisierung", - "Makrofehler", - "Office-Add-ins und Makros", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/office-add-ins-und-makros", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-OFFICE-ADD-INS-UND-MAKROS-SELECT", + "title": "Office-Add-ins und Makros", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Office-Add-ins und Makros. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn ein Office-Add-in, COM-Add-in, VBA-Makro oder automatisierter Office-Ablauf installiert, freigegeben, repariert oder angepasst werden soll. Typische Ticketformulierungen sind: „Excel-Makro läuft nicht“; „Outlook-Add-in fehlt“; „VBA-Fehler“; „COM-Add-in deaktiviert“. Nicht auswählen, wenn die Basisanwendung Word, Excel, PowerPoint oder Outlook ohne Add-in-Bezug fehlerhaft ist oder neue allgemeine Software beschafft werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Sicherheitsbewertung und Signierung können Infrastruktur und Backend beziehungsweise IT-Sicherheit einbeziehen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Office-Add-ins und Makros“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Office-Add-ins und Makros" + ], + "keywords": [ + "Add-in", + "Plugin", + "Makro", + "VBA", + "COM-Add-in", + "Office-Erweiterung", + "Automatisierung", + "Makrofehler", + "Office-Add-ins und Makros", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/office-add-ins-und-makros", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/16_office-aktivierung-und-lizenzierung.json b/knowledge/16_office-aktivierung-und-lizenzierung.json index ab73dfd..fbd3556 100644 --- a/knowledge/16_office-aktivierung-und-lizenzierung.json +++ b/knowledge/16_office-aktivierung-und-lizenzierung.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-OFFICE-AKTIVIERUNG-UND-LIZENZIERUNG-SELECT", - "title": "Office-Aktivierung und Lizenzierung", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Office-Aktivierung und Lizenzierung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Microsoft Office nicht aktiviert ist, eine Lizenz technisch nicht erkannt wird, ein Lizenzstatus fehlerhaft ist oder die korrekte Office-Edition zugeordnet werden muss. Typische Ticketformulierungen sind: „Office nicht aktiviert“; „Lizenz kann nicht überprüft werden“; „Produkt nicht lizenziert“; „Falsche Office-Edition“. Nicht auswählen, wenn neue Lizenzen gekauft, Verträge verlängert oder Rechnungen bearbeitet werden sollen; diese Vorgänge gehören zu Beschaffung, Verträge und Lizenzen. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Lizenzzuordnung liegt bei Fachanwendungen; Einkauf und Vertragsverwaltung bei Leitung und Finanzen / Beschaffung.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Office-Aktivierung und Lizenzierung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Office-Aktivierung und Lizenzierung" - ], - "keywords": [ - "Office Aktivierung", - "Lizenzfehler", - "Produkt nicht lizenziert", - "Microsoft 365 Lizenz", - "Office-Lizenz", - "Aktivierung", - "Lizenzstatus", - "Office-Aktivierung und Lizenzierung", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/office-aktivierung-und-lizenzierung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-OFFICE-AKTIVIERUNG-UND-LIZENZIERUNG-SELECT", + "title": "Office-Aktivierung und Lizenzierung", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Office-Aktivierung und Lizenzierung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Microsoft Office nicht aktiviert ist, eine Lizenz technisch nicht erkannt wird, ein Lizenzstatus fehlerhaft ist oder die korrekte Office-Edition zugeordnet werden muss. Typische Ticketformulierungen sind: „Office nicht aktiviert“; „Lizenz kann nicht überprüft werden“; „Produkt nicht lizenziert“; „Falsche Office-Edition“. Nicht auswählen, wenn neue Lizenzen gekauft, Verträge verlängert oder Rechnungen bearbeitet werden sollen; diese Vorgänge gehören zu Beschaffung, Verträge und Lizenzen. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Lizenzzuordnung liegt bei Fachanwendungen; Einkauf und Vertragsverwaltung bei Leitung und Finanzen / Beschaffung.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Office-Aktivierung und Lizenzierung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Office-Aktivierung und Lizenzierung" + ], + "keywords": [ + "Office Aktivierung", + "Lizenzfehler", + "Produkt nicht lizenziert", + "Microsoft 365 Lizenz", + "Office-Lizenz", + "Aktivierung", + "Lizenzstatus", + "Office-Aktivierung und Lizenzierung", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/office-aktivierung-und-lizenzierung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/17_serienbriefe-und-dokumentfunktionen.json b/knowledge/17_serienbriefe-und-dokumentfunktionen.json index f3dc83d..08dac02 100644 --- a/knowledge/17_serienbriefe-und-dokumentfunktionen.json +++ b/knowledge/17_serienbriefe-und-dokumentfunktionen.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-SERIENBRIEFE-UND-DOKUMENTFUNKTIONEN-SELECT", - "title": "Serienbriefe und Dokumentfunktionen", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Serienbriefe und Dokumentfunktionen. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Serienbriefe, Datenquellen, Feldfunktionen, Dokumentverknüpfungen, Etiketten oder automatisierte Dokumenterstellung in Office fehlerhaft sind oder eingerichtet werden sollen. Typische Ticketformulierungen sind: „Serienbrief verbindet Datenquelle nicht“; „Feldfunktion zeigt Fehler“; „Etikettendruck aus Word vorbereiten“; „Dokument automatisch befüllen“. Nicht auswählen, wenn ein allgemeines Word-Formatierungsproblem, ein Fachverfahrensbericht oder ein Druckerdefekt vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei Datenübergabe aus einer Fachanwendung ist gegebenenfalls zusätzlich die Schnittstellenkategorie relevant.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Serienbriefe und Dokumentfunktionen“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Serienbriefe und Dokumentfunktionen" - ], - "keywords": [ - "Serienbrief", - "Datenquelle", - "Feldfunktion", - "Etiketten", - "Dokumentfunktion", - "Mail Merge", - "Seriendruck", - "Dokumentautomatisierung", - "Serienbriefe und Dokumentfunktionen", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/serienbriefe-und-dokumentfunktionen", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-SERIENBRIEFE-UND-DOKUMENTFUNKTIONEN-SELECT", + "title": "Serienbriefe und Dokumentfunktionen", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Serienbriefe und Dokumentfunktionen. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Serienbriefe, Datenquellen, Feldfunktionen, Dokumentverknüpfungen, Etiketten oder automatisierte Dokumenterstellung in Office fehlerhaft sind oder eingerichtet werden sollen. Typische Ticketformulierungen sind: „Serienbrief verbindet Datenquelle nicht“; „Feldfunktion zeigt Fehler“; „Etikettendruck aus Word vorbereiten“; „Dokument automatisch befüllen“. Nicht auswählen, wenn ein allgemeines Word-Formatierungsproblem, ein Fachverfahrensbericht oder ein Druckerdefekt vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei Datenübergabe aus einer Fachanwendung ist gegebenenfalls zusätzlich die Schnittstellenkategorie relevant.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Serienbriefe und Dokumentfunktionen“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Serienbriefe und Dokumentfunktionen" + ], + "keywords": [ + "Serienbrief", + "Datenquelle", + "Feldfunktion", + "Etiketten", + "Dokumentfunktion", + "Mail Merge", + "Seriendruck", + "Dokumentautomatisierung", + "Serienbriefe und Dokumentfunktionen", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/serienbriefe-und-dokumentfunktionen", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/knowledge/example-vpn.json b/knowledge/example-vpn.json index 0d813fd..d0acddb 100644 --- a/knowledge/example-vpn.json +++ b/knowledge/example-vpn.json @@ -1,20 +1,20 @@ -{ - "id": "KB-EXAMPLE-VPN", - "title": "Beispiel: VPN Gateway nicht erreichbar", - "text": "Beispieldokument. Aktivieren oder ersetzen Sie diesen Eintrag erst nach fachlicher Prüfung. Typisches Symptom: VPN meldet, dass das Gateway nicht erreichbar ist.", - "answer": "Bitte trennen Sie die bestehende VPN-Verbindung vollständig und starten Sie den VPN-Client anschließend neu. Sollte die Meldung weiterhin auftreten, antworten Sie bitte auf dieses Ticket mit dem genauen Fehlertext.", - "auto_reply": false, - "min_score": 0.92, - "categories": [ - - ], - "keywords": [ - "VPN", - "Gateway", - "nicht erreichbar" - ], - "source": "internal-category", - "source_uri": "kb://examples/vpn-gateway", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KB-EXAMPLE-VPN", + "title": "Beispiel: VPN Gateway nicht erreichbar", + "text": "Beispieldokument. Aktivieren oder ersetzen Sie diesen Eintrag erst nach fachlicher Prüfung. Typisches Symptom: VPN meldet, dass das Gateway nicht erreichbar ist.", + "answer": "Bitte trennen Sie die bestehende VPN-Verbindung vollständig und starten Sie den VPN-Client anschließend neu. Sollte die Meldung weiterhin auftreten, antworten Sie bitte auf dieses Ticket mit dem genauen Fehlertext.", + "auto_reply": false, + "min_score": 0.92, + "categories": [ + + ], + "keywords": [ + "VPN", + "Gateway", + "nicht erreichbar" + ], + "source": "internal-category", + "source_uri": "kb://examples/vpn-gateway", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/patches/SHA256SUMS b/patches/SHA256SUMS index b991fc9..c0f4e7b 100644 --- a/patches/SHA256SUMS +++ b/patches/SHA256SUMS @@ -19,3 +19,4 @@ b031ea42356a022d7bb03e7e2bda23c4ceddb66ccc59a5d1f0ebe787a790b829 v1.5.1-to-v1.5 257a78b8f6613f92086a04bf53455f12e9bd1ab94775add31819ada465582163 v1.5.6-to-v1.5.7.diff 53ab438b31a31590edbfe00eea6ba9d9ebff2b4c6255d7ac2056c86757f65c3a v1.5.7-to-v1.5.8.diff 24bb0da9bf77a0e8b692989b05c00b623ed9a558b9b5050c24e396e1da41d0f8 v1.5.8-to-v1.5.9.diff +e579d88ed2846cf5bc306cad45502af3edc61bd1c5bb5a2272979feff23dca62 v1.5.9-to-v1.6.0.diff diff --git a/patches/v1.5.9-to-v1.6.0.diff b/patches/v1.5.9-to-v1.6.0.diff new file mode 100644 index 0000000..f5b8f22 --- /dev/null +++ b/patches/v1.5.9-to-v1.6.0.diff @@ -0,0 +1,14082 @@ +diff --git a/.env.example b/.env.example +index 6af0c6d..d6ccda1 100644 +--- a/.env.example ++++ b/.env.example +@@ -1,5 +1,5 @@ + ############################################################################### +-# GLPI NEUROFORGE MEGA v1.5.9 - VOLLSTÄNDIGE .ENV.example ++# GLPI NEUROFORGE MEGA v1.6.0 - VOLLSTÄNDIGE .ENV.example + # + # Diese Datei ist die zentrale Konfiguration für docker compose. + # Sie enthält: +@@ -29,7 +29,7 @@ + # 01. MEGA STACK - RELEASE / HOST PORTS / PFADE + ############################################################################### + # Immutable Registry-Tag der sechs Projekt-Images. "latest" ist produktiv verboten. +-IMAGE_TAG=1.5.9 ++IMAGE_TAG=1.6.0 + + CONTROL_HOST_PORT=8070 + AGENT_HOST_PORT=8080 +@@ -193,6 +193,49 @@ NEUROFORGE_KB_STAGING_MAX_ARTICLE_CHARS=10000 + NEUROFORGE_KB_STAGING_MIN_ANSWER_CHARS=160 + NEUROFORGE_KB_STAGING_MAX_ANSWER_CHARS=1200 + ++############################################################################### ++# 07B. MASTER / SUBAGENTS / DURABLE ORCHESTRATOR / KNOWLEDGE GRAPH ++############################################################################### ++# Der NeuroForge-Master hält den autoritativen Zustand. Subagents ziehen nur ++# capability-passende Jobs und arbeiten unter lease/heartbeat fencing. ++NEUROFORGE_WORKER_LEASE_SECONDS=120 ++NEUROFORGE_WORKER_HEARTBEAT_SECONDS=15 ++NEUROFORGE_WORKER_STALE_AFTER_SECONDS=60 ++NEUROFORGE_WORKER_DEFAULT_MAX_ATTEMPTS=3 ++NEUROFORGE_WORKER_RETRY_BACKOFF_SECONDS=15 ++NEUROFORGE_WORKER_MAX_QUEUED_JOBS=5000 ++# Worker-Ergebnisse, die Master-State verändern (z.B. vector.relink), werden ++# zweiphasig persistiert und bei Master-Neustart idempotent weiter angewendet. ++NEUROFORGE_WORKER_MASTER_APPLY_MAX_ATTEMPTS=5 ++NEUROFORGE_WORKER_MASTER_APPLY_BACKOFF_SECONDS=5 ++NEUROFORGE_WORKER_JOB_RETENTION_HOURS=168 ++NEUROFORGE_WORKER_MAX_TERMINAL_JOBS=20000 ++# Lokale Compose-Subagents. Zusätzliche Remote-Subagents können mit ++# docker-compose.subagent.yml auf anderen Hosts gestartet werden. ++NEUROFORGE_CPU_WORKER_CONCURRENCY=2 ++NEUROFORGE_GPU_WORKER_CONCURRENCY=1 ++NEUROFORGE_WORKER_HEARTBEAT_INTERVAL=15s ++# Bestehende/importierte knowledge.chunk-Memories werden kontrolliert zu einem ++# n:m-Graphen verlinkt. Kein O(N²)-Queue-Burst: ANN-Kandidaten + bounded queue. ++NEUROFORGE_GRAPH_BACKFILL_ENABLED=true ++NEUROFORGE_GRAPH_BACKFILL_INTERVAL_SECONDS=10 ++NEUROFORGE_GRAPH_BACKFILL_BATCH_SIZE=64 ++NEUROFORGE_GRAPH_BACKFILL_MAX_QUEUED=256 ++NEUROFORGE_GRAPH_BACKFILL_MIN_DEGREE=3 ++NEUROFORGE_GRAPH_CANDIDATE_MULTIPLIER=6 ++NEUROFORGE_GRAPH_RETRY_AFTER_MINUTES=360 ++NEUROFORGE_GRAPH_REQUIRE_WORKER=true ++# Retrieval kann echte mehrstufige Assoziationsketten verfolgen. ++NEUROFORGE_GRAPH_MAX_HOPS=3 ++NEUROFORGE_GRAPH_HOP_DECAY=0.60 ++NEUROFORGE_GRAPH_MAX_EXPANSION=64 ++NEUROFORGE_GRAPH_MIN_EDGE_WEIGHT=0.05 ++# LLM/Embedding-Aufgaben werden bevorzugt an GPU-Subagents delegiert. Bei nicht ++# verfügbarem GPU-Subagent fällt der Master auf seinen Provider-Router zurück. ++NEUROFORGE_OFFLOAD_CHAT=true ++NEUROFORGE_OFFLOAD_EMBEDDINGS=true ++NEUROFORGE_DISTRIBUTED_INFERENCE_WAIT_SECONDS=180 ++ + ############################################################################### + # 08. OPTIONAL CODEBASE MEMORY MCP / ENGINEERING UI + ############################################################################### +diff --git a/mnt/data/v160_work/.env.subagent.example b/.env.subagent.example +new file mode 100644 +index 0000000..ff75be5 +--- /dev/null ++++ b/.env.subagent.example +@@ -0,0 +1,20 @@ ++# Remote NeuroForge subagent host (v1.6.0) ++IMAGE_TAG=1.6.0 ++NEUROFORGE_MASTER_URL=https://neuroforge.internal.example ++NEUROFORGE_WORKER_TOKEN=CHANGE_ME_WORKER_TOKEN_SHARED_WITH_MASTER ++NEUROFORGE_WORKER_HEARTBEAT_INTERVAL=15s ++ ++# CPU profile ++NEUROFORGE_CPU_WORKER_ID=cpu-node-01 ++NEUROFORGE_CPU_WORKER_CAPABILITIES=cpu,vector.relink ++NEUROFORGE_CPU_WORKER_CONCURRENCY=2 ++ ++# GPU profile ++NEUROFORGE_GPU_WORKER_ID=gpu-node-01 ++NEUROFORGE_GPU_WORKER_CAPABILITIES=gpu,model.chat,model.embed ++NEUROFORGE_GPU_WORKER_CONCURRENCY=1 ++NEUROFORGE_WORKER_OLLAMA_URL=http://host.docker.internal:11434 ++OLLAMA_MODEL=gemma4 ++OLLAMA_EMBEDDING_MODEL=embeddinggemma ++NEUROFORGE_OLLAMA_NUM_CTX=8192 ++OLLAMA_KEEP_ALIVE=10m +diff --git a/README.md b/README.md +index d3f330e..26f047c 100644 +--- a/README.md ++++ b/README.md +@@ -1,6 +1,6 @@ +-# GLPI NeuroForge Mega v1.5.9 ++# GLPI NeuroForge Mega v1.6.0 + +-> Release: **v1.5.9** · Article-Depth-Hardening: quellengebundene Staging-Drafts erhalten ein eigenes Context-/Output-Budget, Mindesttiefe und eine messbare Vollartikel-Qualitätsprüfung; `answer` bleibt bewusst die kompakte operative Zusammenfassung. ++> Release: **v1.6.0** · Durable Master/Subagent Orchestration + Knowledge-Graph Convergence: CPU-/GPU-Arbeit wird capability-basiert, lease-gefenced und persistent geplant; importierte Memories werden bounded zu einem n:m-Synapsengraphen verknüpft und Retrieval kann mehrere Hops traversieren. + + Ein kontrolliertes Monorepo aus **GLPI AI Agent**, **GLPI AI Knowledgebase** und **NeuroForge + SQAR**. Ziel ist nicht ein untrennbarer Monolith, sondern eine gemeinsame Plattform mit klaren Zuständigkeiten, getrennten Credentials und nachvollziehbaren Failure-Modi. + +@@ -13,6 +13,19 @@ Der Fortschritt eines Research-Goals wird aus persistierten Research-Runs rekons + + Staging-Entwürfe sind pro Goal idempotent: Solange ein Draft aktiv im Staging liegt, wird er bei neuer Evidenz aktualisiert statt dupliziert. Nach menschlicher Promotion/Archivierung kann später wieder ein neuer Draft entstehen. + ++## Master / Subagents + n:m Knowledge Graph (v1.6.0) ++ ++NeuroForge ist jetzt der autoritative **Master/Orchestrator**. CPU- und GPU-Subagents registrieren Resource-Class, Capabilities und Parallelitätsgrenzen, ziehen passende persistente Jobs und arbeiten unter Heartbeat/Lease-Fencing. Queue, Retry/Backoff, Dependencies, Idempotency und `apply_wait` überleben Master-Neustarts. Abgelaufene Lease-Tokens dürfen keine verspäteten Ergebnisse mehr schreiben; Relink-Ergebnisse sind zusätzlich an Memory-Version und Vector-Fingerprint gebunden. ++ ++Der bisherige Knowledge-Import endet nicht mehr bei isolierten Vector-Memories. Ein bounded Graph-Backfill nutzt ANN nur zur Kandidatenauswahl und delegiert exakte Relink-Arbeit an CPU-Subagents. Ein Memory kann mehrere Synapsen besitzen; Graphmetriken messen u.a. isolierte, verknüpfte und mehrfach verknüpfte Memories, Durchschnitts-/Maximalgrad und Connected Components. Retrieval kann konfigurierbar mehrere Synapsen-Hops mit Decay verfolgen. ++ ++Lokale Standardrollen: ++ ++- `neuroforge-worker-cpu`: `cpu,vector.relink` ++- `neuroforge-worker-gpu`: `gpu,model.chat,model.embed` ++ ++Zusätzliche Hosts können mit `docker-compose.subagent.yml` angebunden werden. Das Control Center bleibt read-only und zeigt Master-, Worker-, Queue- und Graphzustand. Details: [`docs/MASTER-SUBAGENT-ORCHESTRATOR.md`](docs/MASTER-SUBAGENT-ORCHESTRATOR.md). ++ + ## Unified Graph Explorer (v1.4.0) + + Das read-only Control Center visualisiert Runtime/Trust, Ticket-Evidence, Learning-Lineage, Research-Provenance, einen redigierten NeuroForge-Brain-Graph sowie einen reproduzierbaren Engineering-Graph aus Go-AST und Compose. Für Dateien/Symbole/Routen gibt es zusätzlich eine statische Change-Impact-/Blast-Radius-Sicht. 2D ist der operative Default; 3D ist ein optionaler, gebundener Explorer. +diff --git a/mnt/data/v160_work/RELEASE-NOTES-v1.6.0.md b/RELEASE-NOTES-v1.6.0.md +new file mode 100644 +index 0000000..d49b0e3 +--- /dev/null ++++ b/RELEASE-NOTES-v1.6.0.md +@@ -0,0 +1,24 @@ ++# GLPI NeuroForge Mega v1.6.0 ++ ++## Durable Master/Subagent Orchestration ++ ++- NeuroForge is the authoritative scheduler/master. ++- Separate CPU and GPU subagents with capability/resource routing. ++- Persistent priorities, dependencies, idempotency, retries/backoff and bounded queues. ++- Heartbeat/lease-token fencing; expired leases cannot submit late results. ++- Durable `apply_wait` phase for worker results that mutate master state. ++- Admin status/job list/retry/cancel and read-only Control Center visibility. ++ ++## Knowledge Graph Convergence ++ ++- Bounded ANN-assisted graph backfill for imported knowledge memories. ++- Multi-neighbor semantic relinking instead of leaving imported chunks isolated. ++- Adjacency index, multiple relation labels per edge and bounded multi-hop retrieval. ++- Graph health metrics for isolated/linked/multi-linked nodes, degree and connected components. ++- Memory-version plus vector-fingerprint fencing rejects stale relink results after delete/recreate races. ++ ++## Deployment ++ ++- Local `neuroforge-worker-cpu` and `neuroforge-worker-gpu` services. ++- `docker-compose.subagent.yml` for additional remote CPU/GPU hosts. ++- Non-destructive upgrade from v1.5.9; existing volumes and knowledge remain valid. +diff --git a/VERSION b/VERSION +index 2b26b8d..dc1e644 100644 +--- a/VERSION ++++ b/VERSION +@@ -1 +1 @@ +-1.5.9 ++1.6.0 +diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml +index 88dcad2..63175c2 100644 +--- a/docker-compose.dev.yml ++++ b/docker-compose.dev.yml +@@ -6,7 +6,12 @@ services: + context: ./platform/neuroforge + target: server + pull_policy: never +- neuroforge-worker: ++ neuroforge-worker-cpu: ++ build: ++ context: ./platform/neuroforge ++ target: worker ++ pull_policy: never ++ neuroforge-worker-gpu: + build: + context: ./platform/neuroforge + target: worker +diff --git a/mnt/data/v160_work/docker-compose.subagent.yml b/docker-compose.subagent.yml +new file mode 100644 +index 0000000..7f8bd35 +--- /dev/null ++++ b/docker-compose.subagent.yml +@@ -0,0 +1,57 @@ ++# Remote NeuroForge subagents. Run this file on additional CPU/GPU hosts. ++# Examples: ++# docker compose -f docker-compose.subagent.yml --profile cpu up -d ++# docker compose -f docker-compose.subagent.yml --profile gpu up -d ++# The master URL must be reachable from this host/container. ++name: neuroforge-subagents ++services: ++ cpu-subagent: ++ profiles: [cpu] ++ image: git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge-worker:${IMAGE_TAG:?Set IMAGE_TAG, e.g. 1.6.0} ++ command: ++ - -server ++ - ${NEUROFORGE_MASTER_URL:?Set the reachable NeuroForge master URL} ++ - -id ++ - ${NEUROFORGE_CPU_WORKER_ID:-cpu-${HOSTNAME:-remote}} ++ restart: unless-stopped ++ environment: ++ NEUROFORGE_WORKER_TOKEN: ${NEUROFORGE_WORKER_TOKEN:?Set the worker token shared with the master} ++ NEUROFORGE_WORKER_RESOURCE_CLASS: cpu ++ NEUROFORGE_WORKER_CAPABILITIES: ${NEUROFORGE_CPU_WORKER_CAPABILITIES:-cpu,vector.relink} ++ NEUROFORGE_WORKER_MAX_CONCURRENCY: ${NEUROFORGE_CPU_WORKER_CONCURRENCY:-2} ++ NEUROFORGE_WORKER_HEARTBEAT_INTERVAL: ${NEUROFORGE_WORKER_HEARTBEAT_INTERVAL:-15s} ++ read_only: true ++ tmpfs: ++ - /tmp:size=32m,mode=1777 ++ security_opt: ++ - no-new-privileges:true ++ cap_drop: [ALL] ++ ++ gpu-subagent: ++ profiles: [gpu] ++ image: git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge-worker:${IMAGE_TAG:?Set IMAGE_TAG, e.g. 1.6.0} ++ command: ++ - -server ++ - ${NEUROFORGE_MASTER_URL:?Set the reachable NeuroForge master URL} ++ - -id ++ - ${NEUROFORGE_GPU_WORKER_ID:-gpu-${HOSTNAME:-remote}} ++ restart: unless-stopped ++ environment: ++ NEUROFORGE_WORKER_TOKEN: ${NEUROFORGE_WORKER_TOKEN:?Set the worker token shared with the master} ++ NEUROFORGE_WORKER_RESOURCE_CLASS: gpu ++ NEUROFORGE_WORKER_CAPABILITIES: ${NEUROFORGE_GPU_WORKER_CAPABILITIES:-gpu,model.chat,model.embed} ++ NEUROFORGE_WORKER_MAX_CONCURRENCY: ${NEUROFORGE_GPU_WORKER_CONCURRENCY:-1} ++ NEUROFORGE_WORKER_HEARTBEAT_INTERVAL: ${NEUROFORGE_WORKER_HEARTBEAT_INTERVAL:-15s} ++ NEUROFORGE_WORKER_OLLAMA_URL: ${NEUROFORGE_WORKER_OLLAMA_URL:?Set Ollama URL reachable from the GPU subagent} ++ NEUROFORGE_WORKER_OLLAMA_CHAT_MODEL: ${OLLAMA_MODEL:-gemma3} ++ NEUROFORGE_WORKER_OLLAMA_EMBEDDING_MODEL: ${OLLAMA_EMBEDDING_MODEL:-embeddinggemma} ++ NEUROFORGE_WORKER_OLLAMA_NUM_CTX: ${NEUROFORGE_OLLAMA_NUM_CTX:-8192} ++ NEUROFORGE_WORKER_OLLAMA_KEEP_ALIVE: ${OLLAMA_KEEP_ALIVE:-10m} ++ read_only: true ++ tmpfs: ++ - /tmp:size=32m,mode=1777 ++ security_opt: ++ - no-new-privileges:true ++ cap_drop: [ALL] ++ extra_hosts: ++ - host.docker.internal:host-gateway +diff --git a/docker-compose.yml b/docker-compose.yml +index 95a4d75..df01540 100644 +--- a/docker-compose.yml ++++ b/docker-compose.yml +@@ -33,7 +33,7 @@ services: + cap_drop: + - ALL + neuroforge: +- image: git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.5.9} ++ image: git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.6.0} + command: + - -data + - /app/data +@@ -54,6 +54,32 @@ services: + NEUROFORGE_OLLAMA_EMBEDDING_MODEL: ${OLLAMA_EMBEDDING_MODEL:-embeddinggemma} + NEUROFORGE_OLLAMA_NUM_CTX: ${NEUROFORGE_OLLAMA_NUM_CTX:-8192} + NEUROFORGE_OLLAMA_NUM_PREDICT: ${NEUROFORGE_OLLAMA_NUM_PREDICT:-0} ++ # Durable master/subagent scheduler and graph convergence. ++ NEUROFORGE_WORKER_LEASE_SECONDS: ${NEUROFORGE_WORKER_LEASE_SECONDS:-120} ++ NEUROFORGE_WORKER_HEARTBEAT_SECONDS: ${NEUROFORGE_WORKER_HEARTBEAT_SECONDS:-15} ++ NEUROFORGE_WORKER_STALE_AFTER_SECONDS: ${NEUROFORGE_WORKER_STALE_AFTER_SECONDS:-60} ++ NEUROFORGE_WORKER_DEFAULT_MAX_ATTEMPTS: ${NEUROFORGE_WORKER_DEFAULT_MAX_ATTEMPTS:-3} ++ NEUROFORGE_WORKER_RETRY_BACKOFF_SECONDS: ${NEUROFORGE_WORKER_RETRY_BACKOFF_SECONDS:-15} ++ NEUROFORGE_WORKER_MAX_QUEUED_JOBS: ${NEUROFORGE_WORKER_MAX_QUEUED_JOBS:-5000} ++ NEUROFORGE_WORKER_MASTER_APPLY_MAX_ATTEMPTS: ${NEUROFORGE_WORKER_MASTER_APPLY_MAX_ATTEMPTS:-5} ++ NEUROFORGE_WORKER_MASTER_APPLY_BACKOFF_SECONDS: ${NEUROFORGE_WORKER_MASTER_APPLY_BACKOFF_SECONDS:-5} ++ NEUROFORGE_WORKER_JOB_RETENTION_HOURS: ${NEUROFORGE_WORKER_JOB_RETENTION_HOURS:-168} ++ NEUROFORGE_WORKER_MAX_TERMINAL_JOBS: ${NEUROFORGE_WORKER_MAX_TERMINAL_JOBS:-20000} ++ NEUROFORGE_GRAPH_BACKFILL_ENABLED: ${NEUROFORGE_GRAPH_BACKFILL_ENABLED:-true} ++ NEUROFORGE_GRAPH_BACKFILL_INTERVAL_SECONDS: ${NEUROFORGE_GRAPH_BACKFILL_INTERVAL_SECONDS:-10} ++ NEUROFORGE_GRAPH_BACKFILL_BATCH_SIZE: ${NEUROFORGE_GRAPH_BACKFILL_BATCH_SIZE:-64} ++ NEUROFORGE_GRAPH_BACKFILL_MAX_QUEUED: ${NEUROFORGE_GRAPH_BACKFILL_MAX_QUEUED:-256} ++ NEUROFORGE_GRAPH_BACKFILL_MIN_DEGREE: ${NEUROFORGE_GRAPH_BACKFILL_MIN_DEGREE:-3} ++ NEUROFORGE_GRAPH_CANDIDATE_MULTIPLIER: ${NEUROFORGE_GRAPH_CANDIDATE_MULTIPLIER:-6} ++ NEUROFORGE_GRAPH_RETRY_AFTER_MINUTES: ${NEUROFORGE_GRAPH_RETRY_AFTER_MINUTES:-360} ++ NEUROFORGE_GRAPH_REQUIRE_WORKER: ${NEUROFORGE_GRAPH_REQUIRE_WORKER:-true} ++ NEUROFORGE_GRAPH_MAX_HOPS: ${NEUROFORGE_GRAPH_MAX_HOPS:-3} ++ NEUROFORGE_GRAPH_HOP_DECAY: ${NEUROFORGE_GRAPH_HOP_DECAY:-0.60} ++ NEUROFORGE_GRAPH_MAX_EXPANSION: ${NEUROFORGE_GRAPH_MAX_EXPANSION:-64} ++ NEUROFORGE_GRAPH_MIN_EDGE_WEIGHT: ${NEUROFORGE_GRAPH_MIN_EDGE_WEIGHT:-0.05} ++ NEUROFORGE_OFFLOAD_CHAT: ${NEUROFORGE_OFFLOAD_CHAT:-true} ++ NEUROFORGE_OFFLOAD_EMBEDDINGS: ${NEUROFORGE_OFFLOAD_EMBEDDINGS:-true} ++ NEUROFORGE_DISTRIBUTED_INFERENCE_WAIT_SECONDS: ${NEUROFORGE_DISTRIBUTED_INFERENCE_WAIT_SECONDS:-180} + NEUROFORGE_READINESS_OLLAMA_LIVE: ${NEUROFORGE_READINESS_OLLAMA_LIVE:-true} + NEUROFORGE_CONTROLLED_LEARNING: ${NEUROFORGE_CONTROLLED_LEARNING:-true} + NEUROFORGE_GOAL_LEARNING_ENABLED: ${NEUROFORGE_GOAL_LEARNING_ENABLED:-false} +@@ -115,16 +141,20 @@ services: + retries: 8 + start_period: 15s + stop_grace_period: 35s +- neuroforge-worker: +- image: git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge-worker:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.5.9} ++ neuroforge-worker-cpu: ++ image: git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge-worker:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.6.0} + command: + - -server + - http://neuroforge:8080 + - -id +- - mega-worker-1 ++ - mega-cpu-1 + restart: unless-stopped + environment: + NEUROFORGE_WORKER_TOKEN: ${NEUROFORGE_WORKER_TOKEN:?Set a unique NeuroForge worker token} ++ NEUROFORGE_WORKER_RESOURCE_CLASS: cpu ++ NEUROFORGE_WORKER_CAPABILITIES: cpu,vector.relink ++ NEUROFORGE_WORKER_MAX_CONCURRENCY: ${NEUROFORGE_CPU_WORKER_CONCURRENCY:-2} ++ NEUROFORGE_WORKER_HEARTBEAT_INTERVAL: ${NEUROFORGE_WORKER_HEARTBEAT_INTERVAL:-15s} + depends_on: + neuroforge: + condition: service_healthy +@@ -135,8 +165,41 @@ services: + - no-new-privileges:true + cap_drop: + - ALL ++ ++ neuroforge-worker-gpu: ++ image: git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge-worker:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.6.0} ++ command: ++ - -server ++ - http://neuroforge:8080 ++ - -id ++ - mega-gpu-1 ++ restart: unless-stopped ++ environment: ++ NEUROFORGE_WORKER_TOKEN: ${NEUROFORGE_WORKER_TOKEN:?Set a unique NeuroForge worker token} ++ NEUROFORGE_WORKER_RESOURCE_CLASS: gpu ++ NEUROFORGE_WORKER_CAPABILITIES: gpu,model.chat,model.embed ++ NEUROFORGE_WORKER_MAX_CONCURRENCY: ${NEUROFORGE_GPU_WORKER_CONCURRENCY:-1} ++ NEUROFORGE_WORKER_HEARTBEAT_INTERVAL: ${NEUROFORGE_WORKER_HEARTBEAT_INTERVAL:-15s} ++ NEUROFORGE_WORKER_OLLAMA_URL: ${OLLAMA_BASE_URL:-http://ollama:11434} ++ NEUROFORGE_WORKER_OLLAMA_CHAT_MODEL: ${OLLAMA_MODEL:-gemma3} ++ NEUROFORGE_WORKER_OLLAMA_EMBEDDING_MODEL: ${OLLAMA_EMBEDDING_MODEL:-embeddinggemma} ++ NEUROFORGE_WORKER_OLLAMA_NUM_CTX: ${NEUROFORGE_OLLAMA_NUM_CTX:-8192} ++ NEUROFORGE_WORKER_OLLAMA_KEEP_ALIVE: ${OLLAMA_KEEP_ALIVE:-10m} ++ depends_on: ++ neuroforge: ++ condition: service_healthy ++ ollama: ++ condition: service_started ++ read_only: true ++ tmpfs: ++ - /tmp:size=32m,mode=1777 ++ security_opt: ++ - no-new-privileges:true ++ cap_drop: ++ - ALL ++ + agent-data-init: +- image: git.send.nrw/sendnrw/glpi-neuroforge-mega-agent-data-init:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.5.9} ++ image: git.send.nrw/sendnrw/glpi-neuroforge-mega-agent-data-init:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.6.0} + restart: 'no' + user: 0:0 + volumes: +@@ -149,7 +212,7 @@ services: + - CHOWN + - FOWNER + agent: +- image: git.send.nrw/sendnrw/glpi-neuroforge-mega-agent:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.5.9} ++ image: git.send.nrw/sendnrw/glpi-neuroforge-mega-agent:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.6.0} + restart: unless-stopped + environment: + AI_CONTENT_LABEL_ENABLED: ${AI_CONTENT_LABEL_ENABLED:-} +@@ -370,7 +433,7 @@ services: + start_period: 10s + stop_grace_period: 20s + knowledge: +- image: git.send.nrw/sendnrw/glpi-neuroforge-mega-knowledge:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.5.9} ++ image: git.send.nrw/sendnrw/glpi-neuroforge-mega-knowledge:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.6.0} + restart: unless-stopped + environment: + APP_MODE: ${KB_APP_MODE:-editor} +@@ -411,7 +474,7 @@ services: + - ALL + stop_grace_period: 35s + control: +- image: git.send.nrw/sendnrw/glpi-neuroforge-mega-control:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.5.9} ++ image: git.send.nrw/sendnrw/glpi-neuroforge-mega-control:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.6.0} + restart: unless-stopped + environment: + CONTROL_ADDR: :8070 +diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md +index 0d7532d..3b33772 100644 +--- a/docs/ARCHITECTURE.md ++++ b/docs/ARCHITECTURE.md +@@ -200,3 +200,7 @@ Die beiden Evidenzklassen werden absichtlich nicht vermischt. Outcome-Memories l + Für source-begrenzte Exact-Fallbacks hält der Store einen rebuildbaren In-Memory-Index `Provenance.Source -> Memory IDs`. Damit wächst der Fallback mit der betreffenden Integration/Source statt mit dem gesamten Memory-Katalog. HNSW/Disk-PQ bleiben globale Kandidatenindizes. + + Die Qualitätsmessung ist vom Schreibpfad getrennt: `/api/quality/replay` ist read-only und evaluiert live die aktuelle Knowledge-/Outcome-Retrieval-Konfiguration gegen einen bereitgestellten historischen Fallkorpus. ++ ++## v1.6.0 Master/Subagent consistency model ++ ++NeuroForge owns the authoritative durable state. Subagents are stateless executors: they register capabilities, claim only compatible jobs and return lease-fenced results. Jobs and second-phase master applies are persisted through the normal WAL/checkpoint path, so restarts do not lose queued/retrying/apply-wait work. The graph backfill uses ANN candidate selection plus bounded exact relink jobs; pairwise synapses form an n:m adjacency graph and retrieval can traverse several bounded hops. See `MASTER-SUBAGENT-ORCHESTRATOR.md`. +diff --git a/docs/CONTROL-CENTER.md b/docs/CONTROL-CENTER.md +index 1dcd128..93fc56b 100644 +--- a/docs/CONTROL-CENTER.md ++++ b/docs/CONTROL-CENTER.md +@@ -46,3 +46,7 @@ Die eigentlichen Laufzeitmetriken und Einzelfall-Evidenzen bleiben beim Agenten + ## v1.4 Unified Graph Explorer + + The Control Center remains read-only. Its graph views use a dedicated Agent `CONTROL_READ_TOKEN` and the scoped NeuroForge app key. The Engineering Graph is embedded from a reproducible Go AST/Compose snapshot; optional Codebase Memory MCP is developer-only. See `UNIFIED-GRAPH.md`. ++ ++## v1.6.0 Orchestrator / Knowledge Graph ++ ++The read-only Control Center queries scoped NeuroForge endpoints for scheduler and graph state. It displays leader/follower role, online/stale subagents, queue states (`queued`, `claimed`, `retry_wait`, `blocked`, `apply_wait`, `failed`) and graph connectivity (`isolated`, `linked`, `multi_linked`, average/max degree, components). Administrative retry/cancel/backfill actions remain on the separately authenticated NeuroForge admin API and are not exposed by Control Center. +diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md +index 2799abb..d972e31 100644 +--- a/docs/ENVIRONMENT.md ++++ b/docs/ENVIRONMENT.md +@@ -121,3 +121,9 @@ The synthesis call, syntax repair and grounding rewrite use `NEUROFORGE_KB_STAGI + Claim verification checks all material article statements in batches of `NEUROFORGE_KB_STAGING_MAX_VERIFICATION_STATEMENTS`; the value is a batch size, not a total verification cap. A hard safety ceiling of 128 material statements remains. Staging persists `article_quality` with actual article/answer lengths, configured bounds, evidence count, prompt size, expansion status and synthesis token usage. + + Fortinet `support-forum` pages are treated as vendor-community evidence rather than authoritative primary documentation. Editorial `technical-tip` and `troubleshooting-tip` pages remain eligible as authoritative first-party material. ++ ++### Master/Subagent Orchestrator & Knowledge Graph (v1.6.0) ++ ++The master scheduler is controlled by `NEUROFORGE_WORKER_LEASE_SECONDS`, `NEUROFORGE_WORKER_HEARTBEAT_SECONDS`, `NEUROFORGE_WORKER_STALE_AFTER_SECONDS`, retry/backoff/queue/retention settings, and the graph-backfill limits shown in `.env.example`. Local workers use `NEUROFORGE_CPU_WORKER_CONCURRENCY` / `NEUROFORGE_GPU_WORKER_CONCURRENCY`; remote workers use `docker-compose.subagent.yml` with a reachable `NEUROFORGE_MASTER_URL` and unique `NEUROFORGE_CPU_WORKER_ID` / `NEUROFORGE_GPU_WORKER_ID`. ++ ++`NEUROFORGE_OFFLOAD_CHAT` and `NEUROFORGE_OFFLOAD_EMBEDDINGS` prefer a live capability-compatible GPU subagent. If no compatible live subagent is available, NeuroForge continues through its normal provider router. Graph convergence is bounded by `NEUROFORGE_GRAPH_BACKFILL_BATCH_SIZE`, `NEUROFORGE_GRAPH_BACKFILL_MAX_QUEUED`, `NEUROFORGE_GRAPH_BACKFILL_MIN_DEGREE`, `NEUROFORGE_GRAPH_CANDIDATE_MULTIPLIER` and the retry cooldown. Multi-hop retrieval is bounded by `NEUROFORGE_GRAPH_MAX_HOPS`, `NEUROFORGE_GRAPH_HOP_DECAY`, `NEUROFORGE_GRAPH_MAX_EXPANSION` and `NEUROFORGE_GRAPH_MIN_EDGE_WEIGHT`. +diff --git a/docs/IMPLEMENTED.md b/docs/IMPLEMENTED.md +index 0171344..6c16deb 100644 +--- a/docs/IMPLEMENTED.md ++++ b/docs/IMPLEMENTED.md +@@ -26,6 +26,13 @@ + - lokales Outcome-Audit mit `pending|learned|failed`, Retry und unveränderlicher Revisionskette + - Stale-Run-Schutz gegen Lernen aus überholten GLPI-Ticketzuständen + - getrennte Schalter für Research/SearXNG und zeitgesteuerte Autonomie ++- persistenter Master/Subagent-Orchestrator mit Resource-/Capability-Routing, Priorität, DAG-Dependencies, Retry/Backoff und Idempotenz ++- Heartbeat/Lease-Fencing inklusive Expiry-Prüfung und stale-result Schutz ++- zweiphasiger `apply_wait`-Commit für Worker-Ergebnisse, die autoritativen Master-State verändern ++- CPU-Subagents für `vector.relink`, GPU-Subagents für `model.chat`/`model.embed`, optional remote über `docker-compose.subagent.yml` ++- bounded Knowledge-Graph-Backfill für importierte Memories (ANN-Kandidaten statt O(N²)) ++- echte n:m-Adjamenz, Relationstypen, Multi-Hop-Retrieval und Graph-Health-Metriken ++- Memory-Version + Vector-Fingerprint-Fencing gegen Delete/Recreate-Races während Relink + + ## Bewusst nicht automatisiert + +diff --git a/mnt/data/v160_work/docs/MASTER-SUBAGENT-ORCHESTRATOR.md b/docs/MASTER-SUBAGENT-ORCHESTRATOR.md +new file mode 100644 +index 0000000..4d05cff +--- /dev/null ++++ b/docs/MASTER-SUBAGENT-ORCHESTRATOR.md +@@ -0,0 +1,144 @@ ++# NeuroForge Master / Subagent Orchestrator (v1.6.0) ++ ++## Zielbild ++ ++NeuroForge ist der autoritative **Master/Orchestrator**. Subagents besitzen keinen eigenen autoritativen Knowledge-State. Sie registrieren sich mit Resource-Class und Capabilities, ziehen passende Jobs und liefern Ergebnisse unter Lease-Fencing zurück. ++ ++```text ++ durable WAL/checkpoint ++ │ ++ ┌────────▼────────┐ ++ │ NeuroForge │ ++ │ Master │ ++ │ scheduler + DAG │ ++ └───────┬─────────┘ ++ capability /│\ lease-fenced jobs ++ / │ \ ++ ┌───────────┐ │ ┌─────────────┐ ++ │ CPU agent │ │ │ GPU agent │ ++ │ relinking │ │ │ chat/embed │ ++ └───────────┘ │ └─────────────┘ ++ │ ++ more subagents ++``` ++ ++## Konsistenzmodell ++ ++- Jobs sind persistent; Queue, Retry, Dependencies und Worker-Ergebnis überleben einen Master-Neustart. ++- Jeder Claim erhält ein eindeutiges `lease_token`. Eine verspätete Antwort einer abgelaufenen Lease wird auch dann abgewiesen, wenn der Job noch nicht neu vergeben wurde; nach Reclaim verhindert der neue Token zusätzlich stale writes. ++- Heartbeats verlängern nur aktive, zum Worker passende Leases. ++- Fehler werden exponentiell mit begrenztem Backoff wiederholt; `max_attempts` verhindert Endlosschleifen. ++- Abhängigkeiten (`depends_on`) bilden einen DAG: Child-Jobs bleiben `blocked`, bis alle Parents `done` sind. Ein terminal fehlgeschlagener Parent lässt das Child fail-closed scheitern. ++- Worker-Ergebnisse, die autoritativen Master-State verändern (`vector.relink`), verwenden eine zweite persistente Phase `apply_wait`. Der Ergebnisblob ist bereits geschrieben, bevor der Master ihn anwendet. Master-Apply wird idempotent wiederholt und erst danach wird der Job `done`. ++- Terminale Job-Historie wird nach Retention/Cap bereinigt; noch referenzierte Dependency-Jobs werden erhalten. ++- In Clusterbetrieb plant und appliziert nur der aktuelle NeuroForge-Leader. Standalone ist der einzelne Master autoritativ. ++ ++## Resource-/Capability-Routing ++ ++Aktuelle Standardtypen: ++ ++| Job | Resource | Capabilities | Wirkung | ++|---|---|---|---| ++| `vector.relink` | CPU | `cpu,vector.relink` | ANN-Kandidaten exakt nachbewerten, n:m-Synapsen liefern | ++| `model.embed` | GPU | `gpu,model.embed` | Embedding über den GPU-Subagent/Ollama | ++| `model.chat` | GPU | `gpu,model.chat` | Chat/Structured Output über den GPU-Subagent/Ollama | ++ ++Ein Worker kann zusätzliche Capabilities deklarieren. Ein Job wird nur an einen Worker vergeben, der **alle** verlangten Capabilities besitzt und unter seinem `max_concurrency` liegt. ++ ++## Knowledge-Graph statt 1:1 ++ ++Eine Graphdatenbank besteht intern weiterhin aus paarweisen Kanten. Entscheidend für einen echten Graphen ist, dass ein Knoten mehrere Kanten besitzen kann und Traversierung mehrere Hops folgt. v1.6.0 stellt beides sicher: ++ ++- `vector.relink` verbindet einen Memory-Knoten mit mehreren ANN-Nachbarn (`RecallK`). ++- `GraphBackfillMinDegree` erzwingt für geeignete Memories einen Mindestgrad statt einer einzigen Partnerkante. ++- `synapseAdj` hält eine echte Adjazenzliste und verhindert O(E)-Scans pro Hop. ++- Retrieval propagiert bounded über `GraphMaxHops` Hops mit `GraphHopDecay`. ++- Kanten tragen `relations[]`, z.B. `semantic_similarity`, `association`, `coactivation`. ++- `GraphStats` misst `multi_linked_memories`, Max-/Durchschnittsgrad, Connected Components und Largest Component. Damit ist n:m-Verkettung objektiv prüfbar. ++ ++Der aktuelle automatische Backfill erzeugt primär **assoziative/semantische** Beziehungen. Das ist ein Wissensgraph im graphentheoretischen Sinn, aber noch keine vollständig ontologische Aussagenlogik wie `causes`, `solves`, `depends_on`. Solche Relationstypen können später kontrolliert ergänzt werden, ohne das n:m-/Multi-Hop-Fundament zu ändern. ++ ++## Bulk-Backfill für große Knowledge-Bestände ++ ++Der Master legt **nicht** alle möglichen Paare als O(N²)-Jobs an. Für jeden Kandidaten wird zuerst der ANN-Index verwendet; nur eine begrenzte Menge Vektoren wird in einen `vector.relink`-Job geschrieben. ++ ++Wichtige Limits: ++ ++```env ++NEUROFORGE_GRAPH_BACKFILL_BATCH_SIZE=64 ++NEUROFORGE_GRAPH_BACKFILL_MAX_QUEUED=256 ++NEUROFORGE_GRAPH_BACKFILL_MIN_DEGREE=3 ++NEUROFORGE_GRAPH_CANDIDATE_MULTIPLIER=6 ++NEUROFORGE_GRAPH_RETRY_AFTER_MINUTES=360 ++``` ++ ++Bereits wartende Targets werden bei der nächsten Planung übersprungen, damit ein großer Korpus nicht durch die lexikographisch ersten IDs verhungert. ++ ++## Monitoring ++ ++Read-only Control-API: ++ ++```text ++GET /api/v1/integrations/orchestrator/status ++GET /api/v1/integrations/graph/status ++``` ++ ++Admin-API: ++ ++```text ++GET /admin/api/orchestrator/status ++GET /admin/api/orchestrator/jobs ++POST /admin/api/orchestrator/jobs/{id}/retry ++POST /admin/api/orchestrator/jobs/{id}/cancel ++GET /admin/api/graph/status ++POST /admin/api/graph/backfill ++``` ++ ++Prometheus enthält u.a.: ++ ++```text ++neuroforge_jobs{status="queued|claimed|retry_wait|blocked|apply_wait|done|failed|canceled"} ++neuroforge_workers{resource="cpu|gpu",status="online|stale"} ++neuroforge_worker_inflight{worker="...",resource="..."} ++neuroforge_worker_capacity{worker="...",resource="..."} ++neuroforge_graph_memories{state="linked|isolated|multi_linked"} ++neuroforge_graph_max_degree ++neuroforge_graph_average_degree ++neuroforge_graph_connected_components ++neuroforge_graph_largest_component ++``` ++ ++## Remote Subagent ++ ++Auf einem zusätzlichen Host nur `docker-compose.subagent.yml` und eine kleine `.env` bereitstellen. Beispiel GPU: ++ ++```env ++IMAGE_TAG=1.6.0 ++NEUROFORGE_MASTER_URL=https://neuroforge.internal.example ++NEUROFORGE_WORKER_TOKEN= ++NEUROFORGE_GPU_WORKER_ID=gpu-node-02 ++NEUROFORGE_WORKER_OLLAMA_URL=http://127.0.0.1:11434 ++OLLAMA_MODEL=gemma4 ++OLLAMA_EMBEDDING_MODEL=embeddinggemma ++NEUROFORGE_GPU_WORKER_CONCURRENCY=1 ++``` ++ ++Dann: ++ ++```bash ++docker compose -f docker-compose.subagent.yml --profile gpu up -d ++``` ++ ++Der Master-Port muss vom Subagent erreichbar sein. Für Netze außerhalb eines vertrauenswürdigen internen Segments gehört TLS/mTLS vor den Master-Endpunkt (Reverse Proxy/Service Mesh). Der Worker-Token ist ein Service-Credential und muss separat von App/Admin/Integration-Tokens bleiben. ++ ++## Operationaler Zielzustand ++ ++Ein gesunder Zustand hat: ++ ++- mindestens einen `online` CPU-Worker für Graph-Konvergenz, ++- optional mindestens einen `online` GPU-Worker für Offload, ++- keine dauerhaft wachsende `failed`-/`apply_wait`-Queue, ++- sinkende Zahl `isolated` und steigende Zahl `multi_linked`, ++- `max_degree > 1` und `largest_component > 1`, ++- bounded Queue und stabile Worker-Leases, ++- keine Readiness-Abhängigkeit des Masters vom Worker (verhindert Startup-Deadlocks). +diff --git a/mnt/data/v160_work/docs/MIGRATION-v1.5.9-to-v1.6.0.md b/docs/MIGRATION-v1.5.9-to-v1.6.0.md +new file mode 100644 +index 0000000..5931d9f +--- /dev/null ++++ b/docs/MIGRATION-v1.5.9-to-v1.6.0.md +@@ -0,0 +1,28 @@ ++# Migration v1.5.9 → v1.6.0 ++ ++Die Migration ist **nicht destruktiv**. Keine Volumes, Knowledge-Dateien, Memories, Goals oder Staging-Drafts löschen. ++ ++1. v1.6.0 Images bauen/publizieren. ++2. `IMAGE_TAG=1.6.0` setzen. ++3. Die neuen Orchestrator-/Graph-Variablen aus `.env.example` übernehmen. Die Defaults sind produktionsnah und bounded. ++4. Den bisherigen einzelnen `neuroforge-worker` durch `neuroforge-worker-cpu` und `neuroforge-worker-gpu` ersetzen. ++5. Stack neu erzeugen; Volumes beibehalten. ++ ++```bash ++docker compose --profile research pull ++docker compose --profile research up -d --force-recreate --remove-orphans ++``` ++ ++Danach prüfen: ++ ++```bash ++docker compose ps ++curl -s -H "Authorization: Bearer $NEUROFORGE_CONTROL_READ_TOKEN" \ ++ http://127.0.0.1:${NEUROFORGE_HOST_PORT:-8090}/api/v1/integrations/orchestrator/status | jq ++curl -s -H "Authorization: Bearer $NEUROFORGE_CONTROL_READ_TOKEN" \ ++ http://127.0.0.1:${NEUROFORGE_HOST_PORT:-8090}/api/v1/integrations/graph/status | jq ++``` ++ ++Ein gesunder Zustand hat mindestens einen online CPU-Worker, keine dauerhaft wachsende `failed`/`apply_wait` Queue und eine sinkende Zahl isolierter Memories. GPU-Offload ist optional; ohne online GPU-Subagent fällt NeuroForge auf den normalen Provider-Router zurück. ++ ++Remote Subagents erhalten eindeutige `NEUROFORGE_CPU_WORKER_ID` bzw. `NEUROFORGE_GPU_WORKER_ID`. Außerhalb eines vertrauenswürdigen internen Netzes den Master nur hinter TLS/mTLS veröffentlichen. +diff --git a/mnt/data/v160_work/docs/RELEASE-v1.6.0.md b/docs/RELEASE-v1.6.0.md +new file mode 100644 +index 0000000..2d66842 +--- /dev/null ++++ b/docs/RELEASE-v1.6.0.md +@@ -0,0 +1,32 @@ ++# GLPI NeuroForge Mega v1.6.0 ++ ++v1.6.0 erweitert NeuroForge von einem einzelnen Workerpfad zu einem dauerhaften Master/Subagent-Orchestrator und schließt die Graph-Lücke großer Knowledge-Imports. ++ ++## Orchestrator ++ ++- persistente Jobs im NeuroForge-WAL/Checkpoint ++- Resource-Class + Capability-Routing (`cpu`, `gpu`, `vector.relink`, `model.chat`, `model.embed`) ++- Priorität, Idempotency-Key, Parent/Dependencies, Retry mit begrenztem Backoff und Max-Attempts ++- Heartbeats, Worker-Staleness, Max-Concurrency und lease-token fencing ++- abgelaufene Leases werden auch dann abgewiesen, wenn noch kein anderer Worker den Job übernommen hat ++- Worker-Ergebnisse mit Master-Mutation verwenden `apply_wait`: Resultat zuerst persistent, danach idempotenter Master-Apply ++- Apply-Retries überleben Neustarts; terminale Jobhistorie wird bounded bereinigt ++- Admin-Status, Jobliste, Retry, Cancel und manueller Graph-Backfill ++ ++## Knowledge Graph ++ ++- importierte `knowledge.chunk`-Memories werden bounded nachverknüpft ++- ANN bestimmt nur Kandidaten; CPU-Subagents führen exakte Similarity-Bewertung aus ++- ein Memory kann mehrere Edges besitzen; `GraphBackfillMinDegree` verhindert den früher praktisch isolierten/1:1-artigen Zustand ++- Adjazenzindex + bounded Multi-Hop-Retrieval mit Hop-Decay ++- Synapsen können mehrere `relations[]` tragen ++- Graphmetriken: linked/isolated/multi-linked, average/max degree, connected components, largest component ++- Relink-Payload bindet Target und Kandidaten an Version + Vector-Fingerprint; veraltete Delete/Recreate-Ergebnisse werden verworfen ++ ++## Deployment ++ ++Der lokale Stack enthält getrennte CPU- und GPU-Subagents. Zusätzliche Hosts verwenden `docker-compose.subagent.yml`. Für Remote-Netze ist TLS/mTLS vor dem Master-Endpunkt erforderlich; der Worker-Token bleibt ein separates Service-Credential. ++ ++## Compatibility ++ ++Die Migration ist nicht destruktiv. Bestehende Memories/Knowledge/Goals bleiben erhalten. Nach dem Upgrade beginnt der Graph-Backfill kontrolliert im Hintergrund; deshalb können `neuroforge_synapses` und die Graph-Connectivity-Metriken nach und nach steigen. +diff --git a/platform/neuroforge/cmd/server/main.go b/platform/neuroforge/cmd/server/main.go +index 0f438cd..9886bcd 100644 +--- a/platform/neuroforge/cmd/server/main.go ++++ b/platform/neuroforge/cmd/server/main.go +@@ -254,6 +254,108 @@ func run() (retErr error) { + } + } + ++ // Master/subagent orchestrator and graph bootstrap. Every option is explicit ++ // and environment-owned only when set, preserving admin-managed config otherwise. ++ workerEnv := []string{ ++ "NEUROFORGE_WORKER_LEASE_SECONDS", "NEUROFORGE_WORKER_HEARTBEAT_SECONDS", "NEUROFORGE_WORKER_STALE_AFTER_SECONDS", ++ "NEUROFORGE_WORKER_DEFAULT_MAX_ATTEMPTS", "NEUROFORGE_WORKER_RETRY_BACKOFF_SECONDS", "NEUROFORGE_WORKER_MAX_QUEUED_JOBS", ++ "NEUROFORGE_WORKER_JOB_RETENTION_HOURS", "NEUROFORGE_WORKER_MAX_TERMINAL_JOBS", ++ "NEUROFORGE_WORKER_MASTER_APPLY_MAX_ATTEMPTS", "NEUROFORGE_WORKER_MASTER_APPLY_BACKOFF_SECONDS", ++ "NEUROFORGE_GRAPH_BACKFILL_ENABLED", "NEUROFORGE_GRAPH_BACKFILL_INTERVAL_SECONDS", "NEUROFORGE_GRAPH_BACKFILL_BATCH_SIZE", ++ "NEUROFORGE_GRAPH_BACKFILL_MAX_QUEUED", "NEUROFORGE_GRAPH_BACKFILL_MIN_DEGREE", "NEUROFORGE_GRAPH_CANDIDATE_MULTIPLIER", ++ "NEUROFORGE_GRAPH_RETRY_AFTER_MINUTES", "NEUROFORGE_GRAPH_REQUIRE_WORKER", "NEUROFORGE_GRAPH_MAX_HOPS", ++ "NEUROFORGE_GRAPH_HOP_DECAY", "NEUROFORGE_GRAPH_MAX_EXPANSION", "NEUROFORGE_GRAPH_MIN_EDGE_WEIGHT", ++ "NEUROFORGE_OFFLOAD_CHAT", "NEUROFORGE_OFFLOAD_EMBEDDINGS", "NEUROFORGE_DISTRIBUTED_INFERENCE_WAIT_SECONDS", ++ } ++ hasWorkerEnv := false ++ for _, name := range workerEnv { ++ if _, ok := os.LookupEnv(name); ok { ++ hasWorkerEnv = true ++ break ++ } ++ } ++ if hasWorkerEnv { ++ cfg := s.Config() ++ if v, ok := envInt("NEUROFORGE_WORKER_LEASE_SECONDS"); ok { ++ cfg.Worker.LeaseSeconds = v ++ } ++ if v, ok := envInt("NEUROFORGE_WORKER_HEARTBEAT_SECONDS"); ok { ++ cfg.Worker.HeartbeatSeconds = v ++ } ++ if v, ok := envInt("NEUROFORGE_WORKER_STALE_AFTER_SECONDS"); ok { ++ cfg.Worker.StaleAfterSeconds = v ++ } ++ if v, ok := envInt("NEUROFORGE_WORKER_DEFAULT_MAX_ATTEMPTS"); ok { ++ cfg.Worker.DefaultMaxAttempts = v ++ } ++ if v, ok := envInt("NEUROFORGE_WORKER_RETRY_BACKOFF_SECONDS"); ok { ++ cfg.Worker.RetryBackoffSeconds = v ++ } ++ if v, ok := envInt("NEUROFORGE_WORKER_MAX_QUEUED_JOBS"); ok { ++ cfg.Worker.MaxQueuedJobs = v ++ } ++ if v, ok := envInt("NEUROFORGE_WORKER_JOB_RETENTION_HOURS"); ok { ++ cfg.Worker.JobRetentionHours = v ++ } ++ if v, ok := envInt("NEUROFORGE_WORKER_MAX_TERMINAL_JOBS"); ok { ++ cfg.Worker.MaxTerminalJobs = v ++ } ++ if v, ok := envInt("NEUROFORGE_WORKER_MASTER_APPLY_MAX_ATTEMPTS"); ok { ++ cfg.Worker.MasterApplyMaxAttempts = v ++ } ++ if v, ok := envInt("NEUROFORGE_WORKER_MASTER_APPLY_BACKOFF_SECONDS"); ok { ++ cfg.Worker.MasterApplyBackoffSeconds = v ++ } ++ if v, ok := envBool("NEUROFORGE_GRAPH_BACKFILL_ENABLED"); ok { ++ cfg.Worker.GraphBackfillEnabled = v ++ } ++ if v, ok := envInt("NEUROFORGE_GRAPH_BACKFILL_INTERVAL_SECONDS"); ok { ++ cfg.Worker.GraphBackfillIntervalS = v ++ } ++ if v, ok := envInt("NEUROFORGE_GRAPH_BACKFILL_BATCH_SIZE"); ok { ++ cfg.Worker.GraphBackfillBatchSize = v ++ } ++ if v, ok := envInt("NEUROFORGE_GRAPH_BACKFILL_MAX_QUEUED"); ok { ++ cfg.Worker.GraphBackfillMaxQueued = v ++ } ++ if v, ok := envInt("NEUROFORGE_GRAPH_BACKFILL_MIN_DEGREE"); ok { ++ cfg.Worker.GraphBackfillMinDegree = v ++ } ++ if v, ok := envInt("NEUROFORGE_GRAPH_CANDIDATE_MULTIPLIER"); ok { ++ cfg.Worker.GraphCandidateMultiplier = v ++ } ++ if v, ok := envInt("NEUROFORGE_GRAPH_RETRY_AFTER_MINUTES"); ok { ++ cfg.Worker.GraphRetryAfterMinutes = v ++ } ++ if v, ok := envBool("NEUROFORGE_GRAPH_REQUIRE_WORKER"); ok { ++ cfg.Worker.RequireWorkerForGraph = v ++ } ++ if v, ok := envBool("NEUROFORGE_OFFLOAD_CHAT"); ok { ++ cfg.Worker.OffloadChat = v ++ } ++ if v, ok := envBool("NEUROFORGE_OFFLOAD_EMBEDDINGS"); ok { ++ cfg.Worker.OffloadEmbeddings = v ++ } ++ if v, ok := envInt("NEUROFORGE_DISTRIBUTED_INFERENCE_WAIT_SECONDS"); ok { ++ cfg.Worker.DistributedInferenceWaitS = v ++ } ++ if v, ok := envInt("NEUROFORGE_GRAPH_MAX_HOPS"); ok { ++ cfg.Brain.GraphMaxHops = v ++ } ++ if v, ok := envFloat("NEUROFORGE_GRAPH_HOP_DECAY"); ok { ++ cfg.Brain.GraphHopDecay = v ++ } ++ if v, ok := envInt("NEUROFORGE_GRAPH_MAX_EXPANSION"); ok { ++ cfg.Brain.GraphMaxExpansion = v ++ } ++ if v, ok := envFloat("NEUROFORGE_GRAPH_MIN_EDGE_WEIGHT"); ok { ++ cfg.Brain.GraphMinEdgeWeight = v ++ } ++ if err := s.UpdateConfig(cfg); err != nil { ++ return fmt.Errorf("apply orchestrator/graph environment bootstrap: %w", err) ++ } ++ } ++ + r := provider.NewRouter(s) + c := cost.New(s) + b := brain.New(s, r, c) +@@ -336,6 +438,7 @@ func run() (retErr error) { + maintenanceCtx, stopMaintenance := context.WithCancel(rootCtx) + defer stopMaintenance() + go b.RunV6Maintenance(maintenanceCtx) ++ go b.RunOrchestrator(maintenanceCtx) + api := httpapi.New(s, b, r, c) + if v, ok := envBool("NEUROFORGE_READINESS_OLLAMA_LIVE"); ok { + api.SetReadinessOllamaLive(v) +diff --git a/platform/neuroforge/cmd/worker/main.go b/platform/neuroforge/cmd/worker/main.go +index a6ffd1d..d189683 100644 +--- a/platform/neuroforge/cmd/worker/main.go ++++ b/platform/neuroforge/cmd/worker/main.go +@@ -2,6 +2,7 @@ package main + + import ( + "bytes" ++ "context" + "encoding/json" + "flag" + "fmt" +@@ -9,20 +10,30 @@ import ( + "log" + "net/http" + "os" ++ "os/signal" + "sort" ++ "strconv" + "strings" ++ "sync" ++ "syscall" + "time" + + "neuroforge/internal/core" + "neuroforge/internal/vector" + ) + ++const workerVersion = "1.6.0" ++ + type relinkPayload struct { +- TargetID string `json:"target_id"` +- Target []float32 `json:"target"` +- Candidates []struct { +- ID string `json:"id"` +- Vector []float32 `json:"vector"` ++ TargetID string `json:"target_id"` ++ TargetVersion int64 `json:"target_version"` ++ TargetFingerprint string `json:"target_fingerprint"` ++ Target []float32 `json:"target"` ++ Candidates []struct { ++ ID string `json:"id"` ++ Version int64 `json:"version"` ++ Fingerprint string `json:"fingerprint"` ++ Vector []float32 `json:"vector"` + } `json:"candidates"` + K int `json:"k"` + MinSimilarity float64 `json:"min_similarity"` +@@ -36,58 +47,226 @@ type relinkResult struct { + Neighbors []neighbor `json:"neighbors"` + } + ++type modelEmbedPayload struct { ++ Text string `json:"text"` ++ Model string `json:"model,omitempty"` ++} ++ ++type modelChatPayload struct { ++ Instructions string `json:"instructions,omitempty"` ++ Input string `json:"input"` ++ Model string `json:"model,omitempty"` ++ MaxOutput int `json:"max_output,omitempty"` ++ JSONMode bool `json:"json_mode,omitempty"` ++} ++ ++type modelUsage struct { ++ InputTokens int64 `json:"input_tokens"` ++ CachedTokens int64 `json:"cached_tokens,omitempty"` ++ OutputTokens int64 `json:"output_tokens"` ++} ++ ++type modelResult struct { ++ Text string `json:"text,omitempty"` ++ Vector []float32 `json:"vector,omitempty"` ++ Usage modelUsage `json:"usage"` ++ Provider string `json:"provider"` ++ Model string `json:"model"` ++ NodeID string `json:"node_id"` ++} ++ ++type workerConfig struct { ++ ID string ++ Server string ++ Token string ++ Interval time.Duration ++ Heartbeat time.Duration ++ ResourceClass string ++ Capabilities []string ++ MaxConcurrency int ++ Hostname string ++ OllamaURL string ++ OllamaChatModel string ++ OllamaEmbedModel string ++ OllamaNumCtx int ++ OllamaKeepAlive string ++} ++ ++type leaseSet struct { ++ mu sync.RWMutex ++ m map[string]string ++} ++ ++func (l *leaseSet) add(id, token string) { ++ l.mu.Lock() ++ l.m[id] = token ++ l.mu.Unlock() ++} ++func (l *leaseSet) del(id string) { ++ l.mu.Lock() ++ delete(l.m, id) ++ l.mu.Unlock() ++} ++func (l *leaseSet) snapshot() map[string]string { ++ l.mu.RLock() ++ defer l.mu.RUnlock() ++ out := make(map[string]string, len(l.m)) ++ for k, v := range l.m { ++ out[k] = v ++ } ++ return out ++} ++ + func main() { +- server := flag.String("server", "http://localhost:8080", "NeuroForge server") +- id := flag.String("id", hostname(), "worker id") ++ server := flag.String("server", "http://localhost:8080", "NeuroForge master/orchestrator server") ++ id := flag.String("id", hostname(), "subagent worker id") + interval := flag.Duration("interval", 2*time.Second, "poll interval") + flag.Parse() + token := strings.TrimSpace(os.Getenv("NEUROFORGE_WORKER_TOKEN")) + if token == "" { + log.Fatal("NEUROFORGE_WORKER_TOKEN is required") + } +- client := &http.Client{Timeout: 180 * time.Second} +- log.Printf("worker %s polling %s", *id, *server) +- for { +- job, err := claim(client, *server, token, *id) ++ resource := lowerDefault(os.Getenv("NEUROFORGE_WORKER_RESOURCE_CLASS"), "cpu") ++ caps := csv(os.Getenv("NEUROFORGE_WORKER_CAPABILITIES")) ++ if len(caps) == 0 { ++ if resource == "gpu" { ++ caps = []string{"gpu", "model.chat", "model.embed"} ++ } else { ++ caps = []string{"cpu", "vector.relink"} ++ } ++ } ++ maxConcurrency := envInt("NEUROFORGE_WORKER_MAX_CONCURRENCY", 1) ++ if maxConcurrency < 1 { ++ maxConcurrency = 1 ++ } ++ heartbeat := envDuration("NEUROFORGE_WORKER_HEARTBEAT_INTERVAL", 15*time.Second) ++ cfg := workerConfig{ ++ ID: *id, Server: strings.TrimRight(*server, "/"), Token: token, Interval: *interval, ++ Heartbeat: heartbeat, ResourceClass: resource, Capabilities: caps, ++ MaxConcurrency: maxConcurrency, Hostname: hostname(), ++ OllamaURL: strings.TrimRight(firstNonEmpty(os.Getenv("NEUROFORGE_WORKER_OLLAMA_URL"), os.Getenv("OLLAMA_BASE_URL"), os.Getenv("OLLAMA_URL")), "/"), ++ OllamaChatModel: firstNonEmpty(os.Getenv("NEUROFORGE_WORKER_OLLAMA_CHAT_MODEL"), os.Getenv("OLLAMA_MODEL")), ++ OllamaEmbedModel: firstNonEmpty(os.Getenv("NEUROFORGE_WORKER_OLLAMA_EMBEDDING_MODEL"), os.Getenv("OLLAMA_EMBEDDING_MODEL")), ++ OllamaNumCtx: envInt("NEUROFORGE_WORKER_OLLAMA_NUM_CTX", 8192), ++ OllamaKeepAlive: firstNonEmpty(os.Getenv("NEUROFORGE_WORKER_OLLAMA_KEEP_ALIVE"), "10m"), ++ } ++ ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) ++ defer stop() ++ // Control-plane requests must never hang forever on a dead master. Inference ++ // uses its own client and is bounded by the durable job timeout/context. ++ controlClient := &http.Client{Timeout: 30 * time.Second} ++ inferenceClient := &http.Client{Timeout: 0} ++ leases := &leaseSet{m: map[string]string{}} ++ if err := register(controlClient, cfg); err != nil { ++ log.Printf("initial register failed: %v", err) ++ } ++ go heartbeatLoop(ctx, controlClient, cfg, leases) ++ log.Printf("subagent %s polling %s resource=%s caps=%s concurrency=%d", cfg.ID, cfg.Server, cfg.ResourceClass, strings.Join(cfg.Capabilities, ","), cfg.MaxConcurrency) ++ var wg sync.WaitGroup ++ for i := 0; i < cfg.MaxConcurrency; i++ { ++ wg.Add(1) ++ go func(slot int) { ++ defer wg.Done() ++ pollLoop(ctx, controlClient, inferenceClient, cfg, leases, slot) ++ }(i) ++ } ++ <-ctx.Done() ++ log.Printf("subagent shutdown requested; waiting for active slots") ++ wg.Wait() ++} ++ ++func waitOrDone(ctx context.Context, d time.Duration) bool { ++ t := time.NewTimer(d) ++ defer t.Stop() ++ select { ++ case <-ctx.Done(): ++ return false ++ case <-t.C: ++ return true ++ } ++} ++ ++func pollLoop(root context.Context, controlClient, inferenceClient *http.Client, cfg workerConfig, leases *leaseSet, slot int) { ++ for root.Err() == nil { ++ job, err := claim(controlClient, cfg) + if err != nil { +- log.Printf("claim: %v", err) +- time.Sleep(*interval) ++ log.Printf("slot=%d claim: %v", slot, err) ++ if !waitOrDone(root, cfg.Interval) { ++ return ++ } + continue + } + if job == nil { +- time.Sleep(*interval) ++ if !waitOrDone(root, cfg.Interval) { ++ return ++ } + continue + } +- res, jobErr := run(job) +- if err := complete(client, *server, token, *id, job.ID, res, jobErr); err != nil { +- log.Printf("complete %s: %v", job.ID, err) ++ leases.add(job.ID, job.LeaseToken) ++ jobCtx := root ++ cancel := func() {} ++ if job.TimeoutSeconds > 0 { ++ jobCtx, cancel = context.WithTimeout(root, time.Duration(job.TimeoutSeconds)*time.Second) ++ } ++ res, jobErr := run(jobCtx, inferenceClient, cfg, job) ++ cancel() ++ if root.Err() != nil && jobErr == "" { ++ jobErr = root.Err().Error() ++ } ++ if err := complete(controlClient, cfg, job.ID, job.LeaseToken, res, jobErr); err != nil { ++ log.Printf("slot=%d complete %s: %v", slot, job.ID, err) ++ } else if jobErr != "" { ++ log.Printf("slot=%d job %s %s returned error: %s", slot, job.ID, job.Type, jobErr) + } else { +- log.Printf("job %s %s done", job.ID, job.Type) ++ log.Printf("slot=%d job %s %s complete", slot, job.ID, job.Type) + } ++ leases.del(job.ID) + } + } +-func hostname() string { +- h, _ := os.Hostname() +- if h == "" { +- h = "worker" ++ ++func workerBody(cfg workerConfig, leases map[string]string) map[string]any { ++ return map[string]any{ ++ "worker_id": cfg.ID, "resource_class": cfg.ResourceClass, "capabilities": cfg.Capabilities, ++ "max_concurrency": cfg.MaxConcurrency, "version": workerVersion, "hostname": cfg.Hostname, ++ "active_leases": leases, + } +- return h + } +-func claim(c *http.Client, server, token, id string) (*core.Job, error) { +- body, _ := json.Marshal(map[string]string{"worker_id": id}) +- req, _ := http.NewRequest("POST", strings.TrimRight(server, "/")+"/api/v1/worker/claim", bytes.NewReader(body)) +- req.Header.Set("Authorization", "Bearer "+token) ++ ++func register(c *http.Client, cfg workerConfig) error { ++ return postJSON(c, cfg.Server+"/api/v1/worker/register", cfg.Token, workerBody(cfg, nil), nil, http.StatusOK) ++} ++ ++func heartbeatLoop(ctx context.Context, c *http.Client, cfg workerConfig, leases *leaseSet) { ++ t := time.NewTicker(cfg.Heartbeat) ++ defer t.Stop() ++ for { ++ select { ++ case <-ctx.Done(): ++ return ++ case <-t.C: ++ var state core.WorkerState ++ if err := postJSON(c, cfg.Server+"/api/v1/worker/heartbeat", cfg.Token, workerBody(cfg, leases.snapshot()), &state, http.StatusOK); err != nil { ++ log.Printf("heartbeat: %v", err) ++ } ++ } ++ } ++} ++ ++func claim(c *http.Client, cfg workerConfig) (*core.Job, error) { ++ body, _ := json.Marshal(workerBody(cfg, nil)) ++ req, _ := http.NewRequest("POST", cfg.Server+"/api/v1/worker/claim", bytes.NewReader(body)) ++ req.Header.Set("Authorization", "Bearer "+cfg.Token) + req.Header.Set("Content-Type", "application/json") + resp, err := c.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() +- if resp.StatusCode == 204 { ++ if resp.StatusCode == http.StatusNoContent { + return nil, nil + } +- raw, _ := io.ReadAll(io.LimitReader(resp.Body, 8<<20)) +- if resp.StatusCode != 200 { ++ raw, _ := io.ReadAll(io.LimitReader(resp.Body, 32<<20)) ++ if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, raw) + } + var j core.Job +@@ -96,7 +275,8 @@ func claim(c *http.Client, server, token, id string) (*core.Job, error) { + } + return &j, nil + } +-func run(j *core.Job) (json.RawMessage, string) { ++ ++func run(ctx context.Context, c *http.Client, cfg workerConfig, j *core.Job) (json.RawMessage, string) { + switch j.Type { + case "vector.relink": + var p relinkPayload +@@ -104,10 +284,10 @@ func run(j *core.Job) (json.RawMessage, string) { + return nil, err.Error() + } + out := relinkResult{TargetID: p.TargetID} +- for _, c := range p.Candidates { +- sim := vector.Cosine(p.Target, c.Vector) ++ for _, candidate := range p.Candidates { ++ sim := vector.Cosine(p.Target, candidate.Vector) + if sim >= p.MinSimilarity { +- out.Neighbors = append(out.Neighbors, neighbor{ID: c.ID, Similarity: sim}) ++ out.Neighbors = append(out.Neighbors, neighbor{ID: candidate.ID, Similarity: sim}) + } + } + sort.Slice(out.Neighbors, func(i, k int) bool { return out.Neighbors[i].Similarity > out.Neighbors[k].Similarity }) +@@ -116,23 +296,222 @@ func run(j *core.Job) (json.RawMessage, string) { + } + b, _ := json.Marshal(out) + return b, "" ++ case "model.embed": ++ if !hasCap(cfg.Capabilities, "model.embed") { ++ return nil, "worker lacks model.embed capability" ++ } ++ var p modelEmbedPayload ++ if err := json.Unmarshal(j.Payload, &p); err != nil { ++ return nil, err.Error() ++ } ++ out, err := ollamaEmbed(ctx, c, cfg, p) ++ if err != nil { ++ return nil, err.Error() ++ } ++ b, _ := json.Marshal(out) ++ return b, "" ++ case "model.chat": ++ if !hasCap(cfg.Capabilities, "model.chat") { ++ return nil, "worker lacks model.chat capability" ++ } ++ var p modelChatPayload ++ if err := json.Unmarshal(j.Payload, &p); err != nil { ++ return nil, err.Error() ++ } ++ out, err := ollamaChat(ctx, c, cfg, p) ++ if err != nil { ++ return nil, err.Error() ++ } ++ b, _ := json.Marshal(out) ++ return b, "" + default: + return nil, "unsupported job type: " + j.Type + } + } +-func complete(c *http.Client, server, token, id, jobID string, result json.RawMessage, jobErr string) error { +- body, _ := json.Marshal(map[string]any{"worker_id": id, "job_id": jobID, "result": result, "error": jobErr}) +- req, _ := http.NewRequest("POST", strings.TrimRight(server, "/")+"/api/v1/worker/complete", bytes.NewReader(body)) +- req.Header.Set("Authorization", "Bearer "+token) ++ ++func ollamaEmbed(ctx context.Context, c *http.Client, cfg workerConfig, p modelEmbedPayload) (modelResult, error) { ++ if cfg.OllamaURL == "" { ++ return modelResult{}, fmt.Errorf("NEUROFORGE_WORKER_OLLAMA_URL is required for model.embed") ++ } ++ model := firstNonEmpty(p.Model, cfg.OllamaEmbedModel) ++ if model == "" || strings.TrimSpace(p.Text) == "" { ++ return modelResult{}, fmt.Errorf("embedding model and text are required") ++ } ++ body := map[string]any{"model": model, "input": p.Text, "keep_alive": cfg.OllamaKeepAlive} ++ var resp struct { ++ Embeddings [][]float32 `json:"embeddings"` ++ PromptEvalCount int64 `json:"prompt_eval_count"` ++ } ++ if err := postOllama(ctx, c, cfg.OllamaURL+"/api/embed", body, &resp); err != nil { ++ return modelResult{}, err ++ } ++ if len(resp.Embeddings) == 0 || len(resp.Embeddings[0]) == 0 { ++ return modelResult{}, fmt.Errorf("ollama embedding response is empty") ++ } ++ return modelResult{Vector: resp.Embeddings[0], Usage: modelUsage{InputTokens: resp.PromptEvalCount}, Provider: "ollama", Model: model, NodeID: cfg.ID}, nil ++} ++ ++func ollamaChat(ctx context.Context, c *http.Client, cfg workerConfig, p modelChatPayload) (modelResult, error) { ++ if cfg.OllamaURL == "" { ++ return modelResult{}, fmt.Errorf("NEUROFORGE_WORKER_OLLAMA_URL is required for model.chat") ++ } ++ model := firstNonEmpty(p.Model, cfg.OllamaChatModel) ++ if model == "" || strings.TrimSpace(p.Input) == "" { ++ return modelResult{}, fmt.Errorf("chat model and input are required") ++ } ++ messages := []map[string]string{} ++ if strings.TrimSpace(p.Instructions) != "" { ++ messages = append(messages, map[string]string{"role": "system", "content": p.Instructions}) ++ } ++ messages = append(messages, map[string]string{"role": "user", "content": p.Input}) ++ body := map[string]any{"model": model, "messages": messages, "stream": false, "keep_alive": cfg.OllamaKeepAlive} ++ if p.JSONMode { ++ body["format"] = "json" ++ } ++ options := map[string]any{} ++ if cfg.OllamaNumCtx > 0 { ++ options["num_ctx"] = cfg.OllamaNumCtx ++ } ++ if p.MaxOutput > 0 { ++ options["num_predict"] = p.MaxOutput ++ } ++ if len(options) > 0 { ++ body["options"] = options ++ } ++ var resp struct { ++ Message struct { ++ Content string `json:"content"` ++ } `json:"message"` ++ PromptEvalCount int64 `json:"prompt_eval_count"` ++ EvalCount int64 `json:"eval_count"` ++ } ++ if err := postOllama(ctx, c, cfg.OllamaURL+"/api/chat", body, &resp); err != nil { ++ return modelResult{}, err ++ } ++ return modelResult{Text: strings.TrimSpace(resp.Message.Content), Usage: modelUsage{InputTokens: resp.PromptEvalCount, OutputTokens: resp.EvalCount}, Provider: "ollama", Model: model, NodeID: cfg.ID}, nil ++} ++ ++func postOllama(ctx context.Context, c *http.Client, url string, body any, out any) error { ++ raw, _ := json.Marshal(body) ++ req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(raw)) ++ if err != nil { ++ return err ++ } + req.Header.Set("Content-Type", "application/json") + resp, err := c.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() +- raw, _ := io.ReadAll(io.LimitReader(resp.Body, 2<<20)) ++ b, _ := io.ReadAll(io.LimitReader(resp.Body, 32<<20)) + if resp.StatusCode < 200 || resp.StatusCode >= 300 { +- return fmt.Errorf("HTTP %d: %s", resp.StatusCode, raw) ++ return fmt.Errorf("ollama HTTP %d: %s", resp.StatusCode, strings.TrimSpace(string(b))) ++ } ++ if err := json.Unmarshal(b, out); err != nil { ++ return fmt.Errorf("decode ollama response: %w", err) ++ } ++ return nil ++} ++ ++func complete(c *http.Client, cfg workerConfig, jobID, leaseToken string, result json.RawMessage, jobErr string) error { ++ body := map[string]any{"worker_id": cfg.ID, "job_id": jobID, "lease_token": leaseToken, "result": result, "error": jobErr} ++ return postJSON(c, cfg.Server+"/api/v1/worker/complete", cfg.Token, body, nil, http.StatusOK) ++} ++ ++func postJSON(c *http.Client, url, token string, body any, out any, want int) error { ++ raw, _ := json.Marshal(body) ++ req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(raw)) ++ if err != nil { ++ return err ++ } ++ req.Header.Set("Authorization", "Bearer "+token) ++ req.Header.Set("Content-Type", "application/json") ++ resp, err := c.Do(req) ++ if err != nil { ++ return err ++ } ++ defer resp.Body.Close() ++ b, _ := io.ReadAll(io.LimitReader(resp.Body, 8<<20)) ++ if resp.StatusCode != want { ++ return fmt.Errorf("HTTP %d: %s", resp.StatusCode, strings.TrimSpace(string(b))) ++ } ++ if out != nil && len(bytes.TrimSpace(b)) > 0 { ++ if err := json.Unmarshal(b, out); err != nil { ++ return err ++ } + } + return nil + } ++ ++func hostname() string { ++ h, _ := os.Hostname() ++ if h == "" { ++ h = "worker" ++ } ++ return h ++} ++ ++func csv(v string) []string { ++ seen := map[string]bool{} ++ out := []string{} ++ for _, x := range strings.Split(v, ",") { ++ x = strings.ToLower(strings.TrimSpace(x)) ++ if x != "" && !seen[x] { ++ seen[x] = true ++ out = append(out, x) ++ } ++ } ++ sort.Strings(out) ++ return out ++} ++ ++func hasCap(caps []string, want string) bool { ++ want = strings.ToLower(strings.TrimSpace(want)) ++ for _, x := range caps { ++ if strings.ToLower(strings.TrimSpace(x)) == want { ++ return true ++ } ++ } ++ return false ++} ++ ++func lowerDefault(v, def string) string { ++ v = strings.ToLower(strings.TrimSpace(v)) ++ if v == "" { ++ return def ++ } ++ return v ++} ++ ++func firstNonEmpty(xs ...string) string { ++ for _, x := range xs { ++ if strings.TrimSpace(x) != "" { ++ return strings.TrimSpace(x) ++ } ++ } ++ return "" ++} ++ ++func envInt(name string, def int) int { ++ v := strings.TrimSpace(os.Getenv(name)) ++ if v == "" { ++ return def ++ } ++ n, err := strconv.Atoi(v) ++ if err != nil { ++ return def ++ } ++ return n ++} ++ ++func envDuration(name string, def time.Duration) time.Duration { ++ v := strings.TrimSpace(os.Getenv(name)) ++ if v == "" { ++ return def ++ } ++ d, err := time.ParseDuration(v) ++ if err != nil || d <= 0 { ++ return def ++ } ++ return d ++} +diff --git a/platform/neuroforge/docker-compose.yml b/platform/neuroforge/docker-compose.yml +index 154b4aa..618d6aa 100644 +--- a/platform/neuroforge/docker-compose.yml ++++ b/platform/neuroforge/docker-compose.yml +@@ -29,28 +29,46 @@ services: + retries: 4 + start_period: 10s + +- worker: ++ worker-cpu: + build: + context: . + target: worker +- command: +- - -server +- - http://neuroforge:8080 +- - -id +- - worker-compose-1 ++ command: ["-server", "http://neuroforge:8080", "-id", "cpu-compose-1"] + environment: + NEUROFORGE_WORKER_TOKEN: ${NEUROFORGE_WORKER_TOKEN:?Set a unique NeuroForge worker token} ++ NEUROFORGE_WORKER_RESOURCE_CLASS: cpu ++ NEUROFORGE_WORKER_CAPABILITIES: cpu,vector.relink ++ NEUROFORGE_WORKER_MAX_CONCURRENCY: ${NEUROFORGE_CPU_WORKER_CONCURRENCY:-2} + depends_on: + neuroforge: + condition: service_healthy + restart: unless-stopped + read_only: true +- tmpfs: +- - /tmp:size=32m,mode=1777 +- security_opt: +- - no-new-privileges:true +- cap_drop: +- - ALL ++ tmpfs: ["/tmp:size=32m,mode=1777"] ++ security_opt: ["no-new-privileges:true"] ++ cap_drop: ["ALL"] ++ ++ worker-gpu: ++ build: ++ context: . ++ target: worker ++ command: ["-server", "http://neuroforge:8080", "-id", "gpu-compose-1"] ++ environment: ++ NEUROFORGE_WORKER_TOKEN: ${NEUROFORGE_WORKER_TOKEN:?Set a unique NeuroForge worker token} ++ NEUROFORGE_WORKER_RESOURCE_CLASS: gpu ++ NEUROFORGE_WORKER_CAPABILITIES: gpu,model.chat,model.embed ++ NEUROFORGE_WORKER_MAX_CONCURRENCY: ${NEUROFORGE_GPU_WORKER_CONCURRENCY:-1} ++ NEUROFORGE_WORKER_OLLAMA_URL: ${OLLAMA_BASE_URL:-http://ollama:11434} ++ NEUROFORGE_WORKER_OLLAMA_CHAT_MODEL: ${OLLAMA_MODEL:-gemma3} ++ NEUROFORGE_WORKER_OLLAMA_EMBEDDING_MODEL: ${OLLAMA_EMBEDDING_MODEL:-embeddinggemma} ++ depends_on: ++ neuroforge: ++ condition: service_healthy ++ restart: unless-stopped ++ read_only: true ++ tmpfs: ["/tmp:size=32m,mode=1777"] ++ security_opt: ["no-new-privileges:true"] ++ cap_drop: ["ALL"] + + volumes: + neuroforge-data: +diff --git a/platform/neuroforge/internal/brain/brain.go b/platform/neuroforge/internal/brain/brain.go +index 835bdde..290caef 100644 +--- a/platform/neuroforge/internal/brain/brain.go ++++ b/platform/neuroforge/internal/brain/brain.go +@@ -3,10 +3,14 @@ package brain + import ( + "bytes" + "context" ++ "crypto/sha256" ++ "encoding/binary" ++ "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" ++ "math" + "net/http" + "regexp" + "sort" +@@ -87,6 +91,12 @@ func (e *Engine) embed(ctx context.Context, text string) (provider.EmbedResult, + if route == "" { + route = "auto" + } ++ if (route == "auto" || route == "ollama") && nodeID == "" { ++ if res, used, offloadErr := e.distributedEmbed(ctx, model, text); used && offloadErr == nil { ++ costUSD, recErr := e.cost.Record(res.Provider, res.Model, "embedding", res.Usage) ++ return res, costUSD, recErr ++ } ++ } + if route == "auto" { + res, err := e.router.EmbedOn(ctx, "ollama", model, nodeID, text) + if err == nil { +@@ -160,6 +170,12 @@ func (e *Engine) chatModelLimitOnMode(ctx context.Context, providerName, model, + if route == "" { + route = "auto" + } ++ if (route == "auto" || route == "ollama") && nodeID == "" { ++ if res, used, offloadErr := e.distributedChat(ctx, model, instructions, input, maxOutput, jsonMode); used && offloadErr == nil { ++ costUSD, recErr := e.cost.Record(res.Provider, res.Model, "chat", res.Usage) ++ return res, costUSD, recErr ++ } ++ } + if route == "auto" { + var res provider.ChatResult + var err error +@@ -986,15 +1002,19 @@ func minFloat(a, b float64) float64 { + } + + type relinkPayload struct { +- TargetID string `json:"target_id"` +- Target []float32 `json:"target"` +- Candidates []relinkCandidate `json:"candidates"` +- K int `json:"k"` +- MinSimilarity float64 `json:"min_similarity"` ++ TargetID string `json:"target_id"` ++ TargetVersion int64 `json:"target_version"` ++ TargetFingerprint string `json:"target_fingerprint"` ++ Target []float32 `json:"target"` ++ Candidates []relinkCandidate `json:"candidates"` ++ K int `json:"k"` ++ MinSimilarity float64 `json:"min_similarity"` + } + type relinkCandidate struct { +- ID string `json:"id"` +- Vector []float32 `json:"vector"` ++ ID string `json:"id"` ++ Version int64 `json:"version"` ++ Fingerprint string `json:"fingerprint"` ++ Vector []float32 `json:"vector"` + } + type RelinkResult struct { + TargetID string `json:"target_id"` +@@ -1004,16 +1024,49 @@ type RelinkResult struct { + } `json:"neighbors"` + } + ++func vectorFingerprint(v []float32) string { ++ h := sha256.New() ++ var buf [4]byte ++ for _, x := range v { ++ binary.LittleEndian.PutUint32(buf[:], math.Float32bits(x)) ++ _, _ = h.Write(buf[:]) ++ } ++ sum := h.Sum(nil) ++ return hex.EncodeToString(sum[:12]) ++} ++ ++var errObsoleteRelink = errors.New("obsolete vector.relink result") ++ + func (e *Engine) enqueueRelink(m *core.Memory) (*core.Job, error) { + cfg := e.store.Config() +- snap := e.store.MemoriesSnapshot() +- p := relinkPayload{TargetID: m.ID, Target: m.Vector, K: cfg.Brain.RecallK, MinSimilarity: cfg.Brain.MinSimilarity} +- for _, x := range snap { +- if x.ID != m.ID && len(x.Vector) == len(m.Vector) { +- p.Candidates = append(p.Candidates, relinkCandidate{ID: x.ID, Vector: x.Vector}) ++ if m == nil || m.ID == "" || len(m.Vector) == 0 { ++ return nil, errors.New("relink target requires id and vector") ++ } ++ mult := cfg.Worker.GraphCandidateMultiplier ++ if mult < 2 { ++ mult = 4 ++ } ++ candidateK := cfg.Brain.RecallK * mult ++ if candidateK < cfg.Brain.RecallK+4 { ++ candidateK = cfg.Brain.RecallK + 4 ++ } ++ if candidateK > 256 { ++ candidateK = 256 ++ } ++ hits := e.store.SearchVector(m.Vector, candidateK+1, cfg.Brain.MinSimilarity, 0) ++ p := relinkPayload{TargetID: m.ID, TargetVersion: m.Version, TargetFingerprint: vectorFingerprint(m.Vector), Target: append([]float32(nil), m.Vector...), K: cfg.Brain.RecallK, MinSimilarity: cfg.Brain.MinSimilarity} ++ for _, h := range hits { ++ if h.Memory.ID == m.ID || len(h.Memory.Vector) != len(m.Vector) { ++ continue + } ++ p.Candidates = append(p.Candidates, relinkCandidate{ID: h.Memory.ID, Version: h.Memory.Version, Fingerprint: vectorFingerprint(h.Memory.Vector), Vector: append([]float32(nil), h.Memory.Vector...)}) + } +- return e.store.EnqueueJob("vector.relink", p) ++ return e.store.EnqueueJobSpec(store.JobSpec{ ++ Type: "vector.relink", Payload: p, Priority: 20, ResourceClass: "cpu", ++ RequiredCapabilities: []string{"cpu", "vector.relink"}, ++ RequiresMasterApply: true, ++ IdempotencyKey: "vector.relink:" + m.ID + ":v" + strconv.FormatInt(m.Version, 10), ++ }) + } + func (e *Engine) localRelink(m *core.Memory) error { + cfg := e.store.Config() +@@ -1026,20 +1079,53 @@ func (e *Engine) localRelink(m *core.Memory) error { + return nil + } + func (e *Engine) ApplyJobResult(j *core.Job) error { +- if j.Type != "vector.relink" || j.Status != "done" { ++ if j == nil || j.Type != "vector.relink" || (j.Status != "done" && j.Status != "apply_wait") { + return nil + } ++ var p relinkPayload ++ if err := json.Unmarshal(j.Payload, &p); err != nil { ++ return fmt.Errorf("decode relink payload during master apply: %w", err) ++ } ++ current, ok := e.store.GetMemory(p.TargetID) ++ if !ok || current.Version != p.TargetVersion || vectorFingerprint(current.Vector) != p.TargetFingerprint { ++ // The worker computed against an older delete/recreate or memory version. ++ // Treat the result as obsolete rather than poisoning the current graph; the ++ // backfill planner will schedule the current version again. ++ return errObsoleteRelink ++ } + var r RelinkResult + if err := json.Unmarshal(j.Result, &r); err != nil { + return err + } ++ if r.TargetID != p.TargetID { ++ return fmt.Errorf("relink result target %q does not match payload target %q", r.TargetID, p.TargetID) ++ } ++ candidateMeta := make(map[string]relinkCandidate, len(p.Candidates)) ++ for _, c := range p.Candidates { ++ candidateMeta[c.ID] = c ++ } + sort.Slice(r.Neighbors, func(i, j int) bool { return r.Neighbors[i].Similarity > r.Neighbors[j].Similarity }) + for _, n := range r.Neighbors { +- if err := e.reinforcePair(r.TargetID, n.ID, n.Similarity, n.Similarity); err != nil { ++ meta, expected := candidateMeta[n.ID] ++ if !expected { ++ // A worker may only return neighbors from the bounded master-selected ++ // candidate set. Rejecting extras closes a trust-boundary gap. ++ continue ++ } ++ neighbor, ok := e.store.GetMemory(n.ID) ++ if !ok || neighbor.Version != meta.Version || vectorFingerprint(neighbor.Vector) != meta.Fingerprint { ++ continue ++ } ++ cfg := e.store.Config() ++ delta := cfg.Brain.LearningRate * n.Similarity ++ if delta <= 0 { ++ delta = n.Similarity ++ } ++ if err := e.store.UpsertRelationEvidence(r.TargetID, n.ID, "semantic_similarity", n.Similarity, delta, cfg.Brain.MaxSynapseWeight); err != nil { + return err + } + } +- return nil ++ return e.store.MarkGraphLinked(r.TargetID) + } + + // SearchByProvenanceSources embeds text once and searches only the requested +diff --git a/mnt/data/v160_work/platform/neuroforge/internal/brain/distributed.go b/platform/neuroforge/internal/brain/distributed.go +new file mode 100644 +index 0000000..265989f +--- /dev/null ++++ b/platform/neuroforge/internal/brain/distributed.go +@@ -0,0 +1,107 @@ ++package brain ++ ++import ( ++ "context" ++ "encoding/json" ++ "errors" ++ "fmt" ++ "time" ++ ++ "neuroforge/internal/provider" ++ "neuroforge/internal/store" ++) ++ ++type distributedModelResult struct { ++ Text string `json:"text,omitempty"` ++ Vector []float32 `json:"vector,omitempty"` ++ Usage struct { ++ InputTokens int64 `json:"input_tokens"` ++ CachedTokens int64 `json:"cached_tokens,omitempty"` ++ OutputTokens int64 `json:"output_tokens"` ++ } `json:"usage"` ++ Provider string `json:"provider"` ++ Model string `json:"model"` ++ NodeID string `json:"node_id"` ++} ++ ++func (e *Engine) waitDistributedJob(ctx context.Context, jID string) (*distributedModelResult, error) { ++ t := time.NewTicker(50 * time.Millisecond) ++ defer t.Stop() ++ for { ++ select { ++ case <-ctx.Done(): ++ _ = e.store.CancelJob(jID, "caller context ended: "+ctx.Err().Error()) ++ return nil, ctx.Err() ++ case <-t.C: ++ j, ok := e.store.Job(jID) ++ if !ok { ++ return nil, errors.New("distributed job disappeared") ++ } ++ switch j.Status { ++ case "done": ++ var out distributedModelResult ++ if err := json.Unmarshal(j.Result, &out); err != nil { ++ return nil, fmt.Errorf("decode distributed model result: %w", err) ++ } ++ return &out, nil ++ case "failed", "canceled": ++ return nil, fmt.Errorf("distributed job %s: %s", j.Status, j.Error) ++ } ++ } ++ } ++} ++ ++func (e *Engine) distributedEmbed(ctx context.Context, model, text string) (provider.EmbedResult, bool, error) { ++ cfg := e.store.Config() ++ if !cfg.Worker.OffloadEmbeddings || !e.store.HasLiveWorker("gpu", "gpu", "model.embed") { ++ return provider.EmbedResult{}, false, nil ++ } ++ wait := cfg.Worker.DistributedInferenceWaitS ++ if wait <= 0 { ++ wait = 180 ++ } ++ j, err := e.store.EnqueueJobSpec(store.JobSpec{ ++ Type: "model.embed", Payload: map[string]any{"text": text, "model": model}, ++ Priority: 100, ResourceClass: "gpu", RequiredCapabilities: []string{"gpu", "model.embed"}, ++ MaxAttempts: 2, TimeoutSeconds: wait, ++ }) ++ if err != nil { ++ return provider.EmbedResult{}, true, err ++ } ++ jobCtx, cancel := context.WithTimeout(ctx, time.Duration(wait)*time.Second) ++ defer cancel() ++ out, err := e.waitDistributedJob(jobCtx, j.ID) ++ if err != nil { ++ return provider.EmbedResult{}, true, err ++ } ++ if len(out.Vector) == 0 { ++ return provider.EmbedResult{}, true, errors.New("distributed embedding returned empty vector") ++ } ++ return provider.EmbedResult{Vector: out.Vector, Provider: out.Provider, Model: out.Model, NodeID: out.NodeID, Usage: provider.Usage{InputTokens: out.Usage.InputTokens, CachedTokens: out.Usage.CachedTokens, OutputTokens: out.Usage.OutputTokens}}, true, nil ++} ++ ++func (e *Engine) distributedChat(ctx context.Context, model, instructions, input string, maxOutput int, jsonMode bool) (provider.ChatResult, bool, error) { ++ cfg := e.store.Config() ++ if !cfg.Worker.OffloadChat || !e.store.HasLiveWorker("gpu", "gpu", "model.chat") { ++ return provider.ChatResult{}, false, nil ++ } ++ wait := cfg.Worker.DistributedInferenceWaitS ++ if wait <= 0 { ++ wait = 180 ++ } ++ j, err := e.store.EnqueueJobSpec(store.JobSpec{ ++ Type: "model.chat", Payload: map[string]any{"instructions": instructions, "input": input, "model": model, "max_output": maxOutput, "json_mode": jsonMode}, ++ Priority: 100, ResourceClass: "gpu", RequiredCapabilities: []string{"gpu", "model.chat"}, ++ MaxAttempts: 2, TimeoutSeconds: wait, ++ }) ++ if err != nil { ++ return provider.ChatResult{}, true, err ++ } ++ jobCtx, cancel := context.WithTimeout(ctx, time.Duration(wait)*time.Second) ++ defer cancel() ++ out, err := e.waitDistributedJob(jobCtx, j.ID) ++ if err != nil { ++ return provider.ChatResult{}, true, err ++ } ++ return provider.ChatResult{Text: out.Text, Provider: out.Provider, Model: out.Model, NodeID: out.NodeID, Usage: provider.Usage{InputTokens: out.Usage.InputTokens, CachedTokens: out.Usage.CachedTokens, OutputTokens: out.Usage.OutputTokens}}, true, nil ++} +diff --git a/mnt/data/v160_work/platform/neuroforge/internal/brain/orchestrator.go b/platform/neuroforge/internal/brain/orchestrator.go +new file mode 100644 +index 0000000..992fe75 +--- /dev/null ++++ b/platform/neuroforge/internal/brain/orchestrator.go +@@ -0,0 +1,117 @@ ++package brain ++ ++import ( ++ "context" ++ "errors" ++ "time" ++ ++ "neuroforge/internal/core" ++) ++ ++// PlanGraphBackfill keeps the associative graph converging toward a durable, ++// multi-neighbor state. It deliberately uses a bounded queue so a large KB ++// import cannot create an unbounded O(N^2) burst of relink payloads. ++func (e *Engine) PlanGraphBackfill() (int, error) { ++ cfg := e.store.Config() ++ if !e.store.IsOrchestratorLeader() { ++ return 0, nil ++ } ++ wc := cfg.Worker ++ if !wc.GraphBackfillEnabled { ++ return 0, nil ++ } ++ if wc.RequireWorkerForGraph && !e.store.HasLiveWorker("cpu", "cpu", "vector.relink") { ++ return 0, nil ++ } ++ maxQueued := wc.GraphBackfillMaxQueued ++ if maxQueued <= 0 { ++ maxQueued = 512 ++ } ++ pending := e.store.PendingJobCount("vector.relink") ++ if pending >= maxQueued { ++ return 0, nil ++ } ++ batch := wc.GraphBackfillBatchSize ++ if batch <= 0 { ++ batch = 64 ++ } ++ if room := maxQueued - pending; batch > room { ++ batch = room ++ } ++ retryAfter := time.Duration(wc.GraphRetryAfterMinutes) * time.Minute ++ if retryAfter <= 0 { ++ retryAfter = 6 * time.Hour ++ } ++ items := e.store.GraphBackfillCandidates(batch, wc.GraphBackfillMinDegree, retryAfter) ++ planned := 0 ++ for i := range items { ++ if _, err := e.enqueueRelink(&items[i]); err != nil { ++ return planned, err ++ } ++ planned++ ++ } ++ return planned, nil ++} ++ ++// ApplyAndFinalizeJob is the durable second phase for jobs whose worker result ++// mutates authoritative master state. The worker result is already in the WAL; ++// apply can therefore be retried after a master crash without recomputing it. ++func (e *Engine) ApplyAndFinalizeJob(j *core.Job) error { ++ if j == nil || j.Status != "apply_wait" { ++ return nil ++ } ++ err := e.ApplyJobResult(j) ++ if errors.Is(err, errObsoleteRelink) { ++ // Obsolete computation is not a failed mutation. Close the old job and ++ // leave the current memory unlinked so normal backfill schedules it again. ++ err = nil ++ } ++ _, finishErr := e.store.FinishMasterApply(j.ID, err) ++ if finishErr != nil { ++ return finishErr ++ } ++ return err ++} ++ ++func (e *Engine) ApplyPendingJobResults(limit int) (applied, failed int) { ++ jobs := e.store.PendingMasterApplyJobs(limit) ++ for i := range jobs { ++ if err := e.ApplyAndFinalizeJob(&jobs[i]); err != nil { ++ failed++ ++ } else { ++ applied++ ++ } ++ } ++ return applied, failed ++} ++ ++func (e *Engine) RunOrchestrator(ctx context.Context) { ++ var lastPrune time.Time ++ // Run quickly on boot so a previously imported knowledge corpus starts ++ // linking as soon as a capable worker has registered. ++ timer := time.NewTimer(2 * time.Second) ++ defer timer.Stop() ++ for { ++ select { ++ case <-ctx.Done(): ++ return ++ case <-timer.C: ++ cfg := e.store.Config() ++ if !e.store.IsOrchestratorLeader() { ++ timer.Reset(5 * time.Second) ++ continue ++ } ++ _, _ = e.ApplyPendingJobResults(64) ++ if lastPrune.IsZero() || time.Since(lastPrune) >= 10*time.Minute { ++ _, _ = e.store.PruneTerminalJobs(time.Duration(cfg.Worker.JobRetentionHours)*time.Hour, cfg.Worker.MaxTerminalJobs) ++ lastPrune = time.Now().UTC() ++ } ++ interval := time.Duration(cfg.Worker.GraphBackfillIntervalS) * time.Second ++ if interval < 2*time.Second { ++ interval = 10 * time.Second ++ } ++ _, _ = e.PlanGraphBackfill() ++ timer.Reset(interval) ++ } ++ } ++} +diff --git a/mnt/data/v160_work/platform/neuroforge/internal/brain/orchestrator_test.go b/platform/neuroforge/internal/brain/orchestrator_test.go +new file mode 100644 +index 0000000..d05cae1 +--- /dev/null ++++ b/platform/neuroforge/internal/brain/orchestrator_test.go +@@ -0,0 +1,200 @@ ++package brain ++ ++import ( ++ "encoding/json" ++ "errors" ++ "testing" ++ "time" ++ ++ "neuroforge/internal/core" ++ "neuroforge/internal/cost" ++ "neuroforge/internal/provider" ++ "neuroforge/internal/store" ++) ++ ++func TestGraphBackfillPlansBoundedJobsAndDurablyAppliesResult(t *testing.T) { ++ s, err := store.New(t.TempDir()) ++ if err != nil { ++ t.Fatal(err) ++ } ++ defer s.Close() ++ cfg := s.Config() ++ cfg.Worker.GraphBackfillEnabled = true ++ cfg.Worker.RequireWorkerForGraph = true ++ cfg.Worker.GraphBackfillBatchSize = 4 ++ cfg.Worker.GraphBackfillMaxQueued = 8 ++ cfg.Worker.GraphBackfillMinDegree = 2 ++ cfg.Worker.GraphCandidateMultiplier = 4 ++ cfg.Brain.RecallK = 3 ++ cfg.Brain.MinSimilarity = .1 ++ if err := s.UpdateConfig(cfg); err != nil { ++ t.Fatal(err) ++ } ++ for i := 0; i < 20; i++ { ++ m := core.Memory{ID: store.NewID("kb"), Kind: "knowledge.chunk", MemoryType: core.MemorySemantic, Text: "knowledge", Vector: []float32{1, float32(i+1) / 100}} ++ if err := s.AddMemory(&m); err != nil { ++ t.Fatal(err) ++ } ++ } ++ if _, err := s.RegisterWorker(store.WorkerHeartbeat{ID: "cpu-1", ResourceClass: "cpu", Capabilities: []string{"cpu", "vector.relink"}, MaxConcurrency: 2}); err != nil { ++ t.Fatal(err) ++ } ++ e := New(s, provider.NewRouter(s), cost.New(s)) ++ planned, err := e.PlanGraphBackfill() ++ if err != nil { ++ t.Fatal(err) ++ } ++ if planned != 4 || s.PendingJobCount("vector.relink") != 4 { ++ t.Fatalf("planned=%d pending=%d", planned, s.PendingJobCount("vector.relink")) ++ } ++ jobs := s.JobsSnapshot(10, "", "vector.relink") ++ if len(jobs) != 4 { ++ t.Fatalf("jobs=%d", len(jobs)) ++ } ++ full, ok := s.Job(jobs[0].ID) ++ if !ok || !full.RequiresMasterApply { ++ t.Fatalf("job=%+v", full) ++ } ++ var payload relinkPayload ++ if err := json.Unmarshal(full.Payload, &payload); err != nil { ++ t.Fatal(err) ++ } ++ if len(payload.Candidates) == 0 || len(payload.Candidates) > 256 { ++ t.Fatalf("candidate payload is not bounded: %d", len(payload.Candidates)) ++ } ++ ++ worker := store.WorkerHeartbeat{ID: "cpu-1", ResourceClass: "cpu", Capabilities: []string{"cpu", "vector.relink"}, MaxConcurrency: 2} ++ claimed, err := s.ClaimJobForWorker(worker, time.Minute) ++ if err != nil || claimed == nil { ++ t.Fatalf("claim=%+v err=%v", claimed, err) ++ } ++ var p relinkPayload ++ if err := json.Unmarshal(claimed.Payload, &p); err != nil { ++ t.Fatal(err) ++ } ++ if len(p.Candidates) < 2 { ++ t.Fatalf("need at least two candidates: %+v", p) ++ } ++ result, _ := json.Marshal(map[string]any{"target_id": p.TargetID, "neighbors": []map[string]any{{"id": p.Candidates[0].ID, "similarity": .95}, {"id": p.Candidates[1].ID, "similarity": .90}}}) ++ completed, err := s.CompleteJobLease(claimed.ID, worker.ID, claimed.LeaseToken, result, "") ++ if err != nil { ++ t.Fatal(err) ++ } ++ if completed.Status != "apply_wait" { ++ t.Fatalf("worker result must be durable before master apply: %+v", completed) ++ } ++ if err := e.ApplyAndFinalizeJob(completed); err != nil { ++ t.Fatal(err) ++ } ++ final, _ := s.Job(completed.ID) ++ if final.Status != "done" || final.ApplyAttempts != 1 { ++ t.Fatalf("final=%+v", final) ++ } ++ if degree := s.GraphDegree(p.TargetID); degree < 2 { ++ t.Fatalf("target degree=%d; expected real multi-neighbor graph", degree) ++ } ++ m, ok := s.GetMemory(p.TargetID) ++ if !ok || m.GraphLinkedAt.IsZero() || m.GraphVersion != m.Version { ++ t.Fatalf("graph linkage checkpoint missing: %+v", m) ++ } ++} ++ ++func TestMasterApplyReplayDoesNotInflateSemanticEdge(t *testing.T) { ++ s, err := store.New(t.TempDir()) ++ if err != nil { ++ t.Fatal(err) ++ } ++ defer s.Close() ++ for _, id := range []string{"A", "B"} { ++ m := core.Memory{ID: id, Kind: "knowledge.chunk", MemoryType: core.MemorySemantic, Text: id, Vector: []float32{1, .1}} ++ if err := s.AddMemory(&m); err != nil { ++ t.Fatal(err) ++ } ++ } ++ e := New(s, provider.NewRouter(s), cost.New(s)) ++ a, _ := s.GetMemory("A") ++ b, _ := s.GetMemory("B") ++ payload, _ := json.Marshal(relinkPayload{ ++ TargetID: "A", TargetVersion: a.Version, TargetFingerprint: vectorFingerprint(a.Vector), Target: append([]float32(nil), a.Vector...), ++ Candidates: []relinkCandidate{{ID: "B", Version: b.Version, Fingerprint: vectorFingerprint(b.Vector), Vector: append([]float32(nil), b.Vector...)}}, ++ K: 1, MinSimilarity: .1, ++ }) ++ result, _ := json.Marshal(map[string]any{"target_id": "A", "neighbors": []map[string]any{{"id": "B", "similarity": .9}}}) ++ j := &core.Job{Type: "vector.relink", Status: "apply_wait", Payload: payload, Result: result} ++ if err := e.ApplyJobResult(j); err != nil { ++ t.Fatal(err) ++ } ++ if err := e.ApplyJobResult(j); err != nil { ++ t.Fatal(err) ++ } ++ g := s.KnowledgeGraph("A", 1, 10) ++ if len(g.Edges) != 1 { ++ t.Fatalf("edges=%+v", g.Edges) ++ } ++ if g.Edges[0].Activations != 1 { ++ t.Fatalf("retry replay inflated activations: %+v", g.Edges[0]) ++ } ++} ++ ++func TestRelinkResultFromDeletedRecreatedMemoryIsObsolete(t *testing.T) { ++ s, err := store.New(t.TempDir()) ++ if err != nil { ++ t.Fatal(err) ++ } ++ defer s.Close() ++ for _, m := range []core.Memory{ ++ {ID: "A", Kind: "knowledge.chunk", MemoryType: core.MemorySemantic, Text: "old A", Vector: []float32{1, .1}}, ++ {ID: "B", Kind: "knowledge.chunk", MemoryType: core.MemorySemantic, Text: "B", Vector: []float32{1, .2}}, ++ } { ++ mm := m ++ if err := s.AddMemory(&mm); err != nil { ++ t.Fatal(err) ++ } ++ } ++ e := New(s, provider.NewRouter(s), cost.New(s)) ++ job, err := e.enqueueRelink(mustMemory(t, s, "A")) ++ if err != nil { ++ t.Fatal(err) ++ } ++ var p relinkPayload ++ if err := json.Unmarshal(job.Payload, &p); err != nil { ++ t.Fatal(err) ++ } ++ if len(p.Candidates) == 0 { ++ t.Fatal("expected relink candidate") ++ } ++ ++ // Simulate a knowledge sync replacing the stable ID while an old worker is ++ // still computing. Delete removes old edges; the re-created memory may reuse ++ // version 1, so the vector fingerprint is the required fencing signal. ++ if err := s.DeleteMemory("A"); err != nil { ++ t.Fatal(err) ++ } ++ recreated := core.Memory{ID: "A", Kind: "knowledge.chunk", MemoryType: core.MemorySemantic, Text: "new A", Vector: []float32{.1, 1}} ++ if err := s.AddMemory(&recreated); err != nil { ++ t.Fatal(err) ++ } ++ ++ result, _ := json.Marshal(map[string]any{"target_id": "A", "neighbors": []map[string]any{{"id": p.Candidates[0].ID, "similarity": .99}}}) ++ job.Status = "apply_wait" ++ job.Result = result ++ if err := e.ApplyJobResult(job); !errors.Is(err, errObsoleteRelink) { ++ t.Fatalf("expected obsolete relink result, got %v", err) ++ } ++ if got := s.GraphDegree("A"); got != 0 { ++ t.Fatalf("obsolete worker result mutated recreated memory graph: degree=%d", got) ++ } ++ m := mustMemory(t, s, "A") ++ if !m.GraphLinkedAt.IsZero() || m.GraphVersion != 0 { ++ t.Fatalf("recreated memory was incorrectly marked linked: %+v", m) ++ } ++} ++ ++func mustMemory(t *testing.T, s *store.Store, id string) *core.Memory { ++ t.Helper() ++ m, ok := s.GetMemory(id) ++ if !ok { ++ t.Fatalf("memory %s missing", id) ++ } ++ return m ++} +diff --git a/platform/neuroforge/internal/core/types.go b/platform/neuroforge/internal/core/types.go +index 5cd39f3..78b379b 100644 +--- a/platform/neuroforge/internal/core/types.go ++++ b/platform/neuroforge/internal/core/types.go +@@ -173,6 +173,10 @@ type Config struct { + DecayPerDay float64 `json:"decay_per_day"` + MaxSynapseWeight float64 `json:"max_synapse_weight"` + GraphBonus float64 `json:"graph_bonus"` ++ GraphMaxHops int `json:"graph_max_hops"` ++ GraphHopDecay float64 `json:"graph_hop_decay"` ++ GraphMaxExpansion int `json:"graph_max_expansion"` ++ GraphMinEdgeWeight float64 `json:"graph_min_edge_weight"` + MaxContextMemories int `json:"max_context_memories"` + AutoLearn bool `json:"auto_learn"` + ExternalRelinkWorker bool `json:"external_relink_worker"` +@@ -388,7 +392,27 @@ type Config struct { + } `json:"api"` + + Worker struct { +- LeaseSeconds int `json:"lease_seconds"` ++ LeaseSeconds int `json:"lease_seconds"` ++ HeartbeatSeconds int `json:"heartbeat_seconds"` ++ StaleAfterSeconds int `json:"stale_after_seconds"` ++ DefaultMaxAttempts int `json:"default_max_attempts"` ++ RetryBackoffSeconds int `json:"retry_backoff_seconds"` ++ MaxQueuedJobs int `json:"max_queued_jobs"` ++ MasterApplyMaxAttempts int `json:"master_apply_max_attempts"` ++ MasterApplyBackoffSeconds int `json:"master_apply_backoff_seconds"` ++ JobRetentionHours int `json:"job_retention_hours"` ++ MaxTerminalJobs int `json:"max_terminal_jobs"` ++ GraphBackfillEnabled bool `json:"graph_backfill_enabled"` ++ GraphBackfillIntervalS int `json:"graph_backfill_interval_seconds"` ++ GraphBackfillBatchSize int `json:"graph_backfill_batch_size"` ++ GraphBackfillMaxQueued int `json:"graph_backfill_max_queued"` ++ GraphBackfillMinDegree int `json:"graph_backfill_min_degree"` ++ GraphCandidateMultiplier int `json:"graph_candidate_multiplier"` ++ GraphRetryAfterMinutes int `json:"graph_retry_after_minutes"` ++ RequireWorkerForGraph bool `json:"require_worker_for_graph"` ++ OffloadChat bool `json:"offload_chat"` ++ OffloadEmbeddings bool `json:"offload_embeddings"` ++ DistributedInferenceWaitS int `json:"distributed_inference_wait_seconds"` + } `json:"worker"` + } + +@@ -469,6 +493,9 @@ type Memory struct { + ConsolidationCount int `json:"consolidation_count,omitempty"` + EvidenceSourceIDs []string `json:"evidence_source_ids,omitempty"` + EvidenceCount int `json:"evidence_count,omitempty"` ++ GraphLinkedAt time.Time `json:"graph_linked_at,omitempty"` ++ GraphDegree int `json:"graph_degree,omitempty"` ++ GraphVersion int64 `json:"graph_version,omitempty"` + Provenance MemoryProvenance `json:"provenance,omitempty"` + } + +@@ -477,6 +504,7 @@ type Synapse struct { + B string `json:"b"` + Weight float64 `json:"weight"` + Similarity float64 `json:"similarity"` ++ Relations []string `json:"relations,omitempty"` + Activations int64 `json:"activations"` + LastUpdated time.Time `json:"last_updated"` + } +@@ -494,16 +522,50 @@ type UsageEvent struct { + } + + type Job struct { +- ID string `json:"id"` +- Type string `json:"type"` +- Payload json.RawMessage `json:"payload"` +- Result json.RawMessage `json:"result,omitempty"` +- Status string `json:"status"` +- ClaimedBy string `json:"claimed_by,omitempty"` +- LeaseUntil time.Time `json:"lease_until,omitempty"` +- Error string `json:"error,omitempty"` +- CreatedAt time.Time `json:"created_at"` +- UpdatedAt time.Time `json:"updated_at"` ++ ID string `json:"id"` ++ Type string `json:"type"` ++ Payload json.RawMessage `json:"payload"` ++ Result json.RawMessage `json:"result,omitempty"` ++ Status string `json:"status"` ++ Priority int `json:"priority,omitempty"` ++ ResourceClass string `json:"resource_class,omitempty"` ++ RequiredCapabilities []string `json:"required_capabilities,omitempty"` ++ IdempotencyKey string `json:"idempotency_key,omitempty"` ++ ParentJobID string `json:"parent_job_id,omitempty"` ++ DependsOn []string `json:"depends_on,omitempty"` ++ Attempts int `json:"attempts,omitempty"` ++ MaxAttempts int `json:"max_attempts,omitempty"` ++ BackoffSeconds int `json:"backoff_seconds,omitempty"` ++ TimeoutSeconds int `json:"timeout_seconds,omitempty"` ++ RequiresMasterApply bool `json:"requires_master_apply,omitempty"` ++ ApplyAttempts int `json:"apply_attempts,omitempty"` ++ MaxApplyAttempts int `json:"max_apply_attempts,omitempty"` ++ ApplyBackoffSeconds int `json:"apply_backoff_seconds,omitempty"` ++ ApplyNextAttemptAt time.Time `json:"apply_next_attempt_at,omitempty"` ++ ApplyError string `json:"apply_error,omitempty"` ++ ClaimedBy string `json:"claimed_by,omitempty"` ++ LeaseToken string `json:"lease_token,omitempty"` ++ LeaseUntil time.Time `json:"lease_until,omitempty"` ++ NextAttemptAt time.Time `json:"next_attempt_at,omitempty"` ++ StartedAt time.Time `json:"started_at,omitempty"` ++ FinishedAt time.Time `json:"finished_at,omitempty"` ++ Error string `json:"error,omitempty"` ++ CreatedAt time.Time `json:"created_at"` ++ UpdatedAt time.Time `json:"updated_at"` ++} ++ ++type WorkerState struct { ++ ID string `json:"id"` ++ ResourceClass string `json:"resource_class"` ++ Capabilities []string `json:"capabilities"` ++ Labels map[string]string `json:"labels,omitempty"` ++ MaxConcurrency int `json:"max_concurrency"` ++ Inflight int `json:"inflight"` ++ Version string `json:"version,omitempty"` ++ Hostname string `json:"hostname,omitempty"` ++ LastHeartbeat time.Time `json:"last_heartbeat"` ++ RegisteredAt time.Time `json:"registered_at"` ++ Status string `json:"status"` + } + + type MaintenanceStatus struct { +@@ -688,6 +750,10 @@ func DefaultConfig() Config { + c.Brain.DecayPerDay = 0.01 + c.Brain.MaxSynapseWeight = 4.0 + c.Brain.GraphBonus = 0.15 ++ c.Brain.GraphMaxHops = 3 ++ c.Brain.GraphHopDecay = 0.60 ++ c.Brain.GraphMaxExpansion = 64 ++ c.Brain.GraphMinEdgeWeight = 0.05 + c.Brain.MaxContextMemories = 8 + c.Brain.AutoLearn = true + c.Brain.ExternalRelinkWorker = true +@@ -850,5 +916,25 @@ func DefaultConfig() Config { + c.Cluster.LogSegmentBytes = 64 << 20 + c.API.RequireKey = true + c.Worker.LeaseSeconds = 120 ++ c.Worker.HeartbeatSeconds = 15 ++ c.Worker.StaleAfterSeconds = 60 ++ c.Worker.DefaultMaxAttempts = 3 ++ c.Worker.RetryBackoffSeconds = 15 ++ c.Worker.MaxQueuedJobs = 5000 ++ c.Worker.MasterApplyMaxAttempts = 5 ++ c.Worker.MasterApplyBackoffSeconds = 5 ++ c.Worker.JobRetentionHours = 168 ++ c.Worker.MaxTerminalJobs = 20000 ++ c.Worker.GraphBackfillEnabled = true ++ c.Worker.GraphBackfillIntervalS = 10 ++ c.Worker.GraphBackfillBatchSize = 64 ++ c.Worker.GraphBackfillMaxQueued = 512 ++ c.Worker.GraphBackfillMinDegree = 3 ++ c.Worker.GraphCandidateMultiplier = 6 ++ c.Worker.GraphRetryAfterMinutes = 360 ++ c.Worker.RequireWorkerForGraph = true ++ c.Worker.OffloadChat = false ++ c.Worker.OffloadEmbeddings = false ++ c.Worker.DistributedInferenceWaitS = 180 + return c + } +diff --git a/platform/neuroforge/internal/httpapi/httpapi.go b/platform/neuroforge/internal/httpapi/httpapi.go +index 5bfa388..109e8ea 100644 +--- a/platform/neuroforge/internal/httpapi/httpapi.go ++++ b/platform/neuroforge/internal/httpapi/httpapi.go +@@ -92,6 +92,8 @@ func (s *Server) routes() { + s.mux.Handle("POST /api/v1/integrations/outcomes/search", s.integrationAuth(http.HandlerFunc(s.integrationValidatedOutcomeSearch))) + s.mux.Handle("GET /api/v1/integrations/graph/research", s.controlReadAuth(http.HandlerFunc(s.integrationResearchGraph))) + s.mux.Handle("GET /api/v1/integrations/graph/brain", s.controlReadAuth(http.HandlerFunc(s.integrationBrainGraph))) ++ s.mux.Handle("GET /api/v1/integrations/graph/status", s.controlReadAuth(http.HandlerFunc(s.integrationGraphStatus))) ++ s.mux.Handle("GET /api/v1/integrations/orchestrator/status", s.controlReadAuth(http.HandlerFunc(s.integrationOrchestratorStatus))) + + s.mux.Handle("POST /internal/v1/cluster/request-vote", s.clusterAuth(http.HandlerFunc(s.clusterRequestVote))) + s.mux.Handle("POST /internal/v1/cluster/heartbeat", s.clusterAuth(http.HandlerFunc(s.clusterHeartbeat))) +@@ -102,6 +104,8 @@ func (s *Server) routes() { + s.mux.Handle("GET /internal/v1/cluster/decision/{id}", s.clusterAuth(http.HandlerFunc(s.clusterDecision))) + s.mux.Handle("GET /internal/v1/cluster/status", s.clusterAuth(http.HandlerFunc(s.clusterStatus))) + ++ s.mux.Handle("POST /api/v1/worker/register", s.workerAuth(http.HandlerFunc(s.workerRegister))) ++ s.mux.Handle("POST /api/v1/worker/heartbeat", s.workerAuth(http.HandlerFunc(s.workerHeartbeat))) + s.mux.Handle("POST /api/v1/worker/claim", s.workerAuth(http.HandlerFunc(s.workerClaim))) + s.mux.Handle("POST /api/v1/worker/complete", s.workerAuth(http.HandlerFunc(s.workerComplete))) + +@@ -138,6 +142,12 @@ func (s *Server) routes() { + s.mux.Handle("GET /admin/api/knowledge/memories", s.adminAuth(http.HandlerFunc(s.adminKnowledgeMemories))) + s.mux.Handle("GET /admin/api/knowledge/memory/{id}", s.adminAuth(http.HandlerFunc(s.adminKnowledgeMemory))) + s.mux.Handle("GET /admin/api/knowledge/graph", s.adminAuth(http.HandlerFunc(s.adminKnowledgeGraph))) ++ s.mux.Handle("GET /admin/api/graph/status", s.adminAuth(http.HandlerFunc(s.adminGraphStatus))) ++ s.mux.Handle("POST /admin/api/graph/backfill", s.adminAuth(http.HandlerFunc(s.adminGraphBackfill))) ++ s.mux.Handle("GET /admin/api/orchestrator/status", s.adminAuth(http.HandlerFunc(s.adminOrchestratorStatus))) ++ s.mux.Handle("GET /admin/api/orchestrator/jobs", s.adminAuth(http.HandlerFunc(s.adminOrchestratorJobs))) ++ s.mux.Handle("POST /admin/api/orchestrator/jobs/{id}/retry", s.adminAuth(http.HandlerFunc(s.adminOrchestratorRetry))) ++ s.mux.Handle("POST /admin/api/orchestrator/jobs/{id}/cancel", s.adminAuth(http.HandlerFunc(s.adminOrchestratorCancel))) + s.mux.Handle("GET /admin/api/knowledge/events", s.adminAuth(http.HandlerFunc(s.adminKnowledgeEvents))) + s.mux.Handle("POST /admin/api/knowledge/search", s.adminAuth(http.HandlerFunc(s.adminKnowledgeSearch))) + s.mux.Handle("GET /admin/api/learning-policy", s.adminAuth(http.HandlerFunc(s.adminGetLearningPolicy))) +@@ -418,10 +428,84 @@ func (s *Server) stats(w http.ResponseWriter, r *http.Request) { + s.json(w, 200, map[string]any{"stats": s.store.Stats(), "cost": s.cost.Totals()}) + } + +-func (s *Server) workerClaim(w http.ResponseWriter, r *http.Request) { +- var q struct { +- WorkerID string `json:"worker_id"` ++func (s *Server) integrationGraphStatus(w http.ResponseWriter, r *http.Request) { ++ cfg := s.store.Config() ++ s.json(w, http.StatusOK, map[string]any{ ++ "stats": s.store.GraphStats(), ++ "config": map[string]any{ ++ "max_hops": cfg.Brain.GraphMaxHops, "hop_decay": cfg.Brain.GraphHopDecay, ++ "max_expansion": cfg.Brain.GraphMaxExpansion, "min_edge_weight": cfg.Brain.GraphMinEdgeWeight, ++ "backfill_enabled": cfg.Worker.GraphBackfillEnabled, "backfill_min_degree": cfg.Worker.GraphBackfillMinDegree, ++ }, ++ }) ++} ++ ++func (s *Server) integrationOrchestratorStatus(w http.ResponseWriter, r *http.Request) { ++ cfg := s.store.Config() ++ s.json(w, http.StatusOK, map[string]any{ ++ "leader": s.store.IsOrchestratorLeader(), ++ "status": s.store.OrchestratorStatus(), ++ "config": map[string]any{ ++ "lease_seconds": cfg.Worker.LeaseSeconds, "heartbeat_seconds": cfg.Worker.HeartbeatSeconds, ++ "stale_after_seconds": cfg.Worker.StaleAfterSeconds, "max_queued_jobs": cfg.Worker.MaxQueuedJobs, ++ "offload_chat": cfg.Worker.OffloadChat, "offload_embeddings": cfg.Worker.OffloadEmbeddings, ++ }, ++ }) ++} ++ ++type workerRequest struct { ++ WorkerID string `json:"worker_id"` ++ ResourceClass string `json:"resource_class,omitempty"` ++ Capabilities []string `json:"capabilities,omitempty"` ++ Labels map[string]string `json:"labels,omitempty"` ++ MaxConcurrency int `json:"max_concurrency,omitempty"` ++ Version string `json:"version,omitempty"` ++ Hostname string `json:"hostname,omitempty"` ++ ActiveLeases map[string]string `json:"active_leases,omitempty"` ++} ++ ++func workerHeartbeatFromRequest(q workerRequest) store.WorkerHeartbeat { ++ return store.WorkerHeartbeat{ ++ ID: q.WorkerID, ResourceClass: q.ResourceClass, Capabilities: q.Capabilities, ++ Labels: q.Labels, MaxConcurrency: q.MaxConcurrency, Version: q.Version, ++ Hostname: q.Hostname, ActiveLeases: q.ActiveLeases, + } ++} ++ ++func (s *Server) workerRegister(w http.ResponseWriter, r *http.Request) { ++ var q workerRequest ++ if err := decode(r, &q); err != nil { ++ s.err(w, http.StatusBadRequest, err) ++ return ++ } ++ state, err := s.store.RegisterWorker(workerHeartbeatFromRequest(q)) ++ if err != nil { ++ s.err(w, http.StatusBadRequest, err) ++ return ++ } ++ s.json(w, http.StatusOK, state) ++} ++ ++func (s *Server) workerHeartbeat(w http.ResponseWriter, r *http.Request) { ++ var q workerRequest ++ if err := decode(r, &q); err != nil { ++ s.err(w, http.StatusBadRequest, err) ++ return ++ } ++ lease := s.store.Config().Worker.LeaseSeconds ++ if lease < 10 { ++ lease = 120 ++ } ++ state, err := s.store.HeartbeatWorker(workerHeartbeatFromRequest(q), time.Duration(lease)*time.Second) ++ if err != nil { ++ s.err(w, http.StatusBadRequest, err) ++ return ++ } ++ s.json(w, http.StatusOK, state) ++} ++ ++func (s *Server) workerClaim(w http.ResponseWriter, r *http.Request) { ++ var q workerRequest + if err := decode(r, &q); err != nil { + s.err(w, 400, err) + return +@@ -434,7 +518,7 @@ func (s *Server) workerClaim(w http.ResponseWriter, r *http.Request) { + if lease < 10 { + lease = 120 + } +- j, err := s.store.ClaimJob(q.WorkerID, time.Duration(lease)*time.Second) ++ j, err := s.store.ClaimJobForWorker(workerHeartbeatFromRequest(q), time.Duration(lease)*time.Second) + if err != nil { + s.err(w, 500, err) + return +@@ -445,27 +529,33 @@ func (s *Server) workerClaim(w http.ResponseWriter, r *http.Request) { + } + s.json(w, 200, j) + } ++ + func (s *Server) workerComplete(w http.ResponseWriter, r *http.Request) { + var q struct { +- WorkerID string `json:"worker_id"` +- JobID string `json:"job_id"` +- Result json.RawMessage `json:"result"` +- Error string `json:"error"` ++ WorkerID string `json:"worker_id"` ++ JobID string `json:"job_id"` ++ LeaseToken string `json:"lease_token,omitempty"` ++ Result json.RawMessage `json:"result"` ++ Error string `json:"error"` + } + if err := decode(r, &q); err != nil { + s.err(w, 400, err) + return + } +- j, err := s.store.CompleteJob(q.JobID, q.WorkerID, q.Result, q.Error) ++ j, err := s.store.CompleteJobLease(q.JobID, q.WorkerID, q.LeaseToken, q.Result, q.Error) + if err != nil { +- s.err(w, 400, err) ++ s.err(w, 409, err) + return + } +- if err := s.brain.ApplyJobResult(j); err != nil { +- s.err(w, 500, err) +- return ++ if j.Status == "apply_wait" && s.store.IsOrchestratorLeader() { ++ // Best-effort eager second phase. Any failure remains durable as apply_wait ++ // and is retried by the orchestrator; the worker must not recompute it. ++ _ = s.brain.ApplyAndFinalizeJob(j) ++ if cur, ok := s.store.Job(j.ID); ok { ++ j = cur ++ } + } +- s.json(w, 200, map[string]bool{"ok": true}) ++ s.json(w, 200, map[string]any{"ok": true, "status": j.Status, "attempts": j.Attempts, "apply_attempts": j.ApplyAttempts, "apply_error": j.ApplyError, "next_attempt_at": j.NextAttemptAt, "apply_next_attempt_at": j.ApplyNextAttemptAt}) + } + + func (s *Server) adminStatus(w http.ResponseWriter, r *http.Request) { +diff --git a/platform/neuroforge/internal/httpapi/knowledge.go b/platform/neuroforge/internal/httpapi/knowledge.go +index b7073ac..beac310 100644 +--- a/platform/neuroforge/internal/httpapi/knowledge.go ++++ b/platform/neuroforge/internal/httpapi/knowledge.go +@@ -115,3 +115,69 @@ func (s *Server) adminPutLearningPolicy(w http.ResponseWriter, r *http.Request) + }) + s.json(w, http.StatusOK, q) + } ++ ++func (s *Server) adminGraphStatus(w http.ResponseWriter, r *http.Request) { ++ s.json(w, http.StatusOK, map[string]any{ ++ "graph": s.store.GraphStats(), ++ "orchestrator": s.store.OrchestratorStatus(), ++ "config": map[string]any{ ++ "max_hops": s.store.Config().Brain.GraphMaxHops, ++ "hop_decay": s.store.Config().Brain.GraphHopDecay, ++ "max_expansion": s.store.Config().Brain.GraphMaxExpansion, ++ "min_edge_weight": s.store.Config().Brain.GraphMinEdgeWeight, ++ "backfill_enabled": s.store.Config().Worker.GraphBackfillEnabled, ++ "backfill_min_degree": s.store.Config().Worker.GraphBackfillMinDegree, ++ "backfill_max_queued": s.store.Config().Worker.GraphBackfillMaxQueued, ++ }, ++ }) ++} ++ ++func (s *Server) adminGraphBackfill(w http.ResponseWriter, r *http.Request) { ++ planned, err := s.brain.PlanGraphBackfill() ++ if err != nil { ++ s.err(w, http.StatusInternalServerError, err) ++ return ++ } ++ s.json(w, http.StatusAccepted, map[string]any{"ok": true, "planned": planned, "graph": s.store.GraphStats()}) ++} ++ ++func (s *Server) adminOrchestratorStatus(w http.ResponseWriter, r *http.Request) { ++ s.json(w, http.StatusOK, s.store.OrchestratorStatus()) ++} ++ ++func (s *Server) adminOrchestratorJobs(w http.ResponseWriter, r *http.Request) { ++ limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) ++ s.json(w, http.StatusOK, s.store.JobsSnapshot(limit, r.URL.Query().Get("status"), r.URL.Query().Get("type"))) ++} ++ ++func (s *Server) adminOrchestratorRetry(w http.ResponseWriter, r *http.Request) { ++ job, err := s.store.RetryJob(r.PathValue("id")) ++ if err != nil { ++ s.err(w, http.StatusConflict, err) ++ return ++ } ++ s.json(w, http.StatusOK, job) ++} ++ ++func (s *Server) adminOrchestratorCancel(w http.ResponseWriter, r *http.Request) { ++ var q struct { ++ Reason string `json:"reason"` ++ } ++ // Empty bodies are valid for an operator cancellation; malformed non-empty ++ // JSON is not. ++ if r.ContentLength != 0 { ++ if err := decode(r, &q); err != nil { ++ s.err(w, http.StatusBadRequest, err) ++ return ++ } ++ } ++ if strings.TrimSpace(q.Reason) == "" { ++ q.Reason = "canceled by administrator" ++ } ++ if err := s.store.CancelJob(r.PathValue("id"), q.Reason); err != nil { ++ s.err(w, http.StatusNotFound, err) ++ return ++ } ++ job, _ := s.store.Job(r.PathValue("id")) ++ s.json(w, http.StatusOK, job) ++} +diff --git a/platform/neuroforge/internal/httpapi/metrics.go b/platform/neuroforge/internal/httpapi/metrics.go +index e13ba8f..d49bf82 100644 +--- a/platform/neuroforge/internal/httpapi/metrics.go ++++ b/platform/neuroforge/internal/httpapi/metrics.go +@@ -242,6 +242,8 @@ func (s *Server) metricsEndpoint(w http.ResponseWriter, r *http.Request) { + } + + st := s.store.ObservabilitySnapshot() ++ orch := s.store.OrchestratorStatus() ++ graph := s.store.GraphStats() + costs := s.cost.Totals() + cfg := s.store.Config() + rt := currentRuntimeSnapshot() +@@ -269,11 +271,43 @@ func (s *Server) metricsEndpoint(w http.ResponseWriter, r *http.Request) { + promHeader(&b, "neuroforge_knowledge_events", "Current number of retained explainability/knowledge events.", "gauge") + promSample(&b, "neuroforge_knowledge_events", st.KnowledgeEvents) + +- promHeader(&b, "neuroforge_jobs", "Current worker jobs by bounded status class.", "gauge") +- promSample(&b, "neuroforge_jobs", st.JobsQueued, "status", "queued") +- promSample(&b, "neuroforge_jobs", st.JobsClaimed, "status", "claimed") +- promSample(&b, "neuroforge_jobs", st.JobsDone, "status", "done") +- promSample(&b, "neuroforge_jobs", st.JobsFailed, "status", "failed") ++ promHeader(&b, "neuroforge_jobs", "Current durable orchestrator jobs by status.", "gauge") ++ statuses := []string{"queued", "claimed", "retry_wait", "blocked", "apply_wait", "done", "failed", "canceled"} ++ for _, status := range statuses { ++ promSample(&b, "neuroforge_jobs", orch.JobsByStatus[status], "status", status) ++ } ++ promHeader(&b, "neuroforge_jobs_by_resource", "Current durable orchestrator jobs by resource class.", "gauge") ++ for resource, n := range orch.JobsByResource { ++ promSample(&b, "neuroforge_jobs_by_resource", n, "resource", resource) ++ } ++ promHeader(&b, "neuroforge_workers", "Registered subagents by resource class and liveness state.", "gauge") ++ workerCounts := map[string]int{} ++ for _, worker := range orch.Workers { ++ workerCounts[worker.ResourceClass+"\x00"+worker.Status]++ ++ } ++ for key, n := range workerCounts { ++ parts := strings.SplitN(key, "\x00", 2) ++ promSample(&b, "neuroforge_workers", n, "resource", parts[0], "status", parts[1]) ++ } ++ promHeader(&b, "neuroforge_worker_inflight", "Current claimed jobs per registered subagent.", "gauge") ++ promHeader(&b, "neuroforge_worker_capacity", "Configured concurrency capacity per registered subagent.", "gauge") ++ for _, worker := range orch.Workers { ++ promSample(&b, "neuroforge_worker_inflight", worker.Inflight, "worker", worker.ID, "resource", worker.ResourceClass) ++ promSample(&b, "neuroforge_worker_capacity", worker.MaxConcurrency, "worker", worker.ID, "resource", worker.ResourceClass) ++ } ++ ++ promHeader(&b, "neuroforge_graph_memories", "Knowledge memories by graph linkage class.", "gauge") ++ promSample(&b, "neuroforge_graph_memories", graph.LinkedMemories, "state", "linked") ++ promSample(&b, "neuroforge_graph_memories", graph.IsolatedMemories, "state", "isolated") ++ promSample(&b, "neuroforge_graph_memories", graph.MultiLinked, "state", "multi_linked") ++ promHeader(&b, "neuroforge_graph_max_degree", "Maximum associative graph degree.", "gauge") ++ promSample(&b, "neuroforge_graph_max_degree", graph.MaxDegree) ++ promHeader(&b, "neuroforge_graph_average_degree", "Average associative graph degree.", "gauge") ++ promSample(&b, "neuroforge_graph_average_degree", strconv.FormatFloat(graph.AverageDegree, 'f', 6, 64)) ++ promHeader(&b, "neuroforge_graph_connected_components", "Number of connected components in the associative graph.", "gauge") ++ promSample(&b, "neuroforge_graph_connected_components", graph.ConnectedComponents) ++ promHeader(&b, "neuroforge_graph_largest_component", "Memories in the largest connected component.", "gauge") ++ promSample(&b, "neuroforge_graph_largest_component", graph.LargestComponent) + + promHeader(&b, "neuroforge_hnsw_nodes", "Current number of vectors in hot HNSW indexes.", "gauge") + promSample(&b, "neuroforge_hnsw_nodes", st.HNSWNodes) +diff --git a/platform/neuroforge/internal/store/batch.go b/platform/neuroforge/internal/store/batch.go +index 5b2ad86..d5492b2 100644 +--- a/platform/neuroforge/internal/store/batch.go ++++ b/platform/neuroforge/internal/store/batch.go +@@ -129,6 +129,7 @@ func (s *Store) DeleteMemoriesBatch(ids []string) error { + } + for key, syn := range s.state.Synapses { + if syn.A == id || syn.B == id { ++ s.unindexSynapseLocked(syn) + delete(s.state.Synapses, key) + } + } +diff --git a/mnt/data/v160_work/platform/neuroforge/internal/store/graph_index.go b/platform/neuroforge/internal/store/graph_index.go +new file mode 100644 +index 0000000..56dffa5 +--- /dev/null ++++ b/platform/neuroforge/internal/store/graph_index.go +@@ -0,0 +1,405 @@ ++package store ++ ++import ( ++ "sort" ++ "strings" ++ "time" ++ ++ "neuroforge/internal/core" ++ "neuroforge/internal/vector" ++) ++ ++// GraphStats exposes structural graph health, not just raw edge counts. A real ++// associative graph is expected to have nodes with degree >1 and multi-hop ++// reachability; these metrics make that observable. ++type GraphStats struct { ++ Memories int `json:"memories"` ++ Synapses int `json:"synapses"` ++ LinkedMemories int `json:"linked_memories"` ++ IsolatedMemories int `json:"isolated_memories"` ++ MultiLinked int `json:"multi_linked_memories"` ++ MaxDegree int `json:"max_degree"` ++ AverageDegree float64 `json:"average_degree"` ++ ConnectedComponents int `json:"connected_components"` ++ LargestComponent int `json:"largest_component"` ++} ++ ++func (s *Store) rebuildSynapseAdjLocked() { ++ s.synapseAdj = make(map[string]map[string]*core.Synapse) ++ for _, syn := range s.state.Synapses { ++ if syn == nil || syn.A == "" || syn.B == "" || syn.A == syn.B { ++ continue ++ } ++ s.indexSynapseLocked(syn) ++ } ++} ++ ++func (s *Store) indexSynapseLocked(syn *core.Synapse) { ++ if syn == nil || syn.A == "" || syn.B == "" || syn.A == syn.B { ++ return ++ } ++ if s.synapseAdj == nil { ++ s.synapseAdj = make(map[string]map[string]*core.Synapse) ++ } ++ if s.synapseAdj[syn.A] == nil { ++ s.synapseAdj[syn.A] = make(map[string]*core.Synapse) ++ } ++ if s.synapseAdj[syn.B] == nil { ++ s.synapseAdj[syn.B] = make(map[string]*core.Synapse) ++ } ++ s.synapseAdj[syn.A][syn.B] = syn ++ s.synapseAdj[syn.B][syn.A] = syn ++} ++ ++func (s *Store) unindexSynapseLocked(syn *core.Synapse) { ++ if syn == nil || s.synapseAdj == nil { ++ return ++ } ++ if m := s.synapseAdj[syn.A]; m != nil { ++ delete(m, syn.B) ++ if len(m) == 0 { ++ delete(s.synapseAdj, syn.A) ++ } ++ } ++ if m := s.synapseAdj[syn.B]; m != nil { ++ delete(m, syn.A) ++ if len(m) == 0 { ++ delete(s.synapseAdj, syn.B) ++ } ++ } ++} ++ ++func appendUniqueRelation(xs []string, value string) []string { ++ for _, x := range xs { ++ if x == value { ++ return xs ++ } ++ } ++ return append(xs, value) ++} ++ ++// ReinforceRelation adds or reinforces an associative edge while preserving the ++// reason(s) the edge exists. Multiple relations can coexist on the same pair. ++func (s *Store) ReinforceRelation(a, b, relation string, similarity, delta, decayPerDay, maxWeight float64) error { ++ if a == "" || b == "" || a == b { ++ return nil ++ } ++ s.mu.Lock() ++ defer s.mu.Unlock() ++ if s.state.Memories[a] == nil || s.state.Memories[b] == nil { ++ return nil ++ } ++ key := edgeKey(a, b) ++ now := time.Now().UTC() ++ syn, ok := s.state.Synapses[key] ++ if !ok { ++ syn = &core.Synapse{A: a, B: b, Similarity: similarity, LastUpdated: now} ++ s.state.Synapses[key] = syn ++ s.indexSynapseLocked(syn) ++ } ++ if relation == "" { ++ relation = "association" ++ } ++ syn.Relations = appendUniqueRelation(syn.Relations, relation) ++ days := now.Sub(syn.LastUpdated).Hours() / 24 ++ if days > 0 && decayPerDay > 0 { ++ syn.Weight *= pow(1-decayPerDay, days) ++ } ++ syn.Weight = vector.Clamp(syn.Weight+delta, -maxWeight, maxWeight) ++ if similarity > syn.Similarity { ++ syn.Similarity = similarity ++ } ++ syn.Activations++ ++ syn.LastUpdated = now ++ return s.commitLocked("synapse.upsert", *syn) ++} ++ ++func (s *Store) GraphDegree(id string) int { ++ s.mu.RLock() ++ defer s.mu.RUnlock() ++ return len(s.synapseAdj[id]) ++} ++ ++func (s *Store) GraphStats() GraphStats { ++ s.mu.RLock() ++ defer s.mu.RUnlock() ++ out := GraphStats{Memories: len(s.state.Memories), Synapses: len(s.state.Synapses)} ++ if out.Memories == 0 { ++ return out ++ } ++ for id := range s.state.Memories { ++ degree := len(s.synapseAdj[id]) ++ if degree == 0 { ++ out.IsolatedMemories++ ++ } else { ++ out.LinkedMemories++ ++ } ++ if degree > 1 { ++ out.MultiLinked++ ++ } ++ if degree > out.MaxDegree { ++ out.MaxDegree = degree ++ } ++ } ++ out.AverageDegree = float64(out.Synapses*2) / float64(out.Memories) ++ visited := make(map[string]bool, out.Memories) ++ for id := range s.state.Memories { ++ if visited[id] { ++ continue ++ } ++ out.ConnectedComponents++ ++ queue := []string{id} ++ visited[id] = true ++ size := 0 ++ for len(queue) > 0 { ++ cur := queue[0] ++ queue = queue[1:] ++ size++ ++ for next := range s.synapseAdj[cur] { ++ if !visited[next] && s.state.Memories[next] != nil { ++ visited[next] = true ++ queue = append(queue, next) ++ } ++ } ++ } ++ if size > out.LargestComponent { ++ out.LargestComponent = size ++ } ++ } ++ return out ++} ++ ++// graphExpandLocked applies bounded multi-hop associative propagation to a ++// vector result set. The graph remains pairwise at the edge level (as graphs ++// naturally are) but retrieval can now follow chains across several memories. ++func (s *Store) graphExpandLocked(q []float32, hits []SearchHit, k int, min, graphBonus float64) []SearchHit { ++ cfg := s.state.Config.Brain ++ if graphBonus <= 0 || len(hits) == 0 || len(s.state.Synapses) == 0 { ++ return hits ++ } ++ maxHops := cfg.GraphMaxHops ++ if maxHops <= 0 { ++ maxHops = 1 ++ } ++ if maxHops > 6 { ++ maxHops = 6 ++ } ++ decay := cfg.GraphHopDecay ++ if decay <= 0 || decay > 1 { ++ decay = 0.60 ++ } ++ maxExpansion := cfg.GraphMaxExpansion ++ if maxExpansion <= 0 { ++ maxExpansion = 64 ++ } ++ minEdge := cfg.GraphMinEdgeWeight ++ ++ type frontierItem struct { ++ id string ++ boost float64 ++ hop int ++ } ++ bestBoost := map[string]float64{} ++ queue := make([]frontierItem, 0, len(hits)*2) ++ for _, h := range hits { ++ bestBoost[h.Memory.ID] = 0 ++ queue = append(queue, frontierItem{id: h.Memory.ID, boost: graphBonus, hop: 0}) ++ } ++ expanded := 0 ++ for len(queue) > 0 && expanded < maxExpansion { ++ cur := queue[0] ++ queue = queue[1:] ++ if cur.hop >= maxHops { ++ continue ++ } ++ neighbors := s.synapseAdj[cur.id] ++ if len(neighbors) == 0 { ++ continue ++ } ++ type edgeItem struct { ++ id string ++ syn *core.Synapse ++ } ++ edges := make([]edgeItem, 0, len(neighbors)) ++ for id, syn := range neighbors { ++ if syn != nil && syn.Weight >= minEdge { ++ edges = append(edges, edgeItem{id: id, syn: syn}) ++ } ++ } ++ sort.Slice(edges, func(i, j int) bool { return edges[i].syn.Weight > edges[j].syn.Weight }) ++ for _, edge := range edges { ++ if expanded >= maxExpansion { ++ break ++ } ++ meta := s.state.Memories[edge.id] ++ if meta == nil || !memorySearchable(meta) { ++ continue ++ } ++ boost := cur.boost * edge.syn.Weight ++ if cur.hop > 0 { ++ boost *= decay ++ } ++ if old, ok := bestBoost[edge.id]; ok && old >= boost { ++ continue ++ } ++ bestBoost[edge.id] = boost ++ queue = append(queue, frontierItem{id: edge.id, boost: boost, hop: cur.hop + 1}) ++ expanded++ ++ } ++ } ++ ++ byID := make(map[string]int, len(hits)) ++ for i := range hits { ++ byID[hits[i].Memory.ID] = i ++ } ++ for id, boost := range bestBoost { ++ if boost <= 0 { ++ continue ++ } ++ if i, ok := byID[id]; ok { ++ hits[i].GraphBoost += boost ++ hits[i].Score += boost ++ continue ++ } ++ m, ok := s.fullMemoryForReadLocked(id) ++ if !ok || len(m.Vector) != len(q) { ++ continue ++ } ++ sim := vector.Cosine(q, m.Vector) ++ if sim < min { ++ continue ++ } ++ baseScore := sim * 0.5 ++ hits = append(hits, SearchHit{Memory: cloneMemory(m), Similarity: sim, BaseScore: baseScore, GraphBoost: boost, Score: baseScore + boost, TypeWeight: 1, SalienceFactor: 1, ConfidenceFactor: 1, CandidateSource: "synapse-multihop"}) ++ } ++ sort.Slice(hits, func(i, j int) bool { return hits[i].Score > hits[j].Score }) ++ if len(hits) > k { ++ hits = hits[:k] ++ } ++ return hits ++} ++ ++// GraphBackfillCandidates returns a bounded set of memories whose associative ++// links are missing, stale for the current memory version, or remain below the ++// configured minimum degree after the retry cooldown. ++func (s *Store) GraphBackfillCandidates(limit, minDegree int, retryAfter time.Duration) []core.Memory { ++ if limit <= 0 { ++ return nil ++ } ++ if minDegree < 1 { ++ minDegree = 1 ++ } ++ now := time.Now().UTC() ++ s.mu.RLock() ++ defer s.mu.RUnlock() ++ out := make([]core.Memory, 0, limit) ++ // Do not repeatedly return a target that already has a durable relink job ++ // in flight. Without this guard, the lexicographically first memories can ++ // occupy every planning batch until their first job finishes and starve the ++ // rest of a large imported corpus. ++ pendingTargets := make(map[string]bool) ++ for _, j := range s.state.Jobs { ++ if j == nil || j.Type != "vector.relink" { ++ continue ++ } ++ switch j.Status { ++ case "queued", "claimed", "retry_wait", "blocked", "apply_wait": ++ default: ++ continue ++ } ++ const prefix = "vector.relink:" ++ if !strings.HasPrefix(j.IdempotencyKey, prefix) { ++ continue ++ } ++ rest := strings.TrimPrefix(j.IdempotencyKey, prefix) ++ if cut := strings.LastIndex(rest, ":v"); cut > 0 { ++ pendingTargets[rest[:cut]] = true ++ } ++ } ++ ids := make([]string, 0, len(s.state.Memories)) ++ for id := range s.state.Memories { ++ ids = append(ids, id) ++ } ++ sort.Strings(ids) ++ for _, id := range ids { ++ meta := s.state.Memories[id] ++ if pendingTargets[id] || meta == nil || !memorySearchable(meta) { ++ continue ++ } ++ m, ok := s.fullMemoryForReadLocked(id) ++ if !ok || len(m.Vector) == 0 { ++ continue ++ } ++ degree := len(s.synapseAdj[id]) ++ needs := m.GraphVersion < m.Version || m.GraphLinkedAt.IsZero() ++ if !needs && degree < minDegree && (retryAfter <= 0 || now.Sub(m.GraphLinkedAt) >= retryAfter) { ++ needs = true ++ } ++ if !needs { ++ continue ++ } ++ m.GraphDegree = degree ++ out = append(out, cloneMemory(m)) ++ if len(out) >= limit { ++ break ++ } ++ } ++ return out ++} ++ ++func (s *Store) MarkGraphLinked(id string) error { ++ s.mu.Lock() ++ defer s.mu.Unlock() ++ m, ok := s.materializeMemoryLocked(id) ++ if !ok || m == nil { ++ return nil ++ } ++ m.GraphLinkedAt = time.Now().UTC() ++ m.GraphDegree = len(s.synapseAdj[id]) ++ m.GraphVersion = m.Version ++ return s.commitLocked("memory.upsert", []core.Memory{cloneMemory(*m)}) ++} ++ ++// UpsertRelationEvidence records deterministic relation evidence without ++// additive reinforcement. It is used for retryable master-apply phases where ++// the same worker result may be replayed after a crash; replaying it must not ++// inflate graph weights or activation counters. ++func (s *Store) UpsertRelationEvidence(a, b, relation string, similarity, weight, maxWeight float64) error { ++ if a == "" || b == "" || a == b { ++ return nil ++ } ++ s.mu.Lock() ++ defer s.mu.Unlock() ++ if s.state.Memories[a] == nil || s.state.Memories[b] == nil { ++ return nil ++ } ++ if relation == "" { ++ relation = "association" ++ } ++ key := edgeKey(a, b) ++ now := time.Now().UTC() ++ syn, ok := s.state.Synapses[key] ++ if !ok { ++ syn = &core.Synapse{A: a, B: b, LastUpdated: now} ++ s.state.Synapses[key] = syn ++ s.indexSynapseLocked(syn) ++ } ++ beforeRelation := len(syn.Relations) ++ syn.Relations = appendUniqueRelation(syn.Relations, relation) ++ if similarity > syn.Similarity { ++ syn.Similarity = similarity ++ } ++ if maxWeight <= 0 { ++ maxWeight = 1 ++ } ++ weight = vector.Clamp(weight, -maxWeight, maxWeight) ++ if weight > syn.Weight { ++ syn.Weight = weight ++ } ++ // Count creation of relation evidence, not crash/retry replays. ++ if len(syn.Relations) > beforeRelation || syn.Activations == 0 { ++ syn.Activations++ ++ } ++ syn.LastUpdated = now ++ return s.commitLocked("synapse.upsert", *syn) ++} +diff --git a/mnt/data/v160_work/platform/neuroforge/internal/store/graph_multihop_test.go b/platform/neuroforge/internal/store/graph_multihop_test.go +new file mode 100644 +index 0000000..a8f0d72 +--- /dev/null ++++ b/platform/neuroforge/internal/store/graph_multihop_test.go +@@ -0,0 +1,103 @@ ++package store ++ ++import ( ++ "testing" ++ "time" ++ ++ "neuroforge/internal/core" ++) ++ ++func TestGraphIsManyToManyAndTraversesMultipleHops(t *testing.T) { ++ s, err := New(t.TempDir()) ++ if err != nil { ++ t.Fatal(err) ++ } ++ defer s.Close() ++ cfg := s.Config() ++ cfg.Brain.GraphMaxHops = 3 ++ cfg.Brain.GraphHopDecay = .8 ++ cfg.Brain.GraphMaxExpansion = 32 ++ cfg.Brain.GraphMinEdgeWeight = .01 ++ if err := s.UpdateConfig(cfg); err != nil { ++ t.Fatal(err) ++ } ++ ++ items := []core.Memory{ ++ {ID: "A", Kind: "knowledge", MemoryType: core.MemorySemantic, Text: "A", Vector: []float32{1, .01}}, ++ {ID: "B", Kind: "knowledge", MemoryType: core.MemorySemantic, Text: "B", Vector: []float32{.99, .1}}, ++ {ID: "C", Kind: "knowledge", MemoryType: core.MemorySemantic, Text: "C", Vector: []float32{.9, .3}}, ++ {ID: "D", Kind: "knowledge", MemoryType: core.MemorySemantic, Text: "D", Vector: []float32{.8, .4}}, ++ } ++ if err := s.AddMemoriesBatch(items); err != nil { ++ t.Fatal(err) ++ } ++ for _, e := range [][2]string{{"A", "B"}, {"B", "C"}, {"C", "D"}, {"B", "D"}} { ++ if err := s.ReinforceRelation(e[0], e[1], "semantic_similarity", .9, .8, 0, 1); err != nil { ++ t.Fatal(err) ++ } ++ } ++ // A second relation on the same pair proves relation metadata is not limited ++ // to a single 1:1 reason. ++ if err := s.ReinforceRelation("B", "C", "coactivation", .9, .05, 0, 1); err != nil { ++ t.Fatal(err) ++ } ++ ++ st := s.GraphStats() ++ if st.Synapses != 4 || st.MultiLinked < 2 || st.MaxDegree < 3 || st.ConnectedComponents != 1 || st.LargestComponent != 4 { ++ t.Fatalf("graph stats do not prove an n:m connected graph: %+v", st) ++ } ++ ++ s.mu.RLock() ++ seed, ok := s.fullMemoryForReadLocked("A") ++ if !ok { ++ s.mu.RUnlock() ++ t.Fatal("missing A") ++ } ++ hits := s.graphExpandLocked([]float32{1, .01}, []SearchHit{{Memory: cloneMemory(seed), Similarity: 1, Score: 1, BaseScore: 1}}, 4, .1, .25) ++ s.mu.RUnlock() ++ foundD := false ++ for _, h := range hits { ++ if h.Memory.ID == "D" && h.CandidateSource == "synapse-multihop" { ++ foundD = true ++ } ++ } ++ if !foundD { ++ t.Fatalf("multi-hop traversal did not surface D: %+v", hits) ++ } ++ ++ g := s.KnowledgeGraph("B", 2, 32) ++ relationFound := false ++ for _, e := range g.Edges { ++ if (e.A == "B" && e.B == "C") || (e.A == "C" && e.B == "B") { ++ if len(e.Relations) >= 2 { ++ relationFound = true ++ } ++ } ++ } ++ if !relationFound { ++ t.Fatalf("typed/multi-relation edge not exposed: %+v", g.Edges) ++ } ++} ++ ++func TestGraphBackfillSkipsTargetsAlreadyQueued(t *testing.T) { ++ s, err := New(t.TempDir()) ++ if err != nil { ++ t.Fatal(err) ++ } ++ defer s.Close() ++ items := []core.Memory{ ++ {ID: "A", Kind: "knowledge", MemoryType: core.MemorySemantic, Text: "A", Vector: []float32{1, 0}}, ++ {ID: "B", Kind: "knowledge", MemoryType: core.MemorySemantic, Text: "B", Vector: []float32{.9, .1}}, ++ {ID: "C", Kind: "knowledge", MemoryType: core.MemorySemantic, Text: "C", Vector: []float32{.8, .2}}, ++ } ++ if err := s.AddMemoriesBatch(items); err != nil { ++ t.Fatal(err) ++ } ++ if _, err := s.EnqueueJobSpec(JobSpec{Type: "vector.relink", Payload: map[string]any{"target_id": "A"}, IdempotencyKey: "vector.relink:A:v1", ResourceClass: "cpu"}); err != nil { ++ t.Fatal(err) ++ } ++ got := s.GraphBackfillCandidates(2, 3, time.Hour) ++ if len(got) != 2 || got[0].ID == "A" || got[1].ID == "A" { ++ t.Fatalf("queued target was not skipped: %+v", got) ++ } ++} +diff --git a/platform/neuroforge/internal/store/knowledge.go b/platform/neuroforge/internal/store/knowledge.go +index c6e4f34..89c481b 100644 +--- a/platform/neuroforge/internal/store/knowledge.go ++++ b/platform/neuroforge/internal/store/knowledge.go +@@ -61,6 +61,7 @@ type KnowledgeList struct { + type KnowledgeEdge struct { + A string `json:"a"` + B string `json:"b"` ++ Relations []string `json:"relations,omitempty"` + Weight float64 `json:"weight"` + Similarity float64 `json:"similarity"` + Activations int64 `json:"activations"` +@@ -368,7 +369,7 @@ func (s *Store) KnowledgeGraph(center string, depth, maxNodes int) KnowledgeGrap + } + for _, syn := range s.state.Synapses { + if selected[syn.A] && selected[syn.B] { +- out.Edges = append(out.Edges, KnowledgeEdge{A: syn.A, B: syn.B, Weight: syn.Weight, Similarity: syn.Similarity, Activations: syn.Activations, LastUpdated: syn.LastUpdated}) ++ out.Edges = append(out.Edges, KnowledgeEdge{A: syn.A, B: syn.B, Relations: append([]string(nil), syn.Relations...), Weight: syn.Weight, Similarity: syn.Similarity, Activations: syn.Activations, LastUpdated: syn.LastUpdated}) + } + } + sort.Slice(out.Edges, func(i, j int) bool { return out.Edges[i].Weight > out.Edges[j].Weight }) +@@ -397,7 +398,7 @@ func (s *Store) KnowledgeMemoryDetail(id string) (KnowledgeMemoryDetail, bool) { + } else { + continue + } +- out.Edges = append(out.Edges, KnowledgeEdge{A: syn.A, B: syn.B, Weight: syn.Weight, Similarity: syn.Similarity, Activations: syn.Activations, LastUpdated: syn.LastUpdated}) ++ out.Edges = append(out.Edges, KnowledgeEdge{A: syn.A, B: syn.B, Relations: append([]string(nil), syn.Relations...), Weight: syn.Weight, Similarity: syn.Similarity, Activations: syn.Activations, LastUpdated: syn.LastUpdated}) + if !seenNeighbor[other] { + if om, ok := s.fullMemoryForReadLocked(other); ok { + out.Neighbors = append(out.Neighbors, memoryPreview(om)) +diff --git a/mnt/data/v160_work/platform/neuroforge/internal/store/orchestrator.go b/platform/neuroforge/internal/store/orchestrator.go +new file mode 100644 +index 0000000..f3b6368 +--- /dev/null ++++ b/platform/neuroforge/internal/store/orchestrator.go +@@ -0,0 +1,841 @@ ++package store ++ ++import ( ++ "encoding/json" ++ "errors" ++ "fmt" ++ "sort" ++ "strings" ++ "time" ++ ++ "neuroforge/internal/core" ++) ++ ++// JobSpec describes a durable unit of work. Jobs are persisted through the ++// normal NeuroForge WAL/checkpoint path so a master restart does not lose ++// queued, leased, retrying, or dependency-blocked work. ++type JobSpec struct { ++ Type string ++ Payload any ++ Priority int ++ ResourceClass string ++ RequiredCapabilities []string ++ IdempotencyKey string ++ ParentJobID string ++ DependsOn []string ++ MaxAttempts int ++ BackoffSeconds int ++ TimeoutSeconds int ++ RequiresMasterApply bool ++ MaxApplyAttempts int ++ ApplyBackoffSeconds int ++} ++ ++type WorkerHeartbeat struct { ++ ID string ++ ResourceClass string ++ Capabilities []string ++ Labels map[string]string ++ MaxConcurrency int ++ Version string ++ Hostname string ++ ActiveLeases map[string]string // job_id -> lease_token ++} ++ ++type OrchestratorStatus struct { ++ Workers []core.WorkerState `json:"workers"` ++ JobsByStatus map[string]int `json:"jobs_by_status"` ++ JobsByType map[string]int `json:"jobs_by_type"` ++ JobsByResource map[string]int `json:"jobs_by_resource"` ++ OldestQueued time.Time `json:"oldest_queued,omitempty"` ++ Queued int `json:"queued"` ++ Claimed int `json:"claimed"` ++ Retrying int `json:"retrying"` ++ Applying int `json:"applying"` ++ Blocked int `json:"blocked"` ++ Failed int `json:"failed"` ++ Done int `json:"done"` ++} ++ ++func normalizeCapabilities(in []string) []string { ++ seen := map[string]bool{} ++ out := make([]string, 0, len(in)) ++ for _, x := range in { ++ x = strings.ToLower(strings.TrimSpace(x)) ++ if x == "" || seen[x] { ++ continue ++ } ++ seen[x] = true ++ out = append(out, x) ++ } ++ sort.Strings(out) ++ return out ++} ++ ++func capabilitiesContain(have []string, need []string) bool { ++ if len(need) == 0 { ++ return true ++ } ++ m := make(map[string]bool, len(have)) ++ for _, x := range have { ++ m[strings.ToLower(strings.TrimSpace(x))] = true ++ } ++ for _, x := range need { ++ if !m[strings.ToLower(strings.TrimSpace(x))] { ++ return false ++ } ++ } ++ return true ++} ++ ++func (s *Store) EnqueueJob(kind string, payload any) (*core.Job, error) { ++ return s.EnqueueJobSpec(JobSpec{Type: kind, Payload: payload}) ++} ++ ++func (s *Store) EnqueueJobSpec(spec JobSpec) (*core.Job, error) { ++ spec.Type = strings.TrimSpace(spec.Type) ++ if spec.Type == "" { ++ return nil, errors.New("job type is required") ++ } ++ b, err := json.Marshal(spec.Payload) ++ if err != nil { ++ return nil, err ++ } ++ s.mu.Lock() ++ defer s.mu.Unlock() ++ cfg := s.state.Config.Worker ++ if cfg.MaxQueuedJobs > 0 { ++ pending := 0 ++ for _, j := range s.state.Jobs { ++ if j != nil && (j.Status == "queued" || j.Status == "claimed" || j.Status == "retry_wait" || j.Status == "blocked" || j.Status == "apply_wait") { ++ pending++ ++ } ++ } ++ if pending >= cfg.MaxQueuedJobs { ++ return nil, fmt.Errorf("orchestrator queue full: %d >= %d", pending, cfg.MaxQueuedJobs) ++ } ++ } ++ if key := strings.TrimSpace(spec.IdempotencyKey); key != "" { ++ for _, j := range s.state.Jobs { ++ if j != nil && j.IdempotencyKey == key && (j.Status == "queued" || j.Status == "claimed" || j.Status == "retry_wait" || j.Status == "blocked" || j.Status == "apply_wait") { ++ cp := *j ++ return &cp, nil ++ } ++ } ++ } ++ // Dependencies must already exist. Accepting dangling IDs would create jobs ++ // that can remain blocked forever and makes convergence impossible to reason ++ // about after operator typos or partial planning failures. ++ seenDeps := make(map[string]struct{}, len(spec.DependsOn)) ++ cleanDeps := make([]string, 0, len(spec.DependsOn)) ++ for _, depID := range spec.DependsOn { ++ depID = strings.TrimSpace(depID) ++ if depID == "" { ++ return nil, errors.New("dependency job id must not be empty") ++ } ++ if _, duplicate := seenDeps[depID]; duplicate { ++ continue ++ } ++ if s.state.Jobs[depID] == nil { ++ return nil, fmt.Errorf("dependency job %s not found", depID) ++ } ++ seenDeps[depID] = struct{}{} ++ cleanDeps = append(cleanDeps, depID) ++ } ++ spec.DependsOn = cleanDeps ++ if parentID := strings.TrimSpace(spec.ParentJobID); parentID != "" && s.state.Jobs[parentID] == nil { ++ return nil, fmt.Errorf("parent job %s not found", parentID) ++ } ++ now := time.Now().UTC() ++ maxAttempts := spec.MaxAttempts ++ if maxAttempts <= 0 { ++ maxAttempts = cfg.DefaultMaxAttempts ++ } ++ if maxAttempts <= 0 { ++ maxAttempts = 3 ++ } ++ backoff := spec.BackoffSeconds ++ if backoff <= 0 { ++ backoff = cfg.RetryBackoffSeconds ++ } ++ if backoff <= 0 { ++ backoff = 15 ++ } ++ maxApplyAttempts := spec.MaxApplyAttempts ++ if maxApplyAttempts <= 0 { ++ maxApplyAttempts = cfg.MasterApplyMaxAttempts ++ } ++ if maxApplyAttempts <= 0 { ++ maxApplyAttempts = 5 ++ } ++ applyBackoff := spec.ApplyBackoffSeconds ++ if applyBackoff <= 0 { ++ applyBackoff = cfg.MasterApplyBackoffSeconds ++ } ++ if applyBackoff <= 0 { ++ applyBackoff = 5 ++ } ++ priority := spec.Priority ++ if priority < -1000 { ++ priority = -1000 ++ } ++ if priority > 1000 { ++ priority = 1000 ++ } ++ status := "queued" ++ if len(spec.DependsOn) > 0 { ++ status = "blocked" ++ } ++ j := &core.Job{ ++ ID: NewID("job"), ++ Type: spec.Type, ++ Payload: b, ++ Status: status, ++ Priority: priority, ++ ResourceClass: strings.ToLower(strings.TrimSpace(spec.ResourceClass)), ++ RequiredCapabilities: normalizeCapabilities(spec.RequiredCapabilities), ++ IdempotencyKey: strings.TrimSpace(spec.IdempotencyKey), ++ ParentJobID: strings.TrimSpace(spec.ParentJobID), ++ DependsOn: append([]string(nil), spec.DependsOn...), ++ MaxAttempts: maxAttempts, ++ BackoffSeconds: backoff, ++ TimeoutSeconds: spec.TimeoutSeconds, ++ RequiresMasterApply: spec.RequiresMasterApply, ++ MaxApplyAttempts: maxApplyAttempts, ++ ApplyBackoffSeconds: applyBackoff, ++ CreatedAt: now, ++ UpdatedAt: now, ++ } ++ s.state.Jobs[j.ID] = j ++ cp := *j ++ return &cp, s.commitLocked("job.upsert", cp) ++} ++ ++func (s *Store) dependencyStateLocked(j *core.Job) (ready bool, terminalFailure string) { ++ if len(j.DependsOn) == 0 { ++ return true, "" ++ } ++ for _, id := range j.DependsOn { ++ dep := s.state.Jobs[id] ++ if dep == nil { ++ return false, "" ++ } ++ switch dep.Status { ++ case "done": ++ continue ++ case "failed", "canceled": ++ return false, "dependency " + id + " ended as " + dep.Status ++ default: ++ return false, "" ++ } ++ } ++ return true, "" ++} ++ ++func workerMatchesJob(w WorkerHeartbeat, j *core.Job) bool { ++ resource := strings.ToLower(strings.TrimSpace(j.ResourceClass)) ++ workerResource := strings.ToLower(strings.TrimSpace(w.ResourceClass)) ++ if resource != "" && resource != "any" && resource != workerResource { ++ return false ++ } ++ return capabilitiesContain(w.Capabilities, j.RequiredCapabilities) ++} ++ ++func (s *Store) registerWorkerLocked(h WorkerHeartbeat, now time.Time) core.WorkerState { ++ if s.workers == nil { ++ s.workers = map[string]core.WorkerState{} ++ } ++ old := s.workers[h.ID] ++ registered := old.RegisteredAt ++ if registered.IsZero() { ++ registered = now ++ } ++ maxc := h.MaxConcurrency ++ if maxc <= 0 { ++ maxc = 1 ++ } ++ state := core.WorkerState{ ++ ID: h.ID, ++ ResourceClass: strings.ToLower(strings.TrimSpace(h.ResourceClass)), ++ Capabilities: normalizeCapabilities(h.Capabilities), ++ Labels: h.Labels, ++ MaxConcurrency: maxc, ++ Version: strings.TrimSpace(h.Version), ++ Hostname: strings.TrimSpace(h.Hostname), ++ LastHeartbeat: now, ++ RegisteredAt: registered, ++ Status: "online", ++ } ++ if state.ResourceClass == "" { ++ state.ResourceClass = "cpu" ++ } ++ if len(state.Capabilities) == 0 { ++ state.Capabilities = []string{"cpu", "vector.relink"} ++ } ++ for _, j := range s.state.Jobs { ++ if j != nil && j.Status == "claimed" && j.ClaimedBy == h.ID && now.Before(j.LeaseUntil) { ++ state.Inflight++ ++ } ++ } ++ s.workers[h.ID] = state ++ return state ++} ++ ++func (s *Store) RegisterWorker(h WorkerHeartbeat) (core.WorkerState, error) { ++ h.ID = strings.TrimSpace(h.ID) ++ if h.ID == "" { ++ return core.WorkerState{}, errors.New("worker id is required") ++ } ++ s.mu.Lock() ++ defer s.mu.Unlock() ++ return s.registerWorkerLocked(h, time.Now().UTC()), nil ++} ++ ++func (s *Store) HeartbeatWorker(h WorkerHeartbeat, lease time.Duration) (core.WorkerState, error) { ++ h.ID = strings.TrimSpace(h.ID) ++ if h.ID == "" { ++ return core.WorkerState{}, errors.New("worker id is required") ++ } ++ if lease <= 0 { ++ lease = 120 * time.Second ++ } ++ s.mu.Lock() ++ defer s.mu.Unlock() ++ now := time.Now().UTC() ++ state := s.registerWorkerLocked(h, now) ++ for jobID, token := range h.ActiveLeases { ++ j := s.state.Jobs[jobID] ++ if j == nil || j.Status != "claimed" || j.ClaimedBy != h.ID || j.LeaseToken == "" || j.LeaseToken != token { ++ continue ++ } ++ // Persist a renewal only when the remaining lease is below half the ++ // configured lease. This gives restart-safe leases without heartbeat WAL spam. ++ if time.Until(j.LeaseUntil) <= lease/2 { ++ j.LeaseUntil = now.Add(lease) ++ j.UpdatedAt = now ++ cp := *j ++ if err := s.commitLocked("job.upsert", cp); err != nil { ++ return core.WorkerState{}, err ++ } ++ } ++ } ++ return state, nil ++} ++ ++func (s *Store) normalizeJobForClaimLocked(j *core.Job, now time.Time) error { ++ if j == nil { ++ return nil ++ } ++ changed := false ++ if j.MaxAttempts <= 0 { ++ j.MaxAttempts = s.state.Config.Worker.DefaultMaxAttempts ++ if j.MaxAttempts <= 0 { ++ j.MaxAttempts = 3 ++ } ++ changed = true ++ } ++ if j.Status == "claimed" && !j.LeaseUntil.IsZero() && now.After(j.LeaseUntil) { ++ j.ClaimedBy = "" ++ j.LeaseToken = "" ++ j.LeaseUntil = time.Time{} ++ if j.Attempts >= j.MaxAttempts { ++ j.Status = "failed" ++ j.Error = "lease expired after maximum attempts" ++ j.FinishedAt = now ++ } else { ++ j.Status = "retry_wait" ++ j.Error = "worker lease expired" ++ j.NextAttemptAt = now.Add(time.Duration(maxInt(1, j.BackoffSeconds)) * time.Second) ++ } ++ j.UpdatedAt = now ++ changed = true ++ } ++ if j.Status == "retry_wait" && (j.NextAttemptAt.IsZero() || !now.Before(j.NextAttemptAt)) { ++ j.Status = "queued" ++ j.NextAttemptAt = time.Time{} ++ j.UpdatedAt = now ++ changed = true ++ } ++ if j.Status == "blocked" { ++ ready, depFailure := s.dependencyStateLocked(j) ++ if depFailure != "" { ++ j.Status = "failed" ++ j.Error = depFailure ++ j.FinishedAt = now ++ j.UpdatedAt = now ++ changed = true ++ } else if ready { ++ j.Status = "queued" ++ j.UpdatedAt = now ++ changed = true ++ } ++ } ++ if changed { ++ cp := *j ++ return s.commitLocked("job.upsert", cp) ++ } ++ return nil ++} ++ ++func maxInt(a, b int) int { ++ if a > b { ++ return a ++ } ++ return b ++} ++ ++// ClaimJob preserves the v0.8 worker contract for compatibility. New workers ++// should use ClaimJobForWorker so resource/capability routing is enforced. ++func (s *Store) ClaimJob(worker string, lease time.Duration) (*core.Job, error) { ++ return s.ClaimJobForWorker(WorkerHeartbeat{ID: worker, ResourceClass: "cpu", Capabilities: []string{"cpu", "vector.relink"}, MaxConcurrency: 1}, lease) ++} ++ ++func (s *Store) ClaimJobForWorker(h WorkerHeartbeat, lease time.Duration) (*core.Job, error) { ++ h.ID = strings.TrimSpace(h.ID) ++ if h.ID == "" { ++ return nil, errors.New("worker id is required") ++ } ++ if lease <= 0 { ++ lease = 120 * time.Second ++ } ++ s.mu.Lock() ++ defer s.mu.Unlock() ++ now := time.Now().UTC() ++ state := s.registerWorkerLocked(h, now) ++ if state.Inflight >= state.MaxConcurrency { ++ return nil, nil ++ } ++ var chosen *core.Job ++ for _, j := range s.state.Jobs { ++ if err := s.normalizeJobForClaimLocked(j, now); err != nil { ++ return nil, err ++ } ++ if j == nil || j.Status != "queued" || !workerMatchesJob(h, j) { ++ continue ++ } ++ if chosen == nil || j.Priority > chosen.Priority || (j.Priority == chosen.Priority && j.CreatedAt.Before(chosen.CreatedAt)) { ++ chosen = j ++ } ++ } ++ if chosen == nil { ++ return nil, nil ++ } ++ chosen.Status = "claimed" ++ chosen.ClaimedBy = h.ID ++ chosen.LeaseToken = NewID("lease") ++ chosen.LeaseUntil = now.Add(lease) ++ chosen.Attempts++ ++ chosen.UpdatedAt = now ++ if chosen.StartedAt.IsZero() { ++ chosen.StartedAt = now ++ } ++ cp := *chosen ++ return &cp, s.commitLocked("job.upsert", cp) ++} ++ ++func (s *Store) CompleteJob(id, worker string, result json.RawMessage, jobErr string) (*core.Job, error) { ++ return s.CompleteJobLease(id, worker, "", result, jobErr) ++} ++ ++func (s *Store) CompleteJobLease(id, worker, leaseToken string, result json.RawMessage, jobErr string) (*core.Job, error) { ++ s.mu.Lock() ++ defer s.mu.Unlock() ++ j, ok := s.state.Jobs[id] ++ if !ok || j == nil { ++ return nil, errors.New("job not found") ++ } ++ if j.Status != "claimed" { ++ return nil, fmt.Errorf("job is not claimed: %s", j.Status) ++ } ++ if j.ClaimedBy != worker { ++ return nil, errors.New("job claimed by another worker") ++ } ++ if j.LeaseToken != "" && leaseToken != j.LeaseToken { ++ return nil, errors.New("stale or invalid job lease token") ++ } ++ now := time.Now().UTC() ++ if !j.LeaseUntil.IsZero() && !now.Before(j.LeaseUntil) { ++ // A lease token fences ownership, but its validity also has a deadline. ++ // Reject late results even if no other worker has claimed the job yet; ++ // otherwise a paused partitioned worker could mutate state after expiry. ++ return nil, errors.New("stale or expired job lease") ++ } ++ j.Result = result ++ j.Error = strings.TrimSpace(jobErr) ++ j.UpdatedAt = now ++ j.ClaimedBy = "" ++ j.LeaseToken = "" ++ j.LeaseUntil = time.Time{} ++ if j.Error == "" { ++ j.NextAttemptAt = time.Time{} ++ if j.RequiresMasterApply { ++ j.Status = "apply_wait" ++ j.ApplyError = "" ++ j.ApplyNextAttemptAt = now ++ } else { ++ j.Status = "done" ++ j.FinishedAt = now ++ } ++ } else if j.Attempts < maxInt(1, j.MaxAttempts) { ++ j.Status = "retry_wait" ++ backoff := maxInt(1, j.BackoffSeconds) ++ shift := j.Attempts - 1 ++ if shift > 6 { ++ shift = 6 ++ } ++ delay := time.Duration(backoff*(1< time.Hour { ++ delay = time.Hour ++ } ++ j.NextAttemptAt = now.Add(delay) ++ } else { ++ j.Status = "failed" ++ j.FinishedAt = now ++ } ++ cp := *j ++ return &cp, s.commitLocked("job.upsert", cp) ++} ++ ++func (s *Store) Job(id string) (*core.Job, bool) { ++ s.mu.RLock() ++ defer s.mu.RUnlock() ++ j := s.state.Jobs[id] ++ if j == nil { ++ return nil, false ++ } ++ cp := *j ++ cp.Payload = append(json.RawMessage(nil), j.Payload...) ++ cp.Result = append(json.RawMessage(nil), j.Result...) ++ return &cp, true ++} ++ ++func (s *Store) JobsSnapshot(limit int, status, kind string) []core.Job { ++ if limit <= 0 || limit > 5000 { ++ limit = 500 ++ } ++ status = strings.TrimSpace(status) ++ kind = strings.TrimSpace(kind) ++ s.mu.RLock() ++ defer s.mu.RUnlock() ++ out := make([]core.Job, 0) ++ for _, j := range s.state.Jobs { ++ if j == nil || (status != "" && j.Status != status) || (kind != "" && j.Type != kind) { ++ continue ++ } ++ cp := *j ++ // Admin status does not need potentially large/sensitive payloads. ++ cp.Payload = nil ++ cp.Result = nil ++ out = append(out, cp) ++ } ++ sort.Slice(out, func(i, j int) bool { return out[i].CreatedAt.After(out[j].CreatedAt) }) ++ if len(out) > limit { ++ out = out[:limit] ++ } ++ return out ++} ++ ++func (s *Store) RetryJob(id string) (*core.Job, error) { ++ s.mu.Lock() ++ defer s.mu.Unlock() ++ j := s.state.Jobs[id] ++ if j == nil { ++ return nil, errors.New("job not found") ++ } ++ if j.Status != "failed" && j.Status != "canceled" { ++ return nil, fmt.Errorf("job %s cannot be retried from status %s", id, j.Status) ++ } ++ j.Status = "queued" ++ j.Error = "" ++ j.Result = nil ++ j.Attempts = 0 ++ j.NextAttemptAt = time.Time{} ++ j.FinishedAt = time.Time{} ++ j.UpdatedAt = time.Now().UTC() ++ cp := *j ++ return &cp, s.commitLocked("job.upsert", cp) ++} ++ ++func (s *Store) WorkersSnapshot() []core.WorkerState { ++ s.mu.RLock() ++ defer s.mu.RUnlock() ++ now := time.Now().UTC() ++ staleAfter := time.Duration(s.state.Config.Worker.StaleAfterSeconds) * time.Second ++ if staleAfter <= 0 { ++ staleAfter = 60 * time.Second ++ } ++ out := make([]core.WorkerState, 0, len(s.workers)) ++ for _, w := range s.workers { ++ cp := w ++ cp.Inflight = 0 ++ for _, j := range s.state.Jobs { ++ if j != nil && j.Status == "claimed" && j.ClaimedBy == w.ID && now.Before(j.LeaseUntil) { ++ cp.Inflight++ ++ } ++ } ++ if now.Sub(cp.LastHeartbeat) > staleAfter { ++ cp.Status = "stale" ++ } ++ out = append(out, cp) ++ } ++ sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID }) ++ return out ++} ++ ++func (s *Store) HasLiveWorker(resource string, capabilities ...string) bool { ++ resource = strings.ToLower(strings.TrimSpace(resource)) ++ for _, w := range s.WorkersSnapshot() { ++ if w.Status != "online" || (resource != "" && resource != "any" && w.ResourceClass != resource) { ++ continue ++ } ++ if capabilitiesContain(w.Capabilities, capabilities) && w.Inflight < w.MaxConcurrency { ++ return true ++ } ++ } ++ return false ++} ++ ++func (s *Store) OrchestratorStatus() OrchestratorStatus { ++ out := OrchestratorStatus{Workers: s.WorkersSnapshot(), JobsByStatus: map[string]int{}, JobsByType: map[string]int{}, JobsByResource: map[string]int{}} ++ s.mu.RLock() ++ defer s.mu.RUnlock() ++ for _, j := range s.state.Jobs { ++ if j == nil { ++ continue ++ } ++ out.JobsByStatus[j.Status]++ ++ out.JobsByType[j.Type]++ ++ resource := j.ResourceClass ++ if resource == "" { ++ resource = "any" ++ } ++ out.JobsByResource[resource]++ ++ switch j.Status { ++ case "queued": ++ out.Queued++ ++ if out.OldestQueued.IsZero() || j.CreatedAt.Before(out.OldestQueued) { ++ out.OldestQueued = j.CreatedAt ++ } ++ case "claimed": ++ out.Claimed++ ++ case "retry_wait": ++ out.Retrying++ ++ case "apply_wait": ++ out.Applying++ ++ case "blocked": ++ out.Blocked++ ++ case "failed": ++ out.Failed++ ++ case "done": ++ out.Done++ ++ } ++ } ++ return out ++} ++ ++func (s *Store) PendingJobCount(kind string) int { ++ s.mu.RLock() ++ defer s.mu.RUnlock() ++ n := 0 ++ for _, j := range s.state.Jobs { ++ if j == nil || (kind != "" && j.Type != kind) { ++ continue ++ } ++ if j.Status == "queued" || j.Status == "claimed" || j.Status == "retry_wait" || j.Status == "blocked" || j.Status == "apply_wait" { ++ n++ ++ } ++ } ++ return n ++} ++ ++// PendingMasterApplyJobs returns durable worker results whose mutation still ++// needs to be committed by the master. The result payload remains persisted, ++// so a master restart can resume without recomputing the worker job. ++func (s *Store) PendingMasterApplyJobs(limit int) []core.Job { ++ if limit <= 0 || limit > 1024 { ++ limit = 64 ++ } ++ now := time.Now().UTC() ++ s.mu.RLock() ++ defer s.mu.RUnlock() ++ out := make([]core.Job, 0, limit) ++ for _, j := range s.state.Jobs { ++ if j == nil || j.Status != "apply_wait" || (!j.ApplyNextAttemptAt.IsZero() && now.Before(j.ApplyNextAttemptAt)) { ++ continue ++ } ++ cp := *j ++ cp.Payload = append(json.RawMessage(nil), j.Payload...) ++ cp.Result = append(json.RawMessage(nil), j.Result...) ++ out = append(out, cp) ++ } ++ sort.Slice(out, func(i, j int) bool { ++ if out[i].Priority != out[j].Priority { ++ return out[i].Priority > out[j].Priority ++ } ++ return out[i].UpdatedAt.Before(out[j].UpdatedAt) ++ }) ++ if len(out) > limit { ++ out = out[:limit] ++ } ++ return out ++} ++ ++// FinishMasterApply closes the second phase of a worker job. Failed apply ++// attempts are retried from the already persisted Result; successful worker ++// computation is never lost merely because the master restarted mid-commit. ++func (s *Store) FinishMasterApply(id string, applyErr error) (*core.Job, error) { ++ s.mu.Lock() ++ defer s.mu.Unlock() ++ j := s.state.Jobs[id] ++ if j == nil { ++ return nil, errors.New("job not found") ++ } ++ if j.Status != "apply_wait" { ++ return nil, fmt.Errorf("job is not awaiting master apply: %s", j.Status) ++ } ++ now := time.Now().UTC() ++ j.ApplyAttempts++ ++ j.UpdatedAt = now ++ if applyErr == nil { ++ j.Status = "done" ++ j.ApplyError = "" ++ j.ApplyNextAttemptAt = time.Time{} ++ j.FinishedAt = now ++ } else { ++ j.ApplyError = strings.TrimSpace(applyErr.Error()) ++ maxAttempts := j.MaxApplyAttempts ++ if maxAttempts <= 0 { ++ maxAttempts = maxInt(1, s.state.Config.Worker.MasterApplyMaxAttempts) ++ } ++ if j.ApplyAttempts >= maxAttempts { ++ j.Status = "failed" ++ j.Error = "master apply failed: " + j.ApplyError ++ j.FinishedAt = now ++ j.ApplyNextAttemptAt = time.Time{} ++ } else { ++ backoff := j.ApplyBackoffSeconds ++ if backoff <= 0 { ++ backoff = maxInt(1, s.state.Config.Worker.MasterApplyBackoffSeconds) ++ } ++ shift := j.ApplyAttempts - 1 ++ if shift > 6 { ++ shift = 6 ++ } ++ delay := time.Duration(backoff*(1< time.Hour { ++ delay = time.Hour ++ } ++ j.ApplyNextAttemptAt = now.Add(delay) ++ } ++ } ++ cp := *j ++ return &cp, s.commitLocked("job.upsert", cp) ++} ++ ++func (s *Store) CancelJob(id, reason string) error { ++ s.mu.Lock() ++ defer s.mu.Unlock() ++ j := s.state.Jobs[id] ++ if j == nil { ++ return errors.New("job not found") ++ } ++ if j.Status == "done" || j.Status == "failed" || j.Status == "canceled" { ++ return nil ++ } ++ now := time.Now().UTC() ++ j.Status = "canceled" ++ j.Error = strings.TrimSpace(reason) ++ j.ClaimedBy = "" ++ j.LeaseToken = "" ++ j.LeaseUntil = time.Time{} ++ j.FinishedAt = now ++ j.UpdatedAt = now ++ cp := *j ++ return s.commitLocked("job.upsert", cp) ++} ++ ++// PruneTerminalJobs bounds durable scheduler history while preserving jobs that ++// are still referenced as dependencies by non-terminal work. ++func (s *Store) PruneTerminalJobs(retention time.Duration, maxTerminal int) (int, error) { ++ if retention <= 0 { ++ retention = 7 * 24 * time.Hour ++ } ++ if maxTerminal < 100 { ++ maxTerminal = 20000 ++ } ++ s.mu.Lock() ++ defer s.mu.Unlock() ++ now := time.Now().UTC() ++ cutoff := now.Add(-retention) ++ referenced := map[string]bool{} ++ for _, j := range s.state.Jobs { ++ if j == nil || j.Status == "done" || j.Status == "failed" || j.Status == "canceled" { ++ continue ++ } ++ for _, dep := range j.DependsOn { ++ referenced[dep] = true ++ } ++ } ++ type terminal struct { ++ id string ++ t time.Time ++ } ++ items := make([]terminal, 0) ++ for id, j := range s.state.Jobs { ++ if j == nil || referenced[id] || (j.Status != "done" && j.Status != "failed" && j.Status != "canceled") { ++ continue ++ } ++ t := j.FinishedAt ++ if t.IsZero() { ++ t = j.UpdatedAt ++ } ++ items = append(items, terminal{id: id, t: t}) ++ } ++ sort.Slice(items, func(i, j int) bool { return items[i].t.Before(items[j].t) }) ++ remove := map[string]bool{} ++ for _, x := range items { ++ if !x.t.IsZero() && x.t.Before(cutoff) { ++ remove[x.id] = true ++ } ++ } ++ remaining := len(items) - len(remove) ++ if remaining > maxTerminal { ++ need := remaining - maxTerminal ++ for _, x := range items { ++ if need == 0 { ++ break ++ } ++ if remove[x.id] { ++ continue ++ } ++ remove[x.id] = true ++ need-- ++ } ++ } ++ if len(remove) == 0 { ++ return 0, nil ++ } ++ ids := make([]string, 0, len(remove)) ++ for id := range remove { ++ delete(s.state.Jobs, id) ++ ids = append(ids, id) ++ } ++ sort.Strings(ids) ++ return len(ids), s.commitLocked("job.delete", ids) ++} ++ ++// IsOrchestratorLeader prevents two clustered masters from planning/applying ++// the same durable work. Standalone installations are always authoritative. ++func (s *Store) IsOrchestratorLeader() bool { ++ s.mu.RLock() ++ defer s.mu.RUnlock() ++ cfg := s.state.Config.Cluster ++ if !cfg.Enabled { ++ return true ++ } ++ leader := s.state.Cluster.LeaderID ++ if !cfg.AutoElection && leader == "" { ++ leader = cfg.LeaderID ++ } ++ return leader != "" && leader == cfg.NodeID && s.state.Cluster.Role == ClusterLeader ++} +diff --git a/mnt/data/v160_work/platform/neuroforge/internal/store/orchestrator_test.go b/platform/neuroforge/internal/store/orchestrator_test.go +new file mode 100644 +index 0000000..16ca3a8 +--- /dev/null ++++ b/platform/neuroforge/internal/store/orchestrator_test.go +@@ -0,0 +1,294 @@ ++package store ++ ++import ( ++ "encoding/json" ++ "strings" ++ "testing" ++ "time" ++) ++ ++func TestOrchestratorCapabilityRoutingAndLeaseFencing(t *testing.T) { ++ s, err := New(t.TempDir()) ++ if err != nil { ++ t.Fatal(err) ++ } ++ defer s.Close() ++ ++ j, err := s.EnqueueJobSpec(JobSpec{ ++ Type: "model.chat", Payload: map[string]any{"input": "x"}, Priority: 100, ++ ResourceClass: "gpu", RequiredCapabilities: []string{"gpu", "model.chat"}, ++ }) ++ if err != nil { ++ t.Fatal(err) ++ } ++ ++ cpu := WorkerHeartbeat{ID: "cpu-1", ResourceClass: "cpu", Capabilities: []string{"cpu", "vector.relink"}, MaxConcurrency: 1} ++ got, err := s.ClaimJobForWorker(cpu, time.Minute) ++ if err != nil { ++ t.Fatal(err) ++ } ++ if got != nil { ++ t.Fatalf("CPU worker must not claim GPU job: %+v", got) ++ } ++ ++ gpu := WorkerHeartbeat{ID: "gpu-1", ResourceClass: "gpu", Capabilities: []string{"gpu", "model.chat"}, MaxConcurrency: 1} ++ got, err = s.ClaimJobForWorker(gpu, time.Minute) ++ if err != nil { ++ t.Fatal(err) ++ } ++ if got == nil || got.ID != j.ID || got.LeaseToken == "" { ++ t.Fatalf("GPU claim=%+v", got) ++ } ++ ++ if _, err := s.CompleteJobLease(got.ID, gpu.ID, "stale-token", json.RawMessage(`{"ok":true}`), ""); err == nil || !strings.Contains(err.Error(), "lease") { ++ t.Fatalf("stale lease must be fenced, err=%v", err) ++ } ++ done, err := s.CompleteJobLease(got.ID, gpu.ID, got.LeaseToken, json.RawMessage(`{"ok":true}`), "") ++ if err != nil { ++ t.Fatal(err) ++ } ++ if done.Status != "done" { ++ t.Fatalf("status=%s", done.Status) ++ } ++} ++ ++func TestOrchestratorRetryDependencyAndWorkerStaleness(t *testing.T) { ++ s, err := New(t.TempDir()) ++ if err != nil { ++ t.Fatal(err) ++ } ++ defer s.Close() ++ cfg := s.Config() ++ cfg.Worker.RetryBackoffSeconds = 1 ++ cfg.Worker.HeartbeatSeconds = 2 ++ cfg.Worker.StaleAfterSeconds = 2 ++ if err := s.UpdateConfig(cfg); err != nil { ++ t.Fatal(err) ++ } ++ ++ parent, err := s.EnqueueJobSpec(JobSpec{Type: "parent", Payload: map[string]any{}, ResourceClass: "cpu", MaxAttempts: 2, BackoffSeconds: 1}) ++ if err != nil { ++ t.Fatal(err) ++ } ++ child, err := s.EnqueueJobSpec(JobSpec{Type: "child", Payload: map[string]any{}, ResourceClass: "cpu", DependsOn: []string{parent.ID}}) ++ if err != nil { ++ t.Fatal(err) ++ } ++ if child.Status != "blocked" { ++ t.Fatalf("child status=%s", child.Status) ++ } ++ ++ w := WorkerHeartbeat{ID: "cpu-1", ResourceClass: "cpu", Capabilities: []string{"cpu"}, MaxConcurrency: 1} ++ p1, err := s.ClaimJobForWorker(w, time.Minute) ++ if err != nil { ++ t.Fatal(err) ++ } ++ if p1 == nil || p1.ID != parent.ID { ++ t.Fatalf("claim=%+v", p1) ++ } ++ retry, err := s.CompleteJobLease(p1.ID, w.ID, p1.LeaseToken, nil, "temporary") ++ if err != nil { ++ t.Fatal(err) ++ } ++ if retry.Status != "retry_wait" || retry.NextAttemptAt.IsZero() { ++ t.Fatalf("retry=%+v", retry) ++ } ++ ++ // Avoid sleeping: advance this job's retry clock under the package lock. ++ s.mu.Lock() ++ s.state.Jobs[parent.ID].NextAttemptAt = time.Now().Add(-time.Second) ++ s.mu.Unlock() ++ p2, err := s.ClaimJobForWorker(w, time.Minute) ++ if err != nil { ++ t.Fatal(err) ++ } ++ if p2 == nil || p2.ID != parent.ID || p2.Attempts != 2 { ++ t.Fatalf("second claim=%+v", p2) ++ } ++ if _, err := s.CompleteJobLease(p2.ID, w.ID, p2.LeaseToken, json.RawMessage(`{"ok":true}`), ""); err != nil { ++ t.Fatal(err) ++ } ++ ++ c, err := s.ClaimJobForWorker(w, time.Minute) ++ if err != nil { ++ t.Fatal(err) ++ } ++ if c == nil || c.ID != child.ID { ++ t.Fatalf("child claim after dependency=%+v", c) ++ } ++ ++ s.mu.Lock() ++ ws := s.workers[w.ID] ++ ws.LastHeartbeat = time.Now().Add(-2 * time.Second) ++ s.workers[w.ID] = ws ++ s.mu.Unlock() ++ workers := s.WorkersSnapshot() ++ if len(workers) != 1 || workers[0].Status != "stale" { ++ t.Fatalf("workers=%+v", workers) ++ } ++} ++ ++func TestDependencyFailureFailsBlockedChild(t *testing.T) { ++ s, err := New(t.TempDir()) ++ if err != nil { ++ t.Fatal(err) ++ } ++ defer s.Close() ++ p, err := s.EnqueueJobSpec(JobSpec{Type: "parent", Payload: map[string]any{}, ResourceClass: "cpu", MaxAttempts: 1}) ++ if err != nil { ++ t.Fatal(err) ++ } ++ c, err := s.EnqueueJobSpec(JobSpec{Type: "child", Payload: map[string]any{}, ResourceClass: "cpu", DependsOn: []string{p.ID}}) ++ if err != nil { ++ t.Fatal(err) ++ } ++ w := WorkerHeartbeat{ID: "cpu", ResourceClass: "cpu", Capabilities: []string{"cpu"}, MaxConcurrency: 1} ++ pj, _ := s.ClaimJobForWorker(w, time.Minute) ++ if _, err := s.CompleteJobLease(pj.ID, w.ID, pj.LeaseToken, nil, "permanent"); err != nil { ++ t.Fatal(err) ++ } ++ got, err := s.ClaimJobForWorker(w, time.Minute) ++ if err != nil { ++ t.Fatal(err) ++ } ++ if got != nil { ++ t.Fatalf("failed dependency child must not be claimable: %+v", got) ++ } ++ cj, ok := s.Job(c.ID) ++ if !ok || cj.Status != "failed" || !strings.Contains(cj.Error, "dependency") { ++ t.Fatalf("child=%+v", cj) ++ } ++} ++ ++func TestPruneTerminalJobsPreservesReferencedDependencies(t *testing.T) { ++ s, err := New(t.TempDir()) ++ if err != nil { ++ t.Fatal(err) ++ } ++ defer s.Close() ++ p, err := s.EnqueueJobSpec(JobSpec{Type: "parent", Payload: map[string]any{}, ResourceClass: "cpu"}) ++ if err != nil { ++ t.Fatal(err) ++ } ++ w := WorkerHeartbeat{ID: "cpu", ResourceClass: "cpu", Capabilities: []string{"cpu"}, MaxConcurrency: 1} ++ pj, _ := s.ClaimJobForWorker(w, time.Minute) ++ if _, err := s.CompleteJobLease(pj.ID, w.ID, pj.LeaseToken, nil, ""); err != nil { ++ t.Fatal(err) ++ } ++ _, err = s.EnqueueJobSpec(JobSpec{Type: "child", Payload: map[string]any{}, ResourceClass: "gpu", RequiredCapabilities: []string{"gpu"}, DependsOn: []string{p.ID}}) ++ if err != nil { ++ t.Fatal(err) ++ } ++ ++ s.mu.Lock() ++ s.state.Jobs[p.ID].FinishedAt = time.Now().Add(-48 * time.Hour) ++ s.state.Jobs[p.ID].UpdatedAt = s.state.Jobs[p.ID].FinishedAt ++ s.mu.Unlock() ++ n, err := s.PruneTerminalJobs(time.Hour, 100) ++ if err != nil { ++ t.Fatal(err) ++ } ++ if n != 0 { ++ t.Fatalf("pruned referenced dependency: %d", n) ++ } ++ if _, ok := s.Job(p.ID); !ok { ++ t.Fatal("referenced parent was removed") ++ } ++} ++ ++func TestExpiredLeaseCannotCompleteBeforeReclaim(t *testing.T) { ++ s, err := New(t.TempDir()) ++ if err != nil { ++ t.Fatal(err) ++ } ++ defer s.Close() ++ _, err = s.EnqueueJobSpec(JobSpec{Type: "cpu.task", Payload: map[string]any{"x": 1}, ResourceClass: "cpu", RequiredCapabilities: []string{"cpu"}}) ++ if err != nil { ++ t.Fatal(err) ++ } ++ w := WorkerHeartbeat{ID: "cpu-1", ResourceClass: "cpu", Capabilities: []string{"cpu"}, MaxConcurrency: 1} ++ j, err := s.ClaimJobForWorker(w, time.Minute) ++ if err != nil || j == nil { ++ t.Fatalf("claim=%+v err=%v", j, err) ++ } ++ s.mu.Lock() ++ s.state.Jobs[j.ID].LeaseUntil = time.Now().Add(-time.Second) ++ s.mu.Unlock() ++ if _, err := s.CompleteJobLease(j.ID, w.ID, j.LeaseToken, json.RawMessage(`{"ok":true}`), ""); err == nil || !strings.Contains(err.Error(), "expired") { ++ t.Fatalf("expired lease completion must be fenced, err=%v", err) ++ } ++ cur, ok := s.Job(j.ID) ++ if !ok || cur.Status != "claimed" { ++ t.Fatalf("late completion mutated job before scheduler recovery: %+v", cur) ++ } ++ // Any subsequent scheduler/claim pass normalizes the expired lease and may ++ // make the job retryable according to its durable attempt policy. ++ other := WorkerHeartbeat{ID: "cpu-2", ResourceClass: "cpu", Capabilities: []string{"cpu"}, MaxConcurrency: 1} ++ next, err := s.ClaimJobForWorker(other, time.Minute) ++ if err != nil { ++ t.Fatal(err) ++ } ++ if next != nil { ++ t.Fatalf("retry backoff should prevent immediate reclaim: %+v", next) ++ } ++ cur, _ = s.Job(j.ID) ++ if cur.Status != "retry_wait" || cur.Error != "worker lease expired" { ++ t.Fatalf("expired lease not normalized to retry_wait: %+v", cur) ++ } ++} ++ ++func TestOrchestratorRejectsDanglingDependencies(t *testing.T) { ++ s, err := New(t.TempDir()) ++ if err != nil { ++ t.Fatal(err) ++ } ++ defer s.Close() ++ if _, err := s.EnqueueJobSpec(JobSpec{Type: "child", Payload: map[string]any{}, DependsOn: []string{"job_missing"}}); err == nil || !strings.Contains(err.Error(), "not found") { ++ t.Fatalf("dangling dependency must fail closed, err=%v", err) ++ } ++ if s.PendingJobCount("") != 0 { ++ t.Fatalf("failed planning left durable work behind: %+v", s.OrchestratorStatus()) ++ } ++} ++ ++func TestApplyWaitJobSurvivesRestart(t *testing.T) { ++ dir := t.TempDir() ++ s, err := New(dir) ++ if err != nil { ++ t.Fatal(err) ++ } ++ j, err := s.EnqueueJobSpec(JobSpec{Type: "master.apply", Payload: map[string]any{"x": 1}, ResourceClass: "cpu", RequiredCapabilities: []string{"cpu"}, RequiresMasterApply: true}) ++ if err != nil { ++ t.Fatal(err) ++ } ++ w := WorkerHeartbeat{ID: "cpu-1", ResourceClass: "cpu", Capabilities: []string{"cpu"}, MaxConcurrency: 1} ++ claimed, err := s.ClaimJobForWorker(w, time.Minute) ++ if err != nil || claimed == nil || claimed.ID != j.ID { ++ t.Fatalf("claim=%+v err=%v", claimed, err) ++ } ++ result := json.RawMessage(`{"ok":true}`) ++ completed, err := s.CompleteJobLease(claimed.ID, w.ID, claimed.LeaseToken, result, "") ++ if err != nil || completed.Status != "apply_wait" { ++ t.Fatalf("complete=%+v err=%v", completed, err) ++ } ++ if err := s.Close(); err != nil { ++ t.Fatal(err) ++ } ++ ++ s2, err := New(dir) ++ if err != nil { ++ t.Fatal(err) ++ } ++ defer s2.Close() ++ recovered, ok := s2.Job(j.ID) ++ if !ok || recovered.Status != "apply_wait" { ++ t.Fatalf("durable apply_wait job not recovered: %+v", recovered) ++ } ++ if len(recovered.Payload) == 0 || string(recovered.Result) != string(result) { ++ t.Fatalf("recovered payload/result incomplete: %+v", recovered) ++ } ++ pending := s2.PendingMasterApplyJobs(10) ++ if len(pending) != 1 || pending[0].ID != j.ID { ++ t.Fatalf("recovered master apply queue=%+v", pending) ++ } ++} +diff --git a/platform/neuroforge/internal/store/store.go b/platform/neuroforge/internal/store/store.go +index 4cf7567..8445572 100644 +--- a/platform/neuroforge/internal/store/store.go ++++ b/platform/neuroforge/internal/store/store.go +@@ -45,6 +45,8 @@ type Store struct { + clusterLogMu sync.Mutex + clusterLog *ClusterLog + provenanceSourceIDs map[string]map[string]struct{} ++ workers map[string]core.WorkerState ++ synapseAdj map[string]map[string]*core.Synapse + } + + func New(dir string) (*Store, error) { +@@ -54,7 +56,7 @@ func New(dir string) (*Store, error) { + if err := os.MkdirAll(dir, 0700); err != nil { + return nil, err + } +- s := &Store{dir: dir, indexes: map[int]*vector.HNSW{}, diskIndexes: map[int]*vector.PQIndex{}, provenanceSourceIDs: map[string]map[string]struct{}{}} ++ s := &Store{dir: dir, indexes: map[int]*vector.HNSW{}, diskIndexes: map[int]*vector.PQIndex{}, provenanceSourceIDs: map[string]map[string]struct{}{}, workers: map[string]core.WorkerState{}, synapseAdj: map[string]map[string]*core.Synapse{}} + s.state = core.PersistedState{Config: core.DefaultConfig(), Memories: map[string]*core.Memory{}, Synapses: map[string]*core.Synapse{}, Jobs: map[string]*core.Job{}, Goals: map[string]*core.Goal{}, Sources: map[string]*core.KnowledgeSource{}, ResearchRuns: map[string]*core.ResearchRun{}} + _ = s.loadJSON(filepath.Join(dir, "state.json"), &s.state) + _ = s.loadJSON(filepath.Join(dir, "secrets.json"), &s.secrets) +@@ -111,6 +113,7 @@ func New(dir string) (*Store, error) { + if err := s.replayWAL(); err != nil { + return nil, err + } ++ s.rebuildSynapseAdjLocked() + applyNewDefaults(&s.state.Config) + if s.state.Cluster.Term < s.state.Config.Cluster.Term { + s.state.Cluster.Term = s.state.Config.Cluster.Term +@@ -235,6 +238,18 @@ func applyNewDefaults(c *core.Config) { + if c.Brain.TypeWeights == nil { + c.Brain.TypeWeights = d.Brain.TypeWeights + } ++ if c.Brain.GraphMaxHops == 0 { ++ c.Brain.GraphMaxHops = d.Brain.GraphMaxHops ++ } ++ if c.Brain.GraphHopDecay == 0 { ++ c.Brain.GraphHopDecay = d.Brain.GraphHopDecay ++ } ++ if c.Brain.GraphMaxExpansion == 0 { ++ c.Brain.GraphMaxExpansion = d.Brain.GraphMaxExpansion ++ } ++ if c.Brain.GraphMinEdgeWeight == 0 { ++ c.Brain.GraphMinEdgeWeight = d.Brain.GraphMinEdgeWeight ++ } + if c.Brain.LearningPolicy.MaxMemoryTextChars == 0 && c.Brain.LearningPolicy.DuplicateSimilarity == 0 && c.Brain.LearningPolicy.SemanticMinConfirmations == 0 { + c.Brain.LearningPolicy = d.Brain.LearningPolicy + } else { +@@ -484,6 +499,65 @@ func applyNewDefaults(c *core.Config) { + if c.Cluster.LogSegmentBytes == 0 { + c.Cluster.LogSegmentBytes = d.Cluster.LogSegmentBytes + } ++ if c.Worker.LeaseSeconds == 0 { ++ c.Worker.LeaseSeconds = d.Worker.LeaseSeconds ++ } ++ if c.Worker.HeartbeatSeconds == 0 && c.Worker.StaleAfterSeconds == 0 && c.Worker.DefaultMaxAttempts == 0 { ++ legacyLease := c.Worker.LeaseSeconds ++ c.Worker = d.Worker ++ if legacyLease > 0 { ++ c.Worker.LeaseSeconds = legacyLease ++ } ++ } else { ++ if c.Worker.HeartbeatSeconds == 0 { ++ c.Worker.HeartbeatSeconds = d.Worker.HeartbeatSeconds ++ } ++ if c.Worker.StaleAfterSeconds == 0 { ++ c.Worker.StaleAfterSeconds = d.Worker.StaleAfterSeconds ++ } ++ if c.Worker.DefaultMaxAttempts == 0 { ++ c.Worker.DefaultMaxAttempts = d.Worker.DefaultMaxAttempts ++ } ++ if c.Worker.RetryBackoffSeconds == 0 { ++ c.Worker.RetryBackoffSeconds = d.Worker.RetryBackoffSeconds ++ } ++ if c.Worker.MaxQueuedJobs == 0 { ++ c.Worker.MaxQueuedJobs = d.Worker.MaxQueuedJobs ++ } ++ if c.Worker.MasterApplyMaxAttempts == 0 { ++ c.Worker.MasterApplyMaxAttempts = d.Worker.MasterApplyMaxAttempts ++ } ++ if c.Worker.MasterApplyBackoffSeconds == 0 { ++ c.Worker.MasterApplyBackoffSeconds = d.Worker.MasterApplyBackoffSeconds ++ } ++ if c.Worker.JobRetentionHours == 0 { ++ c.Worker.JobRetentionHours = d.Worker.JobRetentionHours ++ } ++ if c.Worker.MaxTerminalJobs == 0 { ++ c.Worker.MaxTerminalJobs = d.Worker.MaxTerminalJobs ++ } ++ if c.Worker.GraphBackfillIntervalS == 0 { ++ c.Worker.GraphBackfillIntervalS = d.Worker.GraphBackfillIntervalS ++ } ++ if c.Worker.GraphBackfillBatchSize == 0 { ++ c.Worker.GraphBackfillBatchSize = d.Worker.GraphBackfillBatchSize ++ } ++ if c.Worker.GraphBackfillMaxQueued == 0 { ++ c.Worker.GraphBackfillMaxQueued = d.Worker.GraphBackfillMaxQueued ++ } ++ if c.Worker.GraphBackfillMinDegree == 0 { ++ c.Worker.GraphBackfillMinDegree = d.Worker.GraphBackfillMinDegree ++ } ++ if c.Worker.GraphCandidateMultiplier == 0 { ++ c.Worker.GraphCandidateMultiplier = d.Worker.GraphCandidateMultiplier ++ } ++ if c.Worker.GraphRetryAfterMinutes == 0 { ++ c.Worker.GraphRetryAfterMinutes = d.Worker.GraphRetryAfterMinutes ++ } ++ if c.Worker.DistributedInferenceWaitS == 0 { ++ c.Worker.DistributedInferenceWaitS = d.Worker.DistributedInferenceWaitS ++ } ++ } + } + + func migrateMemories(memories map[string]*core.Memory, localShard string) { +@@ -1010,51 +1084,7 @@ func (s *Store) searchVectorLocked(q []float32, k int, min float64, graphBonus f + if len(hits) > k { + hits = hits[:k] + } +- if graphBonus > 0 && len(hits) > 0 { +- base := map[string]float64{} +- for _, h := range hits { +- base[h.Memory.ID] = h.Score +- } +- for _, syn := range s.state.Synapses { +- var to string +- if _, ok := base[syn.A]; ok { +- to = syn.B +- } else if _, ok := base[syn.B]; ok { +- to = syn.A +- } else { +- continue +- } +- meta, ok := s.state.Memories[to] +- if !ok || !memorySearchable(meta) { +- continue +- } +- m, fullOK := s.fullMemoryForReadLocked(to) +- if !fullOK || len(m.Vector) != len(q) { +- continue +- } +- bonus := graphBonus * syn.Weight +- found := false +- for i := range hits { +- if hits[i].Memory.ID == to { +- hits[i].GraphBoost += bonus +- hits[i].Score += bonus +- found = true +- break +- } +- } +- if !found && len(hits) < k { +- sim := vector.Cosine(q, m.Vector) +- if sim >= min { +- baseScore := sim * 0.5 +- hits = append(hits, SearchHit{Memory: cloneMemory(m), Similarity: sim, BaseScore: baseScore, GraphBoost: bonus, Score: bonus + baseScore, TypeWeight: 1, SalienceFactor: 1, ConfidenceFactor: 1, CandidateSource: "synapse"}) +- } +- } +- } +- sort.Slice(hits, func(i, j int) bool { return hits[i].Score > hits[j].Score }) +- if len(hits) > k { +- hits = hits[:k] +- } +- } ++ hits = s.graphExpandLocked(q, hits, k, min, graphBonus) + return hits + } + +@@ -1066,32 +1096,7 @@ func edgeKey(a, b string) string { + } + + func (s *Store) Reinforce(a, b string, similarity, delta, decayPerDay, maxWeight float64) error { +- if a == "" || b == "" || a == b { +- return nil +- } +- s.mu.Lock() +- defer s.mu.Unlock() +- if s.state.Memories[a] == nil || s.state.Memories[b] == nil { +- return nil +- } +- key := edgeKey(a, b) +- now := time.Now().UTC() +- syn, ok := s.state.Synapses[key] +- if !ok { +- syn = &core.Synapse{A: a, B: b, Similarity: similarity, LastUpdated: now} +- s.state.Synapses[key] = syn +- } +- days := now.Sub(syn.LastUpdated).Hours() / 24 +- if days > 0 && decayPerDay > 0 { +- syn.Weight *= pow(1-decayPerDay, days) +- } +- syn.Weight = vector.Clamp(syn.Weight+delta, -maxWeight, maxWeight) +- if similarity > syn.Similarity { +- syn.Similarity = similarity +- } +- syn.Activations++ +- syn.LastUpdated = now +- return s.commitLocked("synapse.upsert", *syn) ++ return s.ReinforceRelation(a, b, "association", similarity, delta, decayPerDay, maxWeight) + } + + func pow(base, exp float64) float64 { +@@ -1113,6 +1118,7 @@ func (s *Store) DecayAndPruneSynapses(decayPerDay, pruneBelow float64) (int, err + syn.LastUpdated = now + } + if pruneBelow > 0 && math.Abs(syn.Weight) < pruneBelow { ++ s.unindexSynapseLocked(syn) + delete(s.state.Synapses, key) + pruned++ + } +@@ -1348,66 +1354,6 @@ func (s *Store) RecentUsage(limit int) []core.UsageEvent { + return out + } + +-func (s *Store) EnqueueJob(kind string, payload any) (*core.Job, error) { +- b, err := json.Marshal(payload) +- if err != nil { +- return nil, err +- } +- s.mu.Lock() +- defer s.mu.Unlock() +- now := time.Now().UTC() +- j := &core.Job{ID: NewID("job"), Type: kind, Payload: b, Status: "queued", CreatedAt: now, UpdatedAt: now} +- s.state.Jobs[j.ID] = j +- return j, s.commitLocked("job.upsert", *j) +-} +- +-func (s *Store) ClaimJob(worker string, lease time.Duration) (*core.Job, error) { +- s.mu.Lock() +- defer s.mu.Unlock() +- now := time.Now().UTC() +- var chosen *core.Job +- for _, j := range s.state.Jobs { +- if j.Status == "claimed" && !j.LeaseUntil.IsZero() && now.After(j.LeaseUntil) { +- j.Status = "queued" +- j.ClaimedBy = "" +- } +- if j.Status == "queued" && (chosen == nil || j.CreatedAt.Before(chosen.CreatedAt)) { +- chosen = j +- } +- } +- if chosen == nil { +- return nil, nil +- } +- chosen.Status = "claimed" +- chosen.ClaimedBy = worker +- chosen.LeaseUntil = now.Add(lease) +- chosen.UpdatedAt = now +- cp := *chosen +- return &cp, s.commitLocked("job.upsert", cp) +-} +- +-func (s *Store) CompleteJob(id, worker string, result json.RawMessage, jobErr string) (*core.Job, error) { +- s.mu.Lock() +- defer s.mu.Unlock() +- j, ok := s.state.Jobs[id] +- if !ok { +- return nil, errors.New("job not found") +- } +- if j.ClaimedBy != worker { +- return nil, errors.New("job claimed by another worker") +- } +- j.Result = result +- j.Error = jobErr +- j.UpdatedAt = time.Now().UTC() +- if jobErr != "" { +- j.Status = "failed" +- } else { +- j.Status = "done" +- } +- cp := *j +- return &cp, s.commitLocked("job.upsert", cp) +-} +- + func (s *Store) DeleteMemory(id string) error { + s.mu.Lock() + defer s.mu.Unlock() +@@ -1421,6 +1367,7 @@ func (s *Store) DeleteMemory(id string) error { + } + for k, x := range s.state.Synapses { + if x.A == id || x.B == id { ++ s.unindexSynapseLocked(x) + delete(s.state.Synapses, k) + } + } +@@ -1459,6 +1406,9 @@ func (s *Store) validateConfigLocked(c core.Config) error { + if c.Brain.MinSimilarity < -1 || c.Brain.MinSimilarity > 1 { + return errors.New("brain.min_similarity must be -1..1") + } ++ if c.Brain.GraphMaxHops < 1 || c.Brain.GraphMaxHops > 6 || c.Brain.GraphHopDecay <= 0 || c.Brain.GraphHopDecay > 1 || c.Brain.GraphMaxExpansion < 1 || c.Brain.GraphMaxExpansion > 5000 || c.Brain.GraphMinEdgeWeight < 0 || c.Brain.GraphMinEdgeWeight > c.Brain.MaxSynapseWeight { ++ return errors.New("invalid brain graph traversal configuration") ++ } + if c.Brain.Index.Enabled { + mode := indexMode(c) + if c.Brain.Index.Mode != "" && c.Brain.Index.Mode != "hnsw" && c.Brain.Index.Mode != "hybrid" && c.Brain.Index.Mode != "disk-pq" { +@@ -1656,6 +1606,18 @@ func (s *Store) validateConfigLocked(c core.Config) error { + return fmt.Errorf("cluster.term %d cannot be lower than persisted term %d", c.Cluster.Term, s.state.Cluster.Term) + } + } ++ if c.Worker.LeaseSeconds < 10 || c.Worker.LeaseSeconds > 3600 || c.Worker.HeartbeatSeconds < 2 || c.Worker.HeartbeatSeconds >= c.Worker.LeaseSeconds || c.Worker.StaleAfterSeconds < c.Worker.HeartbeatSeconds || c.Worker.StaleAfterSeconds > 7200 { ++ return errors.New("invalid worker lease/heartbeat/stale timing") ++ } ++ if c.Worker.DefaultMaxAttempts < 1 || c.Worker.DefaultMaxAttempts > 20 || c.Worker.RetryBackoffSeconds < 1 || c.Worker.RetryBackoffSeconds > 3600 || c.Worker.MaxQueuedJobs < 16 || c.Worker.MaxQueuedJobs > 1000000 || c.Worker.MasterApplyMaxAttempts < 1 || c.Worker.MasterApplyMaxAttempts > 20 || c.Worker.MasterApplyBackoffSeconds < 1 || c.Worker.MasterApplyBackoffSeconds > 3600 || c.Worker.JobRetentionHours < 1 || c.Worker.JobRetentionHours > 8760 || c.Worker.MaxTerminalJobs < 100 || c.Worker.MaxTerminalJobs > 1000000 { ++ return errors.New("invalid worker retry/queue/retention configuration") ++ } ++ if c.Worker.GraphBackfillIntervalS < 2 || c.Worker.GraphBackfillIntervalS > 3600 || c.Worker.GraphBackfillBatchSize < 1 || c.Worker.GraphBackfillBatchSize > 4096 || c.Worker.GraphBackfillMaxQueued < 1 || c.Worker.GraphBackfillMaxQueued > c.Worker.MaxQueuedJobs || c.Worker.GraphBackfillMinDegree < 1 || c.Worker.GraphBackfillMinDegree > 100 || c.Worker.GraphCandidateMultiplier < 2 || c.Worker.GraphCandidateMultiplier > 64 || c.Worker.GraphRetryAfterMinutes < 1 || c.Worker.GraphRetryAfterMinutes > 43200 { ++ return errors.New("invalid worker graph backfill configuration") ++ } ++ if c.Worker.DistributedInferenceWaitS < 5 || c.Worker.DistributedInferenceWaitS > 3600 { ++ return errors.New("worker.distributed_inference_wait_seconds must be 5..3600") ++ } + if c.OpenAI.DailyBudgetUSD < 0 || c.OpenAI.MonthlyBudgetUSD < 0 { + return errors.New("budgets must be >= 0") + } +diff --git a/platform/neuroforge/internal/store/wal.go b/platform/neuroforge/internal/store/wal.go +index 5e67496..afab3bc 100644 +--- a/platform/neuroforge/internal/store/wal.go ++++ b/platform/neuroforge/internal/store/wal.go +@@ -209,6 +209,7 @@ func (s *Store) applyWALEvent(ev walEvent) error { + return err + } + s.state.Synapses[edgeKey(syn.A, syn.B)] = &syn ++ s.indexSynapseLocked(&syn) + case "synapse.replace": + var items []core.Synapse + if err := json.Unmarshal(ev.Data, &items); err != nil { +@@ -219,6 +220,7 @@ func (s *Store) applyWALEvent(ev walEvent) error { + x := items[i] + s.state.Synapses[edgeKey(x.A, x.B)] = &x + } ++ s.rebuildSynapseAdjLocked() + case "usage.add": + var x core.UsageEvent + if err := json.Unmarshal(ev.Data, &x); err != nil { +@@ -231,6 +233,14 @@ func (s *Store) applyWALEvent(ev walEvent) error { + return err + } + s.state.Jobs[x.ID] = &x ++ case "job.delete": ++ var ids []string ++ if err := json.Unmarshal(ev.Data, &ids); err != nil { ++ return err ++ } ++ for _, id := range ids { ++ delete(s.state.Jobs, id) ++ } + case "maintenance.set": + return json.Unmarshal(ev.Data, &s.state.Maintenance) + case "goal.upsert": +diff --git a/platform/neuroforge/openapi.yaml b/platform/neuroforge/openapi.yaml +index 031fc51..afbe818 100644 +--- a/platform/neuroforge/openapi.yaml ++++ b/platform/neuroforge/openapi.yaml +@@ -16,6 +16,10 @@ components: + WorkerKey: + type: http + scheme: bearer ++ ControlReadKey: ++ type: http ++ scheme: bearer ++ description: Scoped read token used by the Control Center for graph/orchestrator status. + AdminToken: + type: apiKey + in: header +@@ -1324,13 +1328,46 @@ paths: + responses: + '200': + description: Open truth-key conflicts ++ /api/v1/integrations/graph/status: ++ get: ++ security: ++ - ControlReadKey: [] ++ - AppKey: [] ++ - AdminToken: [] ++ description: Structural knowledge-graph health and graph/backfill configuration. ++ responses: ++ '200': ++ description: Graph statistics including linked/isolated/multi-linked nodes and connected components. ++ /api/v1/integrations/orchestrator/status: ++ get: ++ security: ++ - ControlReadKey: [] ++ - AppKey: [] ++ - AdminToken: [] ++ description: Read-only master/subagent scheduler status, workers, queues and offload settings. ++ responses: ++ '200': {description: Orchestrator status} ++ /api/v1/worker/register: ++ post: ++ security: ++ - WorkerKey: [] ++ description: Register or refresh a capability-bearing CPU/GPU subagent. ++ responses: ++ '200': {description: Worker state} ++ /api/v1/worker/heartbeat: ++ post: ++ security: ++ - WorkerKey: [] ++ description: Refresh worker liveness and renew matching active leases using lease-token fencing. ++ responses: ++ '200': {description: Worker state} + /api/v1/worker/claim: + post: + security: + - WorkerKey: [] + responses: + '200': +- description: Claimed CPU job ++ description: Highest-priority compatible leased job for this worker resource/capability set. + '204': + description: No job available + /api/v1/worker/complete: +@@ -1339,7 +1376,9 @@ paths: + - WorkerKey: [] + responses: + '200': +- description: Job completion accepted ++ description: Lease-fenced result accepted. Master-state mutations may enter durable apply_wait before done. ++ '409': ++ description: Stale/expired lease, wrong worker, or job no longer claimable. + /internal/v1/cluster/request-vote: + post: + security: +@@ -1554,6 +1593,54 @@ paths: + responses: + '200': + description: Secrets updated ++ /admin/api/graph/status: ++ get: ++ security: [{AdminToken: []}] ++ responses: ++ '200': {description: Graph and orchestrator status with effective graph configuration} ++ /admin/api/graph/backfill: ++ post: ++ security: [{AdminToken: []}] ++ responses: ++ '202': {description: Bounded graph-backfill planning cycle accepted} ++ /admin/api/orchestrator/status: ++ get: ++ security: [{AdminToken: []}] ++ responses: ++ '200': {description: Master/subagent scheduler status} ++ /admin/api/orchestrator/jobs: ++ get: ++ security: [{AdminToken: []}] ++ parameters: ++ - {name: limit, in: query, schema: {type: integer, minimum: 1, maximum: 5000}} ++ - {name: status, in: query, schema: {type: string}} ++ - {name: type, in: query, schema: {type: string}} ++ responses: ++ '200': {description: Bounded job list without large payload/result bodies} ++ /admin/api/orchestrator/jobs/{id}/retry: ++ post: ++ security: [{AdminToken: []}] ++ parameters: ++ - {name: id, in: path, required: true, schema: {type: string}} ++ responses: ++ '200': {description: Failed/canceled job reset to queued} ++ '409': {description: Job is not retryable from its current state} ++ /admin/api/orchestrator/jobs/{id}/cancel: ++ post: ++ security: [{AdminToken: []}] ++ parameters: ++ - {name: id, in: path, required: true, schema: {type: string}} ++ requestBody: ++ required: false ++ content: ++ application/json: ++ schema: ++ type: object ++ properties: ++ reason: {type: string} ++ responses: ++ '200': {description: Non-terminal job canceled and any active lease fenced} ++ '404': {description: Job not found} + /admin/api/provider-health: + post: + security: +diff --git a/scripts/backup-data.sh b/scripts/backup-data.sh +index 8775933..9b6f5a5 100755 +--- a/scripts/backup-data.sh ++++ b/scripts/backup-data.sh +@@ -10,11 +10,11 @@ dest="$DEST_ROOT/$stamp" + mkdir -p "$dest" + + ./scripts/preflight.sh +-restart() { docker compose --env-file "$ENV_FILE" up -d knowledge neuroforge neuroforge-worker agent >/dev/null 2>&1 || true; } ++restart() { docker compose --env-file "$ENV_FILE" up -d knowledge neuroforge neuroforge-worker-cpu neuroforge-worker-gpu agent >/dev/null 2>&1 || true; } + trap restart EXIT INT TERM + + echo "backup: entering short maintenance stop" +-docker compose --env-file "$ENV_FILE" stop neuroforge-worker agent neuroforge knowledge ++docker compose --env-file "$ENV_FILE" stop neuroforge-worker-cpu neuroforge-worker-gpu agent neuroforge knowledge + docker compose --env-file "$ENV_FILE" create neuroforge agent >/dev/null + mkdir -p "$dest/neuroforge-data" "$dest/agent-data" "$dest/knowledge" "$dest/staging" "$dest/knowledge-backups" + docker compose --env-file "$ENV_FILE" cp neuroforge:/app/data/. "$dest/neuroforge-data/" +diff --git a/scripts/preflight.sh b/scripts/preflight.sh +index 54f0c93..4dae0ef 100755 +--- a/scripts/preflight.sh ++++ b/scripts/preflight.sh +@@ -16,6 +16,12 @@ if grep -Eq '^[[:space:]]+env_file:' docker-compose.yml; then fail "production d + for image in neuroforge neuroforge-worker agent agent-data-init knowledge control; do + grep -Fq "git.send.nrw/sendnrw/glpi-neuroforge-mega-${image}:\${IMAGE_TAG:" docker-compose.yml || fail "registry image mapping missing for ${image}" + done ++for service in neuroforge-worker-cpu neuroforge-worker-gpu; do ++ grep -Eq "^[[:space:]]{2}${service}:" docker-compose.yml || fail "production compose is missing ${service}" ++done ++[ -f docker-compose.subagent.yml ] || fail "docker-compose.subagent.yml is missing" ++if grep -Eq '^[[:space:]]+build:|^[[:space:]]+env_file:' docker-compose.subagent.yml; then fail "remote subagent compose contains build/env_file"; fi ++grep -Fq 'glpi-neuroforge-mega-neuroforge-worker:${IMAGE_TAG:' docker-compose.subagent.yml || fail "remote subagent image is not pinned by IMAGE_TAG" + + if [ "$STATIC_ONLY" = true ]; then + if grep -Eq '^IMAGE_TAG[[:space:]]*=[[:space:]]*latest([[:space:]]|$)' .env.example; then fail ".env.example sets IMAGE_TAG=latest"; fi +diff --git a/scripts/release-gate.sh b/scripts/release-gate.sh +index e2c2cf9..7fb9b1c 100755 +--- a/scripts/release-gate.sh ++++ b/scripts/release-gate.sh +@@ -5,6 +5,7 @@ cd "$ROOT" + ./scripts/preflight.sh --static + ./scripts/check-compose-env.py + ./scripts/secret-scan.sh ++make engineering-graph-check + for module in platform/neuroforge services/agent services/knowledge services/control; do + echo "release-gate: $module: test" + (cd "$module" && go test ./...) +diff --git a/scripts/research-up.sh b/scripts/research-up.sh +index 0403c8a..02843af 100755 +--- a/scripts/research-up.sh ++++ b/scripts/research-up.sh +@@ -15,5 +15,5 @@ export SEARXNG_SECRET=$secret + cd "$ROOT" + NEUROFORGE_RESEARCH_ENABLED=true \ + NEUROFORGE_SEARXNG_ENABLED=true \ +-docker compose --profile research up -d ollama knowledge searxng neuroforge neuroforge-worker ++docker compose --profile research up -d ollama knowledge searxng neuroforge neuroforge-worker-cpu neuroforge-worker-gpu + printf '%s\n' 'SearXNG + NeuroForge research are running. Autonomy remains controlled by NEUROFORGE_AUTONOMY_ENABLED.' +diff --git a/scripts/restore-data.sh b/scripts/restore-data.sh +index 8038c6e..da1b3cb 100755 +--- a/scripts/restore-data.sh ++++ b/scripts/restore-data.sh +@@ -9,10 +9,10 @@ HELPER_IMAGE=${BACKUP_HELPER_IMAGE:-busybox:1.36} + ./scripts/preflight.sh + getv() { sed -n "s/^$1=//p" "$ENV_FILE" | tail -n1 | tr -d '\r'; } + +-restart() { docker compose --env-file "$ENV_FILE" up -d knowledge neuroforge neuroforge-worker agent >/dev/null 2>&1 || true; } ++restart() { docker compose --env-file "$ENV_FILE" up -d knowledge neuroforge neuroforge-worker-cpu neuroforge-worker-gpu agent >/dev/null 2>&1 || true; } + trap restart EXIT INT TERM + +-docker compose --env-file "$ENV_FILE" stop neuroforge-worker agent neuroforge knowledge ++docker compose --env-file "$ENV_FILE" stop neuroforge-worker-cpu neuroforge-worker-gpu agent neuroforge knowledge + docker compose --env-file "$ENV_FILE" create neuroforge agent >/dev/null + volume_for() { + service=$1; destination=$2 +diff --git a/services/control/engineering-graph.json b/services/control/engineering-graph.json +index 858e6cd..3665df0 100644 +--- a/services/control/engineering-graph.json ++++ b/services/control/engineering-graph.json +@@ -82,6 +82,16 @@ + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/distributed.go", ++ "kind": "file", ++ "label": "distributed.go", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "path": "platform/neuroforge/internal/brain/distributed.go" ++ } ++ }, + { + "id": "file:platform/neuroforge/internal/brain/goal_progress.go", + "kind": "file", +@@ -92,6 +102,16 @@ + "path": "platform/neuroforge/internal/brain/goal_progress.go" + } + }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/orchestrator.go", ++ "kind": "file", ++ "label": "orchestrator.go", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "path": "platform/neuroforge/internal/brain/orchestrator.go" ++ } ++ }, + { + "id": "file:platform/neuroforge/internal/brain/policy.go", + "kind": "file", +@@ -102,6 +122,16 @@ + "path": "platform/neuroforge/internal/brain/policy.go" + } + }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/research_quality.go", ++ "kind": "file", ++ "label": "research_quality.go", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "path": "platform/neuroforge/internal/brain/research_quality.go" ++ } ++ }, + { + "id": "file:platform/neuroforge/internal/brain/research_trace.go", + "kind": "file", +@@ -122,6 +152,16 @@ + "path": "platform/neuroforge/internal/brain/staging.go" + } + }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "kind": "file", ++ "label": "staging_quality.go", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "path": "platform/neuroforge/internal/brain/staging_quality.go" ++ } ++ }, + { + "id": "file:platform/neuroforge/internal/brain/v3.go", + "kind": "file", +@@ -372,6 +412,16 @@ + "path": "platform/neuroforge/internal/store/diskann.go" + } + }, ++ { ++ "id": "file:platform/neuroforge/internal/store/graph_index.go", ++ "kind": "file", ++ "label": "graph_index.go", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "path": "platform/neuroforge/internal/store/graph_index.go" ++ } ++ }, + { + "id": "file:platform/neuroforge/internal/store/index_segments.go", + "kind": "file", +@@ -422,6 +472,16 @@ + "path": "platform/neuroforge/internal/store/observability.go" + } + }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "kind": "file", ++ "label": "orchestrator.go", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "path": "platform/neuroforge/internal/store/orchestrator.go" ++ } ++ }, + { + "id": "file:platform/neuroforge/internal/store/pagecache.go", + "kind": "file", +@@ -9306,7 +9366,7 @@ + "community": "mega-control", + "meta": { + "exported": false, +- "line": 178, ++ "line": 188, + "path": "services/control/main.go" + } + }, +@@ -9402,7 +9462,7 @@ + "community": "mega-control", + "meta": { + "exported": false, +- "line": 201, ++ "line": 211, + "path": "services/control/main.go" + } + }, +@@ -9414,7 +9474,7 @@ + "community": "mega-control", + "meta": { + "exported": false, +- "line": 244, ++ "line": 254, + "path": "services/control/main.go" + } + }, +@@ -9438,7 +9498,7 @@ + "community": "mega-control", + "meta": { + "exported": false, +- "line": 211, ++ "line": 221, + "path": "services/control/main.go" + } + }, +@@ -9490,6 +9550,30 @@ + "path": "services/control/graph.go" + } + }, ++ { ++ "id": "function:mega-control:server.handleNeuroForgeGraphStatus", ++ "kind": "function", ++ "label": "server.handleNeuroForgeGraphStatus", ++ "group": "engineering", ++ "community": "mega-control", ++ "meta": { ++ "exported": false, ++ "line": 153, ++ "path": "services/control/main.go" ++ } ++ }, ++ { ++ "id": "function:mega-control:server.handleNeuroForgeOrchestrator", ++ "kind": "function", ++ "label": "server.handleNeuroForgeOrchestrator", ++ "group": "engineering", ++ "community": "mega-control", ++ "meta": { ++ "exported": false, ++ "line": 149, ++ "path": "services/control/main.go" ++ } ++ }, + { + "id": "function:mega-control:server.handleResearchGraph", + "kind": "function", +@@ -9522,7 +9606,7 @@ + "community": "mega-control", + "meta": { + "exported": false, +- "line": 215, ++ "line": 225, + "path": "services/control/main.go" + } + }, +@@ -9558,7 +9642,7 @@ + "community": "mega-control", + "meta": { + "exported": false, +- "line": 232, ++ "line": 242, + "path": "services/control/main.go" + } + }, +@@ -9594,7 +9678,7 @@ + "community": "mega-control", + "meta": { + "exported": false, +- "line": 147, ++ "line": 157, + "path": "services/control/main.go" + } + }, +@@ -9606,7 +9690,7 @@ + "community": "mega-control", + "meta": { + "exported": false, +- "line": 279, ++ "line": 289, + "path": "services/control/main.go" + } + }, +@@ -9670,6 +9754,18 @@ + "path": "platform/neuroforge/cmd/server/main.go" + } + }, ++ { ++ "id": "function:neuroforge/cmd/server:envFloat", ++ "kind": "function", ++ "label": "envFloat", ++ "group": "engineering", ++ "community": "neuroforge/cmd/server", ++ "meta": { ++ "exported": false, ++ "line": 50, ++ "path": "platform/neuroforge/cmd/server/main.go" ++ } ++ }, + { + "id": "function:neuroforge/cmd/server:envInt", + "kind": "function", +@@ -9682,6 +9778,18 @@ + "path": "platform/neuroforge/cmd/server/main.go" + } + }, ++ { ++ "id": "function:neuroforge/cmd/server:firstNonEmptyMain", ++ "kind": "function", ++ "label": "firstNonEmptyMain", ++ "group": "engineering", ++ "community": "neuroforge/cmd/server", ++ "meta": { ++ "exported": false, ++ "line": 514, ++ "path": "platform/neuroforge/cmd/server/main.go" ++ } ++ }, + { + "id": "function:neuroforge/cmd/server:main", + "kind": "function", +@@ -9690,7 +9798,7 @@ + "community": "neuroforge/cmd/server", + "meta": { + "exported": false, +- "line": 92, ++ "line": 104, + "path": "platform/neuroforge/cmd/server/main.go" + } + }, +@@ -9702,7 +9810,7 @@ + "community": "neuroforge/cmd/server", + "meta": { + "exported": false, +- "line": 85, ++ "line": 97, + "path": "platform/neuroforge/cmd/server/main.go" + } + }, +@@ -9714,7 +9822,7 @@ + "community": "neuroforge/cmd/server", + "meta": { + "exported": false, +- "line": 99, ++ "line": 111, + "path": "platform/neuroforge/cmd/server/main.go" + } + }, +@@ -9726,7 +9834,7 @@ + "community": "neuroforge/cmd/server", + "meta": { + "exported": false, +- "line": 50, ++ "line": 62, + "path": "platform/neuroforge/cmd/server/main.go" + } + }, +@@ -9738,7 +9846,7 @@ + "community": "neuroforge/cmd/server", + "meta": { + "exported": false, +- "line": 65, ++ "line": 77, + "path": "platform/neuroforge/cmd/server/main.go" + } + }, +@@ -9750,7 +9858,7 @@ + "community": "neuroforge/cmd/worker", + "meta": { + "exported": false, +- "line": 76, ++ "line": 255, + "path": "platform/neuroforge/cmd/worker/main.go" + } + }, +@@ -9762,7 +9870,79 @@ + "community": "neuroforge/cmd/worker", + "meta": { + "exported": false, +- "line": 123, ++ "line": 416, ++ "path": "platform/neuroforge/cmd/worker/main.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:csv", ++ "kind": "function", ++ "label": "csv", ++ "group": "engineering", ++ "community": "neuroforge/cmd/worker", ++ "meta": { ++ "exported": false, ++ "line": 454, ++ "path": "platform/neuroforge/cmd/worker/main.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:envDuration", ++ "kind": "function", ++ "label": "envDuration", ++ "group": "engineering", ++ "community": "neuroforge/cmd/worker", ++ "meta": { ++ "exported": false, ++ "line": 507, ++ "path": "platform/neuroforge/cmd/worker/main.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:envInt", ++ "kind": "function", ++ "label": "envInt", ++ "group": "engineering", ++ "community": "neuroforge/cmd/worker", ++ "meta": { ++ "exported": false, ++ "line": 495, ++ "path": "platform/neuroforge/cmd/worker/main.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:firstNonEmpty", ++ "kind": "function", ++ "label": "firstNonEmpty", ++ "group": "engineering", ++ "community": "neuroforge/cmd/worker", ++ "meta": { ++ "exported": false, ++ "line": 486, ++ "path": "platform/neuroforge/cmd/worker/main.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:hasCap", ++ "kind": "function", ++ "label": "hasCap", ++ "group": "engineering", ++ "community": "neuroforge/cmd/worker", ++ "meta": { ++ "exported": false, ++ "line": 468, ++ "path": "platform/neuroforge/cmd/worker/main.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:heartbeatLoop", ++ "kind": "function", ++ "label": "heartbeatLoop", ++ "group": "engineering", ++ "community": "neuroforge/cmd/worker", ++ "meta": { ++ "exported": false, ++ "line": 239, + "path": "platform/neuroforge/cmd/worker/main.go" + } + }, +@@ -9774,7 +9954,55 @@ + "community": "neuroforge/cmd/worker", + "meta": { + "exported": false, +- "line": 69, ++ "line": 446, ++ "path": "platform/neuroforge/cmd/worker/main.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:leaseSet.add", ++ "kind": "function", ++ "label": "leaseSet.add", ++ "group": "engineering", ++ "community": "neuroforge/cmd/worker", ++ "meta": { ++ "exported": false, ++ "line": 100, ++ "path": "platform/neuroforge/cmd/worker/main.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:leaseSet.del", ++ "kind": "function", ++ "label": "leaseSet.del", ++ "group": "engineering", ++ "community": "neuroforge/cmd/worker", ++ "meta": { ++ "exported": false, ++ "line": 105, ++ "path": "platform/neuroforge/cmd/worker/main.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:leaseSet.snapshot", ++ "kind": "function", ++ "label": "leaseSet.snapshot", ++ "group": "engineering", ++ "community": "neuroforge/cmd/worker", ++ "meta": { ++ "exported": false, ++ "line": 110, ++ "path": "platform/neuroforge/cmd/worker/main.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:lowerDefault", ++ "kind": "function", ++ "label": "lowerDefault", ++ "group": "engineering", ++ "community": "neuroforge/cmd/worker", ++ "meta": { ++ "exported": false, ++ "line": 478, + "path": "platform/neuroforge/cmd/worker/main.go" + } + }, +@@ -9786,7 +10014,79 @@ + "community": "neuroforge/cmd/worker", + "meta": { + "exported": false, +- "line": 39, ++ "line": 120, ++ "path": "platform/neuroforge/cmd/worker/main.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:ollamaChat", ++ "kind": "function", ++ "label": "ollamaChat", ++ "group": "engineering", ++ "community": "neuroforge/cmd/worker", ++ "meta": { ++ "exported": false, ++ "line": 354, ++ "path": "platform/neuroforge/cmd/worker/main.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:ollamaEmbed", ++ "kind": "function", ++ "label": "ollamaEmbed", ++ "group": "engineering", ++ "community": "neuroforge/cmd/worker", ++ "meta": { ++ "exported": false, ++ "line": 332, ++ "path": "platform/neuroforge/cmd/worker/main.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:pollLoop", ++ "kind": "function", ++ "label": "pollLoop", ++ "group": "engineering", ++ "community": "neuroforge/cmd/worker", ++ "meta": { ++ "exported": false, ++ "line": 189, ++ "path": "platform/neuroforge/cmd/worker/main.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:postJSON", ++ "kind": "function", ++ "label": "postJSON", ++ "group": "engineering", ++ "community": "neuroforge/cmd/worker", ++ "meta": { ++ "exported": false, ++ "line": 421, ++ "path": "platform/neuroforge/cmd/worker/main.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:postOllama", ++ "kind": "function", ++ "label": "postOllama", ++ "group": "engineering", ++ "community": "neuroforge/cmd/worker", ++ "meta": { ++ "exported": false, ++ "line": 394, ++ "path": "platform/neuroforge/cmd/worker/main.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:register", ++ "kind": "function", ++ "label": "register", ++ "group": "engineering", ++ "community": "neuroforge/cmd/worker", ++ "meta": { ++ "exported": false, ++ "line": 235, + "path": "platform/neuroforge/cmd/worker/main.go" + } + }, +@@ -9798,10 +10098,46 @@ + "community": "neuroforge/cmd/worker", + "meta": { + "exported": false, +- "line": 99, ++ "line": 279, + "path": "platform/neuroforge/cmd/worker/main.go" + } + }, ++ { ++ "id": "function:neuroforge/cmd/worker:waitOrDone", ++ "kind": "function", ++ "label": "waitOrDone", ++ "group": "engineering", ++ "community": "neuroforge/cmd/worker", ++ "meta": { ++ "exported": false, ++ "line": 178, ++ "path": "platform/neuroforge/cmd/worker/main.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:workerBody", ++ "kind": "function", ++ "label": "workerBody", ++ "group": "engineering", ++ "community": "neuroforge/cmd/worker", ++ "meta": { ++ "exported": false, ++ "line": 227, ++ "path": "platform/neuroforge/cmd/worker/main.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.ApplyAndFinalizeJob", ++ "kind": "function", ++ "label": "Engine.ApplyAndFinalizeJob", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": true, ++ "line": 59, ++ "path": "platform/neuroforge/internal/brain/orchestrator.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.ApplyJobResult", + "kind": "function", +@@ -9810,10 +10146,22 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": true, +- "line": 1008, ++ "line": 1081, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.ApplyPendingJobResults", ++ "kind": "function", ++ "label": "Engine.ApplyPendingJobResults", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": true, ++ "line": 76, ++ "path": "platform/neuroforge/internal/brain/orchestrator.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.Chat", + "kind": "function", +@@ -9822,7 +10170,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": true, +- "line": 203, ++ "line": 239, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, +@@ -9834,7 +10182,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": true, +- "line": 232, ++ "line": 487, + "path": "platform/neuroforge/internal/brain/staging.go" + } + }, +@@ -9918,7 +10266,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": true, +- "line": 29, ++ "line": 48, + "path": "platform/neuroforge/internal/brain/staging.go" + } + }, +@@ -9930,7 +10278,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": true, +- "line": 749, ++ "line": 785, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, +@@ -9942,7 +10290,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": true, +- "line": 533, ++ "line": 569, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, +@@ -9954,7 +10302,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": true, +- "line": 457, ++ "line": 493, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, +@@ -9990,10 +10338,22 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": true, +- "line": 378, ++ "line": 414, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.PlanGraphBackfill", ++ "kind": "function", ++ "label": "Engine.PlanGraphBackfill", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": true, ++ "line": 14, ++ "path": "platform/neuroforge/internal/brain/orchestrator.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.RebalanceShards", + "kind": "function", +@@ -10014,7 +10374,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": true, +- "line": 100, ++ "line": 159, + "path": "platform/neuroforge/internal/brain/goal_progress.go" + } + }, +@@ -10038,7 +10398,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": true, +- "line": 298, ++ "line": 310, + "path": "platform/neuroforge/internal/brain/v8.go" + } + }, +@@ -10074,10 +10434,22 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": true, +- "line": 935, ++ "line": 971, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.RunOrchestrator", ++ "kind": "function", ++ "label": "Engine.RunOrchestrator", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": true, ++ "line": 88, ++ "path": "platform/neuroforge/internal/brain/orchestrator.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.RunV3Maintenance", + "kind": "function", +@@ -10134,7 +10506,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": true, +- "line": 510, ++ "line": 546, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, +@@ -10146,7 +10518,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": true, +- "line": 1029, ++ "line": 1135, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, +@@ -10158,7 +10530,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": true, +- "line": 523, ++ "line": 559, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, +@@ -10194,7 +10566,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 47, ++ "line": 51, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, +@@ -10206,7 +10578,19 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 127, ++ "line": 137, ++ "path": "platform/neuroforge/internal/brain/brain.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.chatModelJSONLimitOn", ++ "kind": "function", ++ "label": "Engine.chatModelJSONLimitOn", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 157, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, +@@ -10218,7 +10602,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 131, ++ "line": 141, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, +@@ -10230,7 +10614,19 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 143, ++ "line": 153, ++ "path": "platform/neuroforge/internal/brain/brain.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.chatModelLimitOnMode", ++ "kind": "function", ++ "label": "Engine.chatModelLimitOnMode", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 161, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, +@@ -10266,10 +10662,34 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 168, ++ "line": 363, + "path": "platform/neuroforge/internal/brain/staging.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.distributedChat", ++ "kind": "function", ++ "label": "Engine.distributedChat", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 83, ++ "path": "platform/neuroforge/internal/brain/distributed.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.distributedEmbed", ++ "kind": "function", ++ "label": "Engine.distributedEmbed", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 54, ++ "path": "platform/neuroforge/internal/brain/distributed.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.duplicateMemory", + "kind": "function", +@@ -10314,7 +10734,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 82, ++ "line": 86, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, +@@ -10326,7 +10746,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 57, ++ "line": 61, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, +@@ -10338,10 +10758,22 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 987, ++ "line": 1040, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.ensureGoalDraftArticleDepth", ++ "kind": "function", ++ "label": "Engine.ensureGoalDraftArticleDepth", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 585, ++ "path": "platform/neuroforge/internal/brain/staging.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.evaluateReward", + "kind": "function", +@@ -10350,7 +10782,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 552, ++ "line": 588, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, +@@ -10374,7 +10806,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 456, ++ "line": 524, + "path": "platform/neuroforge/internal/brain/v8.go" + } + }, +@@ -10422,7 +10854,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 998, ++ "line": 1071, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, +@@ -10434,7 +10866,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 78, ++ "line": 157, + "path": "platform/neuroforge/internal/brain/staging.go" + } + }, +@@ -10482,7 +10914,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 351, ++ "line": 387, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, +@@ -10494,10 +10926,22 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 663, ++ "line": 699, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", ++ "kind": "function", ++ "label": "Engine.repairDraftGrounding", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 662, ++ "path": "platform/neuroforge/internal/brain/staging_quality.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.replicateMemory", + "kind": "function", +@@ -10506,7 +10950,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 696, ++ "line": 732, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, +@@ -10530,7 +10974,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 544, ++ "line": 700, + "path": "platform/neuroforge/internal/brain/v8.go" + } + }, +@@ -10546,6 +10990,18 @@ + "path": "platform/neuroforge/internal/brain/v5.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", ++ "kind": "function", ++ "label": "Engine.reshapeGoalDraftArticle", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 549, ++ "path": "platform/neuroforge/internal/brain/staging.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.searchVectorFederated", + "kind": "function", +@@ -10554,7 +11010,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 594, ++ "line": 630, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, +@@ -10578,7 +11034,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 47, ++ "line": 116, + "path": "platform/neuroforge/internal/brain/staging.go" + } + }, +@@ -10590,7 +11046,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 871, ++ "line": 907, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, +@@ -10602,10 +11058,34 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 258, ++ "line": 597, + "path": "platform/neuroforge/internal/brain/staging.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.verifyDraftClaims", ++ "kind": "function", ++ "label": "Engine.verifyDraftClaims", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 520, ++ "path": "platform/neuroforge/internal/brain/staging_quality.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.waitDistributedJob", ++ "kind": "function", ++ "label": "Engine.waitDistributedJob", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 27, ++ "path": "platform/neuroforge/internal/brain/distributed.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/brain:New", + "kind": "function", +@@ -10614,7 +11094,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": true, +- "line": 43, ++ "line": 47, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, +@@ -10626,7 +11106,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": true, +- "line": 619, ++ "line": 775, + "path": "platform/neuroforge/internal/brain/v8.go" + } + }, +@@ -10638,10 +11118,22 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": true, +- "line": 627, ++ "line": 783, + "path": "platform/neuroforge/internal/brain/v8.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/brain:allDigits", ++ "kind": "function", ++ "label": "allDigits", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 136, ++ "path": "platform/neuroforge/internal/brain/research_quality.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/brain:appendUniqueTags", + "kind": "function", +@@ -10666,6 +11158,30 @@ + "path": "platform/neuroforge/internal/brain/v3.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/brain:articleQualityForDraft", ++ "kind": "function", ++ "label": "articleQualityForDraft", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 535, ++ "path": "platform/neuroforge/internal/brain/staging.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:articleRuneCount", ++ "kind": "function", ++ "label": "articleRuneCount", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 513, ++ "path": "platform/neuroforge/internal/brain/staging.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/brain:buildContext", + "kind": "function", +@@ -10674,7 +11190,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 337, ++ "line": 373, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, +@@ -10702,6 +11218,54 @@ + "path": "platform/neuroforge/internal/brain/v4.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/brain:countDraftIndependentCorroborations", ++ "kind": "function", ++ "label": "countDraftIndependentCorroborations", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 697, ++ "path": "platform/neuroforge/internal/brain/staging_quality.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:criticalIdentifiers", ++ "kind": "function", ++ "label": "criticalIdentifiers", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 394, ++ "path": "platform/neuroforge/internal/brain/staging_quality.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON", ++ "kind": "function", ++ "label": "decodeStagingSynthesisJSON", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 814, ++ "path": "platform/neuroforge/internal/brain/staging.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:decodeVerifierJSON", ++ "kind": "function", ++ "label": "decodeVerifierJSON", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 477, ++ "path": "platform/neuroforge/internal/brain/staging_quality.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/brain:dedupeStrings", + "kind": "function", +@@ -10710,7 +11274,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 605, ++ "line": 761, + "path": "platform/neuroforge/internal/brain/v8.go" + } + }, +@@ -10722,7 +11286,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 449, ++ "line": 517, + "path": "platform/neuroforge/internal/brain/v8.go" + } + }, +@@ -10734,7 +11298,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 890, ++ "line": 926, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, +@@ -10746,7 +11310,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 299, ++ "line": 865, + "path": "platform/neuroforge/internal/brain/staging.go" + } + }, +@@ -10774,6 +11338,54 @@ + "path": "platform/neuroforge/internal/brain/v3.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/brain:deterministicResearchQueries", ++ "kind": "function", ++ "label": "deterministicResearchQueries", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 592, ++ "path": "platform/neuroforge/internal/brain/v8.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:deterministicStagingFailure", ++ "kind": "function", ++ "label": "deterministicStagingFailure", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 339, ++ "path": "platform/neuroforge/internal/brain/staging.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:domainMatches", ++ "kind": "function", ++ "label": "domainMatches", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 122, ++ "path": "platform/neuroforge/internal/brain/staging_quality.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:draftEvidenceAuthorityScore", ++ "kind": "function", ++ "label": "draftEvidenceAuthorityScore", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 261, ++ "path": "platform/neuroforge/internal/brain/staging_quality.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/brain:due", + "kind": "function", +@@ -10810,6 +11422,30 @@ + "path": "platform/neuroforge/internal/brain/v3.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/brain:evidencePackForPrompt", ++ "kind": "function", ++ "label": "evidencePackForPrompt", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 318, ++ "path": "platform/neuroforge/internal/brain/staging_quality.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:extractDraftStatements", ++ "kind": "function", ++ "label": "extractDraftStatements", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 452, ++ "path": "platform/neuroforge/internal/brain/staging_quality.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/brain:filterGoalEvidenceHits", + "kind": "function", +@@ -10818,7 +11454,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 86, ++ "line": 145, + "path": "platform/neuroforge/internal/brain/goal_progress.go" + } + }, +@@ -10830,10 +11466,46 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 440, ++ "line": 508, + "path": "platform/neuroforge/internal/brain/v8.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/brain:goalAnchorTokens", ++ "kind": "function", ++ "label": "goalAnchorTokens", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 16, ++ "path": "platform/neuroforge/internal/brain/research_quality.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:goalEvidenceRelevant", ++ "kind": "function", ++ "label": "goalEvidenceRelevant", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 174, ++ "path": "platform/neuroforge/internal/brain/research_quality.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:goalIDFromTags", ++ "kind": "function", ++ "label": "goalIDFromTags", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 148, ++ "path": "platform/neuroforge/internal/brain/research_quality.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/brain:goalKeywords", + "kind": "function", +@@ -10842,10 +11514,22 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 317, ++ "line": 883, + "path": "platform/neuroforge/internal/brain/staging.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/brain:goalPreferredAuthorityDomains", ++ "kind": "function", ++ "label": "goalPreferredAuthorityDomains", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 722, ++ "path": "platform/neuroforge/internal/brain/staging_quality.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/brain:hashText", + "kind": "function", +@@ -10858,6 +11542,30 @@ + "path": "platform/neuroforge/internal/brain/v8.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/brain:isActionableDraftStatement", ++ "kind": "function", ++ "label": "isActionableDraftStatement", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 442, ++ "path": "platform/neuroforge/internal/brain/staging_quality.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:isJSONHex4", ++ "kind": "function", ++ "label": "isJSONHex4", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 801, ++ "path": "platform/neuroforge/internal/brain/staging.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/brain:maxIntV3", + "kind": "function", +@@ -10870,6 +11578,18 @@ + "path": "platform/neuroforge/internal/brain/v3.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/brain:memoryBelongsToGoal", ++ "kind": "function", ++ "label": "memoryBelongsToGoal", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 159, ++ "path": "platform/neuroforge/internal/brain/research_quality.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/brain:memoryTypeForKind", + "kind": "function", +@@ -10878,7 +11598,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 440, ++ "line": 476, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, +@@ -10890,7 +11610,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 961, ++ "line": 997, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, +@@ -10906,6 +11626,42 @@ + "path": "platform/neuroforge/internal/brain/v3.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/brain:normalizeAuthoritativeDomain", ++ "kind": "function", ++ "label": "normalizeAuthoritativeDomain", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 101, ++ "path": "platform/neuroforge/internal/brain/staging_quality.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:normalizeStatementText", ++ "kind": "function", ++ "label": "normalizeStatementText", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 436, ++ "path": "platform/neuroforge/internal/brain/staging_quality.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:normalizedResearchWords", ++ "kind": "function", ++ "label": "normalizedResearchWords", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 126, ++ "path": "platform/neuroforge/internal/brain/research_quality.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/brain:parsePrediction", + "kind": "function", +@@ -10954,6 +11710,18 @@ + "path": "platform/neuroforge/internal/brain/policy.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/brain:promptEvidenceTexts", ++ "kind": "function", ++ "label": "promptEvidenceTexts", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 285, ++ "path": "platform/neuroforge/internal/brain/staging_quality.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/brain:rendezvousScore", + "kind": "function", +@@ -10978,6 +11746,30 @@ + "path": "platform/neuroforge/internal/brain/v3.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/brain:repairInvalidJSONStringEscapes", ++ "kind": "function", ++ "label": "repairInvalidJSONStringEscapes", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 754, ++ "path": "platform/neuroforge/internal/brain/staging.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:researchMaterialRelevant", ++ "kind": "function", ++ "label": "researchMaterialRelevant", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 64, ++ "path": "platform/neuroforge/internal/brain/research_quality.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/brain:researchQueryUseful", + "kind": "function", +@@ -10986,7 +11778,19 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 519, ++ "line": 675, ++ "path": "platform/neuroforge/internal/brain/v8.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:researchResultsContainRelevant", ++ "kind": "function", ++ "label": "researchResultsContainRelevant", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 287, + "path": "platform/neuroforge/internal/brain/v8.go" + } + }, +@@ -11022,7 +11826,7 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 193, ++ "line": 229, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, +@@ -11050,6 +11854,30 @@ + "path": "platform/neuroforge/internal/brain/research_trace.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/brain:slashSwitchIdentifiers", ++ "kind": "function", ++ "label": "slashSwitchIdentifiers", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 370, ++ "path": "platform/neuroforge/internal/brain/staging_quality.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:sortDraftEvidenceByAuthority", ++ "kind": "function", ++ "label": "sortDraftEvidenceByAuthority", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 271, ++ "path": "platform/neuroforge/internal/brain/staging_quality.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/brain:sortedGoalIDs", + "kind": "function", +@@ -11062,6 +11890,42 @@ + "path": "platform/neuroforge/internal/brain/v3.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/brain:sourceAuditForEvidence", ++ "kind": "function", ++ "label": "sourceAuditForEvidence", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 209, ++ "path": "platform/neuroforge/internal/brain/staging_quality.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:sourceAuthorityFor", ++ "kind": "function", ++ "label": "sourceAuthorityFor", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 155, ++ "path": "platform/neuroforge/internal/brain/staging_quality.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:sourceOriginKey", ++ "kind": "function", ++ "label": "sourceOriginKey", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 132, ++ "path": "platform/neuroforge/internal/brain/staging_quality.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/brain:sourcePolicyKey", + "kind": "function", +@@ -11086,6 +11950,42 @@ + "path": "platform/neuroforge/internal/brain/v8.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/brain:stagingEvidenceSignature", ++ "kind": "function", ++ "label": "stagingEvidenceSignature", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 323, ++ "path": "platform/neuroforge/internal/brain/staging.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:strictUnmarshalJSONObject", ++ "kind": "function", ++ "label": "strictUnmarshalJSONObject", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 732, ++ "path": "platform/neuroforge/internal/brain/staging.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:summarizeEvidenceAuthority", ++ "kind": "function", ++ "label": "summarizeEvidenceAuthority", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 242, ++ "path": "platform/neuroforge/internal/brain/staging_quality.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/brain:summarizeObservation", + "kind": "function", +@@ -11106,10 +12006,34 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 453, ++ "line": 489, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/brain:validateDraftArticleBounds", ++ "kind": "function", ++ "label": "validateDraftArticleBounds", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 517, ++ "path": "platform/neuroforge/internal/brain/staging.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:validateDraftCriticalIdentifiers", ++ "kind": "function", ++ "label": "validateDraftCriticalIdentifiers", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 415, ++ "path": "platform/neuroforge/internal/brain/staging_quality.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/brain:vectorCentroid", + "kind": "function", +@@ -11118,7 +12042,19 @@ + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, +- "line": 910, ++ "line": 946, ++ "path": "platform/neuroforge/internal/brain/brain.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:vectorFingerprint", ++ "kind": "function", ++ "label": "vectorFingerprint", ++ "group": "engineering", ++ "community": "neuroforge/internal/brain", ++ "meta": { ++ "exported": false, ++ "line": 1027, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, +@@ -11130,7 +12066,7 @@ + "community": "neuroforge/internal/core", + "meta": { + "exported": true, +- "line": 675, ++ "line": 740, + "path": "platform/neuroforge/internal/core/types.go" + } + }, +@@ -11286,7 +12222,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 276, ++ "line": 286, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -11298,7 +12234,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 126, ++ "line": 131, + "path": "platform/neuroforge/internal/httpapi/v3.go" + } + }, +@@ -11310,7 +12246,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 145, ++ "line": 150, + "path": "platform/neuroforge/internal/httpapi/v3.go" + } + }, +@@ -11346,7 +12282,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 737, ++ "line": 827, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -11358,7 +12294,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 711, ++ "line": 801, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -11394,7 +12330,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 733, ++ "line": 823, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -11406,7 +12342,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 508, ++ "line": 598, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -11430,7 +12366,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 563, ++ "line": 653, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -11442,10 +12378,34 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 617, ++ "line": 707, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.adminGraphBackfill", ++ "kind": "function", ++ "label": "Server.adminGraphBackfill", ++ "group": "engineering", ++ "community": "neuroforge/internal/httpapi", ++ "meta": { ++ "exported": false, ++ "line": 135, ++ "path": "platform/neuroforge/internal/httpapi/knowledge.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.adminGraphStatus", ++ "kind": "function", ++ "label": "Server.adminGraphStatus", ++ "group": "engineering", ++ "community": "neuroforge/internal/httpapi", ++ "meta": { ++ "exported": false, ++ "line": 119, ++ "path": "platform/neuroforge/internal/httpapi/knowledge.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/httpapi:Server.adminKnowledgeEvents", + "kind": "function", +@@ -11526,7 +12486,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 700, ++ "line": 790, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -11542,6 +12502,54 @@ + "path": "platform/neuroforge/internal/httpapi/v5.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.adminOrchestratorCancel", ++ "kind": "function", ++ "label": "Server.adminOrchestratorCancel", ++ "group": "engineering", ++ "community": "neuroforge/internal/httpapi", ++ "meta": { ++ "exported": false, ++ "line": 162, ++ "path": "platform/neuroforge/internal/httpapi/knowledge.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.adminOrchestratorJobs", ++ "kind": "function", ++ "label": "Server.adminOrchestratorJobs", ++ "group": "engineering", ++ "community": "neuroforge/internal/httpapi", ++ "meta": { ++ "exported": false, ++ "line": 148, ++ "path": "platform/neuroforge/internal/httpapi/knowledge.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.adminOrchestratorRetry", ++ "kind": "function", ++ "label": "Server.adminOrchestratorRetry", ++ "group": "engineering", ++ "community": "neuroforge/internal/httpapi", ++ "meta": { ++ "exported": false, ++ "line": 153, ++ "path": "platform/neuroforge/internal/httpapi/knowledge.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.adminOrchestratorStatus", ++ "kind": "function", ++ "label": "Server.adminOrchestratorStatus", ++ "group": "engineering", ++ "community": "neuroforge/internal/httpapi", ++ "meta": { ++ "exported": false, ++ "line": 144, ++ "path": "platform/neuroforge/internal/httpapi/knowledge.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/httpapi:Server.adminProviderHealth", + "kind": "function", +@@ -11550,7 +12558,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 695, ++ "line": 785, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -11562,7 +12570,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 511, ++ "line": 601, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -11586,7 +12594,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 567, ++ "line": 657, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -11598,7 +12606,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 630, ++ "line": 720, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -11610,7 +12618,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 130, ++ "line": 135, + "path": "platform/neuroforge/internal/httpapi/v3.go" + } + }, +@@ -11658,7 +12666,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 157, ++ "line": 162, + "path": "platform/neuroforge/internal/httpapi/v3.go" + } + }, +@@ -11670,7 +12678,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 117, ++ "line": 122, + "path": "platform/neuroforge/internal/httpapi/v3.go" + } + }, +@@ -11682,7 +12690,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 604, ++ "line": 694, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -11694,7 +12702,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 471, ++ "line": 561, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -11718,7 +12726,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 723, ++ "line": 813, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -11742,7 +12750,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 726, ++ "line": 816, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -11754,7 +12762,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 153, ++ "line": 158, + "path": "platform/neuroforge/internal/httpapi/v3.go" + } + }, +@@ -11766,7 +12774,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 221, ++ "line": 231, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -11778,7 +12786,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 312, ++ "line": 322, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -11802,7 +12810,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 286, ++ "line": 296, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -11898,7 +12906,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 113, ++ "line": 118, + "path": "platform/neuroforge/internal/httpapi/v3.go" + } + }, +@@ -11910,7 +12918,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 253, ++ "line": 263, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -11922,7 +12930,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 308, ++ "line": 318, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -11934,7 +12942,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 405, ++ "line": 415, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -11946,7 +12954,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 92, ++ "line": 97, + "path": "platform/neuroforge/internal/httpapi/v3.go" + } + }, +@@ -11958,7 +12966,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 72, ++ "line": 77, + "path": "platform/neuroforge/internal/httpapi/v3.go" + } + }, +@@ -11994,7 +13002,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 82, ++ "line": 87, + "path": "platform/neuroforge/internal/httpapi/v3.go" + } + }, +@@ -12006,7 +13014,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 18, ++ "line": 19, + "path": "platform/neuroforge/internal/httpapi/v3.go" + } + }, +@@ -12018,7 +13026,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 57, ++ "line": 62, + "path": "platform/neuroforge/internal/httpapi/v3.go" + } + }, +@@ -12030,7 +13038,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 31, ++ "line": 36, + "path": "platform/neuroforge/internal/httpapi/v3.go" + } + }, +@@ -12042,7 +13050,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 14, ++ "line": 15, + "path": "platform/neuroforge/internal/httpapi/v3.go" + } + }, +@@ -12054,7 +13062,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 40, ++ "line": 45, + "path": "platform/neuroforge/internal/httpapi/v3.go" + } + }, +@@ -12066,7 +13074,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 391, ++ "line": 401, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -12078,7 +13086,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 150, ++ "line": 160, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -12114,7 +13122,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 240, ++ "line": 250, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -12142,6 +13150,18 @@ + "path": "platform/neuroforge/internal/httpapi/integration.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.integrationGraphStatus", ++ "kind": "function", ++ "label": "Server.integrationGraphStatus", ++ "group": "engineering", ++ "community": "neuroforge/internal/httpapi", ++ "meta": { ++ "exported": false, ++ "line": 431, ++ "path": "platform/neuroforge/internal/httpapi/httpapi.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/httpapi:Server.integrationKnowledgeDelete", + "kind": "function", +@@ -12178,6 +13198,18 @@ + "path": "platform/neuroforge/internal/httpapi/integration.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.integrationOrchestratorStatus", ++ "kind": "function", ++ "label": "Server.integrationOrchestratorStatus", ++ "group": "engineering", ++ "community": "neuroforge/internal/httpapi", ++ "meta": { ++ "exported": false, ++ "line": 443, ++ "path": "platform/neuroforge/internal/httpapi/httpapi.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/httpapi:Server.integrationResearchGraph", + "kind": "function", +@@ -12222,7 +13254,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 303, ++ "line": 313, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -12234,7 +13266,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 325, ++ "line": 335, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -12246,7 +13278,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 105, ++ "line": 110, + "path": "platform/neuroforge/internal/httpapi/v3.go" + } + }, +@@ -12258,7 +13290,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 794, ++ "line": 884, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -12270,7 +13302,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 191, ++ "line": 201, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -12294,7 +13326,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 859, ++ "line": 949, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -12306,7 +13338,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 769, ++ "line": 859, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -12342,7 +13374,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 338, ++ "line": 348, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -12354,7 +13386,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 354, ++ "line": 364, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -12366,7 +13398,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 747, ++ "line": 837, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -12402,7 +13434,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 417, ++ "line": 427, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -12414,7 +13446,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 267, ++ "line": 277, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -12426,7 +13458,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 421, ++ "line": 507, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -12438,7 +13470,31 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 448, ++ "line": 533, ++ "path": "platform/neuroforge/internal/httpapi/httpapi.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.workerHeartbeat", ++ "kind": "function", ++ "label": "Server.workerHeartbeat", ++ "group": "engineering", ++ "community": "neuroforge/internal/httpapi", ++ "meta": { ++ "exported": false, ++ "line": 489, ++ "path": "platform/neuroforge/internal/httpapi/httpapi.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.workerRegister", ++ "kind": "function", ++ "label": "Server.workerRegister", ++ "group": "engineering", ++ "community": "neuroforge/internal/httpapi", ++ "meta": { ++ "exported": false, ++ "line": 475, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -12462,7 +13518,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 214, ++ "line": 224, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -12486,7 +13542,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 812, ++ "line": 902, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -12498,7 +13554,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 798, ++ "line": 888, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -12522,7 +13578,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 297, ++ "line": 307, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -12618,7 +13674,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 608, ++ "line": 698, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -12690,7 +13746,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 550, ++ "line": 640, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -12750,7 +13806,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, +- "line": 207, ++ "line": 217, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -12786,7 +13842,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": true, +- "line": 172, ++ "line": 182, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -12798,7 +13854,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": true, +- "line": 182, ++ "line": 192, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -12810,7 +13866,7 @@ + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": true, +- "line": 174, ++ "line": 184, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, +@@ -12826,6 +13882,18 @@ + "path": "platform/neuroforge/internal/httpapi/integration.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/httpapi:workerHeartbeatFromRequest", ++ "kind": "function", ++ "label": "workerHeartbeatFromRequest", ++ "group": "engineering", ++ "community": "neuroforge/internal/httpapi", ++ "meta": { ++ "exported": false, ++ "line": 467, ++ "path": "platform/neuroforge/internal/httpapi/httpapi.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/ingest:ChunkText", + "kind": "function", +@@ -12970,6 +14038,18 @@ + "path": "platform/neuroforge/internal/provider/provider.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/provider:Router.ChatJSONOn", ++ "kind": "function", ++ "label": "Router.ChatJSONOn", ++ "group": "engineering", ++ "community": "neuroforge/internal/provider", ++ "meta": { ++ "exported": true, ++ "line": 133, ++ "path": "platform/neuroforge/internal/provider/provider.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/provider:Router.ChatOn", + "kind": "function", +@@ -12990,7 +14070,7 @@ + "community": "neuroforge/internal/provider", + "meta": { + "exported": true, +- "line": 173, ++ "line": 186, + "path": "platform/neuroforge/internal/provider/provider.go" + } + }, +@@ -13002,7 +14082,7 @@ + "community": "neuroforge/internal/provider", + "meta": { + "exported": true, +- "line": 187, ++ "line": 200, + "path": "platform/neuroforge/internal/provider/provider.go" + } + }, +@@ -13014,7 +14094,7 @@ + "community": "neuroforge/internal/provider", + "meta": { + "exported": true, +- "line": 433, ++ "line": 449, + "path": "platform/neuroforge/internal/provider/provider.go" + } + }, +@@ -13026,7 +14106,19 @@ + "community": "neuroforge/internal/provider", + "meta": { + "exported": false, +- "line": 258, ++ "line": 271, ++ "path": "platform/neuroforge/internal/provider/provider.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/provider:Router.chatOn", ++ "kind": "function", ++ "label": "Router.chatOn", ++ "group": "engineering", ++ "community": "neuroforge/internal/provider", ++ "meta": { ++ "exported": false, ++ "line": 137, + "path": "platform/neuroforge/internal/provider/provider.go" + } + }, +@@ -13038,7 +14130,7 @@ + "community": "neuroforge/internal/provider", + "meta": { + "exported": false, +- "line": 323, ++ "line": 339, + "path": "platform/neuroforge/internal/provider/provider.go" + } + }, +@@ -13050,7 +14142,7 @@ + "community": "neuroforge/internal/provider", + "meta": { + "exported": false, +- "line": 402, ++ "line": 418, + "path": "platform/neuroforge/internal/provider/provider.go" + } + }, +@@ -13062,7 +14154,7 @@ + "community": "neuroforge/internal/provider", + "meta": { + "exported": false, +- "line": 303, ++ "line": 319, + "path": "platform/neuroforge/internal/provider/provider.go" + } + }, +@@ -13074,7 +14166,7 @@ + "community": "neuroforge/internal/provider", + "meta": { + "exported": false, +- "line": 373, ++ "line": 389, + "path": "platform/neuroforge/internal/provider/provider.go" + } + }, +@@ -13134,7 +14226,7 @@ + "community": "neuroforge/internal/provider", + "meta": { + "exported": false, +- "line": 243, ++ "line": 256, + "path": "platform/neuroforge/internal/provider/provider.go" + } + }, +@@ -13146,7 +14238,7 @@ + "community": "neuroforge/internal/provider", + "meta": { + "exported": false, +- "line": 236, ++ "line": 249, + "path": "platform/neuroforge/internal/provider/provider.go" + } + }, +@@ -13470,7 +14562,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 50, ++ "line": 52, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -13482,7 +14574,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 576, ++ "line": 650, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -13758,7 +14850,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 100, ++ "line": 101, + "path": "platform/neuroforge/internal/store/knowledge.go" + } + }, +@@ -13770,7 +14862,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 292, ++ "line": 315, + "path": "platform/neuroforge/internal/store/v3.go" + } + }, +@@ -13794,7 +14886,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 789, ++ "line": 863, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -13818,7 +14910,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 1306, ++ "line": 1312, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -13834,6 +14926,18 @@ + "path": "platform/neuroforge/internal/store/raftstate.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.CancelJob", ++ "kind": "function", ++ "label": "Store.CancelJob", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": true, ++ "line": 735, ++ "path": "platform/neuroforge/internal/store/orchestrator.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/store:Store.ClaimJob", + "kind": "function", +@@ -13842,8 +14946,20 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 1364, +- "path": "platform/neuroforge/internal/store/store.go" ++ "line": 389, ++ "path": "platform/neuroforge/internal/store/orchestrator.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.ClaimJobForWorker", ++ "kind": "function", ++ "label": "Store.ClaimJobForWorker", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": true, ++ "line": 393, ++ "path": "platform/neuroforge/internal/store/orchestrator.go" + } + }, + { +@@ -13854,7 +14970,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 578, ++ "line": 652, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -13938,7 +15054,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 594, ++ "line": 668, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -13950,8 +15066,20 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 1389, +- "path": "platform/neuroforge/internal/store/store.go" ++ "line": 436, ++ "path": "platform/neuroforge/internal/store/orchestrator.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.CompleteJobLease", ++ "kind": "function", ++ "label": "Store.CompleteJobLease", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": true, ++ "line": 440, ++ "path": "platform/neuroforge/internal/store/orchestrator.go" + } + }, + { +@@ -13962,7 +15090,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 615, ++ "line": 689, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -13974,7 +15102,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 136, ++ "line": 138, + "path": "platform/neuroforge/internal/store/v3.go" + } + }, +@@ -13986,7 +15114,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 1151, ++ "line": 1157, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -13998,7 +15126,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 1104, ++ "line": 1109, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -14010,7 +15138,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 282, ++ "line": 305, + "path": "platform/neuroforge/internal/store/v3.go" + } + }, +@@ -14034,7 +15162,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 1411, ++ "line": 1357, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -14082,8 +15210,20 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 1351, +- "path": "platform/neuroforge/internal/store/store.go" ++ "line": 91, ++ "path": "platform/neuroforge/internal/store/orchestrator.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.EnqueueJobSpec", ++ "kind": "function", ++ "label": "Store.EnqueueJobSpec", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": true, ++ "line": 95, ++ "path": "platform/neuroforge/internal/store/orchestrator.go" + } + }, + { +@@ -14094,10 +15234,22 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 1431, ++ "line": 1378, + "path": "platform/neuroforge/internal/store/store.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.FinishMasterApply", ++ "kind": "function", ++ "label": "Store.FinishMasterApply", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": true, ++ "line": 686, ++ "path": "platform/neuroforge/internal/store/orchestrator.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/store:Store.FinishResearchRun", + "kind": "function", +@@ -14118,7 +15270,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 387, ++ "line": 397, + "path": "platform/neuroforge/internal/store/wal.go" + } + }, +@@ -14130,7 +15282,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 209, ++ "line": 232, + "path": "platform/neuroforge/internal/store/v3.go" + } + }, +@@ -14142,7 +15294,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 855, ++ "line": 929, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -14178,7 +15330,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 220, ++ "line": 243, + "path": "platform/neuroforge/internal/store/v3.go" + } + }, +@@ -14194,6 +15346,66 @@ + "path": "platform/neuroforge/internal/store/raftstate.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.GraphBackfillCandidates", ++ "kind": "function", ++ "label": "Store.GraphBackfillCandidates", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": true, ++ "line": 285, ++ "path": "platform/neuroforge/internal/store/graph_index.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.GraphDegree", ++ "kind": "function", ++ "label": "Store.GraphDegree", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": true, ++ "line": 117, ++ "path": "platform/neuroforge/internal/store/graph_index.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.GraphStats", ++ "kind": "function", ++ "label": "Store.GraphStats", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": true, ++ "line": 123, ++ "path": "platform/neuroforge/internal/store/graph_index.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.HasLiveWorker", ++ "kind": "function", ++ "label": "Store.HasLiveWorker", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": true, ++ "line": 585, ++ "path": "platform/neuroforge/internal/store/orchestrator.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.HeartbeatWorker", ++ "kind": "function", ++ "label": "Store.HeartbeatWorker", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": true, ++ "line": 294, ++ "path": "platform/neuroforge/internal/store/orchestrator.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/store:Store.IndexSnapshotStatus", + "kind": "function", +@@ -14218,6 +15430,42 @@ + "path": "platform/neuroforge/internal/store/index_segments.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.IsOrchestratorLeader", ++ "kind": "function", ++ "label": "Store.IsOrchestratorLeader", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": true, ++ "line": 829, ++ "path": "platform/neuroforge/internal/store/orchestrator.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.Job", ++ "kind": "function", ++ "label": "Store.Job", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": true, ++ "line": 499, ++ "path": "platform/neuroforge/internal/store/orchestrator.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.JobsSnapshot", ++ "kind": "function", ++ "label": "Store.JobsSnapshot", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": true, ++ "line": 512, ++ "path": "platform/neuroforge/internal/store/orchestrator.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/store:Store.KnowledgeGraph", + "kind": "function", +@@ -14226,7 +15474,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 274, ++ "line": 275, + "path": "platform/neuroforge/internal/store/knowledge.go" + } + }, +@@ -14238,7 +15486,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 212, ++ "line": 213, + "path": "platform/neuroforge/internal/store/knowledge.go" + } + }, +@@ -14250,7 +15498,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 382, ++ "line": 383, + "path": "platform/neuroforge/internal/store/knowledge.go" + } + }, +@@ -14262,7 +15510,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 136, ++ "line": 137, + "path": "platform/neuroforge/internal/store/knowledge.go" + } + }, +@@ -14286,7 +15534,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 1246, ++ "line": 1252, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -14298,10 +15546,22 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 1204, ++ "line": 1210, + "path": "platform/neuroforge/internal/store/store.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.MarkGraphLinked", ++ "kind": "function", ++ "label": "Store.MarkGraphLinked", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": true, ++ "line": 350, ++ "path": "platform/neuroforge/internal/store/graph_index.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/store:Store.MemoriesSnapshot", + "kind": "function", +@@ -14310,7 +15570,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 1223, ++ "line": 1229, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -14350,6 +15610,18 @@ + "path": "platform/neuroforge/internal/store/observability.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.OrchestratorStatus", ++ "kind": "function", ++ "label": "Store.OrchestratorStatus", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": true, ++ "line": 598, ++ "path": "platform/neuroforge/internal/store/orchestrator.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/store:Store.PauseGoal", + "kind": "function", +@@ -14358,7 +15630,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 236, ++ "line": 259, + "path": "platform/neuroforge/internal/store/v3.go" + } + }, +@@ -14374,6 +15646,30 @@ + "path": "platform/neuroforge/internal/store/cluster.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.PendingJobCount", ++ "kind": "function", ++ "label": "Store.PendingJobCount", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": true, ++ "line": 636, ++ "path": "platform/neuroforge/internal/store/orchestrator.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.PendingMasterApplyJobs", ++ "kind": "function", ++ "label": "Store.PendingMasterApplyJobs", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": true, ++ "line": 654, ++ "path": "platform/neuroforge/internal/store/orchestrator.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/store:Store.PrepareClusterEntry", + "kind": "function", +@@ -14386,6 +15682,18 @@ + "path": "platform/neuroforge/internal/store/cluster.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.PruneTerminalJobs", ++ "kind": "function", ++ "label": "Store.PruneTerminalJobs", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": true, ++ "line": 759, ++ "path": "platform/neuroforge/internal/store/orchestrator.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/store:Store.RebuildDiskANN", + "kind": "function", +@@ -14406,7 +15714,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 116, ++ "line": 117, + "path": "platform/neuroforge/internal/store/knowledge.go" + } + }, +@@ -14418,7 +15726,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 308, ++ "line": 331, + "path": "platform/neuroforge/internal/store/v3.go" + } + }, +@@ -14430,7 +15738,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 1338, ++ "line": 1344, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -14446,6 +15754,18 @@ + "path": "platform/neuroforge/internal/store/cluster.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.RegisterWorker", ++ "kind": "function", ++ "label": "Store.RegisterWorker", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": true, ++ "line": 284, ++ "path": "platform/neuroforge/internal/store/orchestrator.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/store:Store.Reinforce", + "kind": "function", +@@ -14454,10 +15774,22 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 1068, ++ "line": 1098, + "path": "platform/neuroforge/internal/store/store.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.ReinforceRelation", ++ "kind": "function", ++ "label": "Store.ReinforceRelation", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": true, ++ "line": 83, ++ "path": "platform/neuroforge/internal/store/graph_index.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/store:Store.ResearchRunsSnapshot", + "kind": "function", +@@ -14478,7 +15810,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 99, ++ "line": 101, + "path": "platform/neuroforge/internal/store/v3.go" + } + }, +@@ -14490,10 +15822,22 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 256, ++ "line": 279, + "path": "platform/neuroforge/internal/store/v3.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.RetryJob", ++ "kind": "function", ++ "label": "Store.RetryJob", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": true, ++ "line": 538, ++ "path": "platform/neuroforge/internal/store/orchestrator.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/store:Store.RunRetention", + "kind": "function", +@@ -14502,7 +15846,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 348, ++ "line": 371, + "path": "platform/neuroforge/internal/store/v3.go" + } + }, +@@ -14526,7 +15870,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 912, ++ "line": 986, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -14538,7 +15882,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 1747, ++ "line": 1709, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -14550,7 +15894,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 1756, ++ "line": 1718, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -14562,7 +15906,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 672, ++ "line": 746, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -14574,7 +15918,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 606, ++ "line": 680, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -14586,7 +15930,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 151, ++ "line": 153, + "path": "platform/neuroforge/internal/store/v3.go" + } + }, +@@ -14598,7 +15942,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 1179, ++ "line": 1185, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -14610,7 +15954,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 1190, ++ "line": 1196, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -14658,7 +16002,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 1259, ++ "line": 1265, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -14694,7 +16038,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 1236, ++ "line": 1242, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -14730,7 +16074,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 1127, ++ "line": 1133, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -14754,7 +16098,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 616, ++ "line": 690, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -14766,7 +16110,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 1252, ++ "line": 1258, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -14778,7 +16122,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 679, ++ "line": 753, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -14802,10 +16146,22 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 168, ++ "line": 170, + "path": "platform/neuroforge/internal/store/v3.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.UpsertRelationEvidence", ++ "kind": "function", ++ "label": "Store.UpsertRelationEvidence", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": true, ++ "line": 367, ++ "path": "platform/neuroforge/internal/store/graph_index.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/store:Store.UpsertSource", + "kind": "function", +@@ -14826,7 +16182,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 1322, ++ "line": 1328, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -14838,7 +16194,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 1449, ++ "line": 1396, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -14862,10 +16218,22 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 393, ++ "line": 403, + "path": "platform/neuroforge/internal/store/wal.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.WorkersSnapshot", ++ "kind": "function", ++ "label": "Store.WorkersSnapshot", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": true, ++ "line": 559, ++ "path": "platform/neuroforge/internal/store/orchestrator.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/store:Store.appendClusterLogDecision", + "kind": "function", +@@ -14934,7 +16302,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 313, ++ "line": 323, + "path": "platform/neuroforge/internal/store/wal.go" + } + }, +@@ -14998,6 +16366,18 @@ + "path": "platform/neuroforge/internal/store/cluster.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.dependencyStateLocked", ++ "kind": "function", ++ "label": "Store.dependencyStateLocked", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": false, ++ "line": 214, ++ "path": "platform/neuroforge/internal/store/orchestrator.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/store:Store.ensureClusterLog", + "kind": "function", +@@ -15030,10 +16410,22 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 866, ++ "line": 940, + "path": "platform/neuroforge/internal/store/store.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.graphExpandLocked", ++ "kind": "function", ++ "label": "Store.graphExpandLocked", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": false, ++ "line": 175, ++ "path": "platform/neuroforge/internal/store/graph_index.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/store:Store.indexCountMatchesLocked", + "kind": "function", +@@ -15058,6 +16450,18 @@ + "path": "platform/neuroforge/internal/store/source_index.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.indexSynapseLocked", ++ "kind": "function", ++ "label": "Store.indexSynapseLocked", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": false, ++ "line": 37, ++ "path": "platform/neuroforge/internal/store/graph_index.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/store:Store.initHotTrackerLocked", + "kind": "function", +@@ -15102,7 +16506,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 373, ++ "line": 383, + "path": "platform/neuroforge/internal/store/wal.go" + } + }, +@@ -15114,7 +16518,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 541, ++ "line": 615, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -15162,10 +16566,22 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 697, ++ "line": 771, + "path": "platform/neuroforge/internal/store/store.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.normalizeJobForClaimLocked", ++ "kind": "function", ++ "label": "Store.normalizeJobForClaimLocked", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": false, ++ "line": 325, ++ "path": "platform/neuroforge/internal/store/orchestrator.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/store:Store.oldestHotLocked", + "kind": "function", +@@ -15198,7 +16614,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 561, ++ "line": 635, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -15210,7 +16626,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 564, ++ "line": 638, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -15222,7 +16638,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 344, ++ "line": 354, + "path": "platform/neuroforge/internal/store/wal.go" + } + }, +@@ -15234,7 +16650,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 702, ++ "line": 776, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -15246,7 +16662,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 739, ++ "line": 813, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -15262,6 +16678,30 @@ + "path": "platform/neuroforge/internal/store/source_index.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.rebuildSynapseAdjLocked", ++ "kind": "function", ++ "label": "Store.rebuildSynapseAdjLocked", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": false, ++ "line": 27, ++ "path": "platform/neuroforge/internal/store/graph_index.go" ++ } ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.registerWorkerLocked", ++ "kind": "function", ++ "label": "Store.registerWorkerLocked", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": false, ++ "line": 244, ++ "path": "platform/neuroforge/internal/store/orchestrator.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/store:Store.replayWAL", + "kind": "function", +@@ -15294,7 +16734,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 15, ++ "line": 17, + "path": "platform/neuroforge/internal/store/v3.go" + } + }, +@@ -15306,7 +16746,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 918, ++ "line": 992, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -15358,6 +16798,18 @@ + "path": "platform/neuroforge/internal/store/source_index.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.unindexSynapseLocked", ++ "kind": "function", ++ "label": "Store.unindexSynapseLocked", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": false, ++ "line": 54, ++ "path": "platform/neuroforge/internal/store/graph_index.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/store:Store.untrackHotMemoryLocked", + "kind": "function", +@@ -15378,7 +16830,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 1455, ++ "line": 1402, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -15426,7 +16878,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 366, ++ "line": 376, + "path": "platform/neuroforge/internal/store/wal.go" + } + }, +@@ -15586,6 +17038,18 @@ + "path": "platform/neuroforge/internal/store/sqar_vector.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/store:appendUniqueRelation", ++ "kind": "function", ++ "label": "appendUniqueRelation", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": false, ++ "line": 72, ++ "path": "platform/neuroforge/internal/store/graph_index.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/store:appendUniqueString", + "kind": "function", +@@ -15594,7 +17058,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 90, ++ "line": 92, + "path": "platform/neuroforge/internal/store/v3.go" + } + }, +@@ -15618,7 +17082,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 214, ++ "line": 217, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -15658,6 +17122,18 @@ + "path": "platform/neuroforge/internal/store/vector_journal.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/store:capabilitiesContain", ++ "kind": "function", ++ "label": "capabilitiesContain", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": false, ++ "line": 75, ++ "path": "platform/neuroforge/internal/store/orchestrator.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/store:cleanupOldIndexBases", + "kind": "function", +@@ -15678,7 +17154,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 162, ++ "line": 164, + "path": "platform/neuroforge/internal/store/v3.go" + } + }, +@@ -15690,7 +17166,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 891, ++ "line": 965, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -15726,7 +17202,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 689, ++ "line": 763, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -15786,7 +17262,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 1061, ++ "line": 1091, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -15802,6 +17278,18 @@ + "path": "platform/neuroforge/internal/store/sqar_vector.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/store:goalFingerprint", ++ "kind": "function", ++ "label": "goalFingerprint", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": false, ++ "line": 222, ++ "path": "platform/neuroforge/internal/store/v3.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/store:hashSnapshotNode", + "kind": "function", +@@ -15894,7 +17382,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 528, ++ "line": 602, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -15918,7 +17406,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 78, ++ "line": 80, + "path": "platform/neuroforge/internal/store/v3.go" + } + }, +@@ -15958,6 +17446,18 @@ + "path": "platform/neuroforge/internal/store/mmap_linux.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/store:maxInt", ++ "kind": "function", ++ "label": "maxInt", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": false, ++ "line": 380, ++ "path": "platform/neuroforge/internal/store/orchestrator.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/store:maxIntStore", + "kind": "function", +@@ -16002,7 +17502,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 92, ++ "line": 93, + "path": "platform/neuroforge/internal/store/knowledge.go" + } + }, +@@ -16014,7 +17514,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 383, ++ "line": 393, + "path": "platform/neuroforge/internal/store/wal.go" + } + }, +@@ -16026,7 +17526,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 333, ++ "line": 356, + "path": "platform/neuroforge/internal/store/v3.go" + } + }, +@@ -16038,7 +17538,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 489, ++ "line": 563, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -16066,6 +17566,18 @@ + "path": "platform/neuroforge/internal/store/pagecache.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/store:normalizeCapabilities", ++ "kind": "function", ++ "label": "normalizeCapabilities", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": false, ++ "line": 60, ++ "path": "platform/neuroforge/internal/store/orchestrator.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/store:normalizeVectorJournalOptions", + "kind": "function", +@@ -16158,7 +17670,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 1097, ++ "line": 1102, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -16182,7 +17694,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 206, ++ "line": 207, + "path": "platform/neuroforge/internal/store/knowledge.go" + } + }, +@@ -16194,7 +17706,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 207, ++ "line": 208, + "path": "platform/neuroforge/internal/store/knowledge.go" + } + }, +@@ -16206,7 +17718,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 210, ++ "line": 211, + "path": "platform/neuroforge/internal/store/knowledge.go" + } + }, +@@ -16218,7 +17730,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 209, ++ "line": 210, + "path": "platform/neuroforge/internal/store/knowledge.go" + } + }, +@@ -16230,7 +17742,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": true, +- "line": 208, ++ "line": 209, + "path": "platform/neuroforge/internal/store/knowledge.go" + } + }, +@@ -16242,7 +17754,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 568, ++ "line": 642, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -16366,6 +17878,18 @@ + "path": "platform/neuroforge/internal/store/sqar_vector.go" + } + }, ++ { ++ "id": "function:neuroforge/internal/store:workerMatchesJob", ++ "kind": "function", ++ "label": "workerMatchesJob", ++ "group": "engineering", ++ "community": "neuroforge/internal/store", ++ "meta": { ++ "exported": false, ++ "line": 235, ++ "path": "platform/neuroforge/internal/store/orchestrator.go" ++ } ++ }, + { + "id": "function:neuroforge/internal/store:writeAtomic", + "kind": "function", +@@ -16374,7 +17898,7 @@ + "community": "neuroforge/internal/store", + "meta": { + "exported": false, +- "line": 549, ++ "line": 623, + "path": "platform/neuroforge/internal/store/store.go" + } + }, +@@ -17942,6 +19466,16 @@ + "file": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, ++ { ++ "id": "route:GET /admin/api/graph/status", ++ "kind": "route", ++ "label": "GET /admin/api/graph/status", ++ "group": "engineering", ++ "community": "platform/neuroforge", ++ "meta": { ++ "file": "platform/neuroforge/internal/httpapi/httpapi.go" ++ } ++ }, + { + "id": "route:GET /admin/api/index/disk", + "kind": "route", +@@ -18032,6 +19566,26 @@ + "file": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, ++ { ++ "id": "route:GET /admin/api/orchestrator/jobs", ++ "kind": "route", ++ "label": "GET /admin/api/orchestrator/jobs", ++ "group": "engineering", ++ "community": "platform/neuroforge", ++ "meta": { ++ "file": "platform/neuroforge/internal/httpapi/httpapi.go" ++ } ++ }, ++ { ++ "id": "route:GET /admin/api/orchestrator/status", ++ "kind": "route", ++ "label": "GET /admin/api/orchestrator/status", ++ "group": "engineering", ++ "community": "platform/neuroforge", ++ "meta": { ++ "file": "platform/neuroforge/internal/httpapi/httpapi.go" ++ } ++ }, + { + "id": "route:GET /admin/api/research", + "kind": "route", +@@ -18392,6 +19946,26 @@ + "file": "services/agent/internal/web/server.go" + } + }, ++ { ++ "id": "route:GET /api/neuroforge/graph-status", ++ "kind": "route", ++ "label": "GET /api/neuroforge/graph-status", ++ "group": "engineering", ++ "community": "services/control", ++ "meta": { ++ "file": "services/control/main.go" ++ } ++ }, ++ { ++ "id": "route:GET /api/neuroforge/orchestrator", ++ "kind": "route", ++ "label": "GET /api/neuroforge/orchestrator", ++ "group": "engineering", ++ "community": "services/control", ++ "meta": { ++ "file": "services/control/main.go" ++ } ++ }, + { + "id": "route:GET /api/outcomes", + "kind": "route", +@@ -18522,6 +20096,26 @@ + "file": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, ++ { ++ "id": "route:GET /api/v1/integrations/graph/status", ++ "kind": "route", ++ "label": "GET /api/v1/integrations/graph/status", ++ "group": "engineering", ++ "community": "platform/neuroforge", ++ "meta": { ++ "file": "platform/neuroforge/internal/httpapi/httpapi.go" ++ } ++ }, ++ { ++ "id": "route:GET /api/v1/integrations/orchestrator/status", ++ "kind": "route", ++ "label": "GET /api/v1/integrations/orchestrator/status", ++ "group": "engineering", ++ "community": "platform/neuroforge", ++ "meta": { ++ "file": "platform/neuroforge/internal/httpapi/httpapi.go" ++ } ++ }, + { + "id": "route:GET /api/v1/learning-cycles", + "kind": "route", +@@ -18702,6 +20296,16 @@ + "file": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, ++ { ++ "id": "route:POST /admin/api/graph/backfill", ++ "kind": "route", ++ "label": "POST /admin/api/graph/backfill", ++ "group": "engineering", ++ "community": "platform/neuroforge", ++ "meta": { ++ "file": "platform/neuroforge/internal/httpapi/httpapi.go" ++ } ++ }, + { + "id": "route:POST /admin/api/index/disk/rebuild", + "kind": "route", +@@ -18732,6 +20336,26 @@ + "file": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, ++ { ++ "id": "route:POST /admin/api/orchestrator/jobs/{id}/cancel", ++ "kind": "route", ++ "label": "POST /admin/api/orchestrator/jobs/{id}/cancel", ++ "group": "engineering", ++ "community": "platform/neuroforge", ++ "meta": { ++ "file": "platform/neuroforge/internal/httpapi/httpapi.go" ++ } ++ }, ++ { ++ "id": "route:POST /admin/api/orchestrator/jobs/{id}/retry", ++ "kind": "route", ++ "label": "POST /admin/api/orchestrator/jobs/{id}/retry", ++ "group": "engineering", ++ "community": "platform/neuroforge", ++ "meta": { ++ "file": "platform/neuroforge/internal/httpapi/httpapi.go" ++ } ++ }, + { + "id": "route:POST /admin/api/provider-health", + "kind": "route", +@@ -19102,6 +20726,26 @@ + "file": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, ++ { ++ "id": "route:POST /api/v1/worker/heartbeat", ++ "kind": "route", ++ "label": "POST /api/v1/worker/heartbeat", ++ "group": "engineering", ++ "community": "platform/neuroforge", ++ "meta": { ++ "file": "platform/neuroforge/internal/httpapi/httpapi.go" ++ } ++ }, ++ { ++ "id": "route:POST /api/v1/worker/register", ++ "kind": "route", ++ "label": "POST /api/v1/worker/register", ++ "group": "engineering", ++ "community": "platform/neuroforge", ++ "meta": { ++ "file": "platform/neuroforge/internal/httpapi/httpapi.go" ++ } ++ }, + { + "id": "route:POST /internal/v1/cluster/abort", + "kind": "route", +@@ -19280,7 +20924,7 @@ + "community": "compose", + "status": "configured", + "meta": { +- "image": "git.send.nrw/sendnrw/glpi-neuroforge-mega-agent:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.5.0}" ++ "image": "git.send.nrw/sendnrw/glpi-neuroforge-mega-agent:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.6.0}" + } + }, + { +@@ -19291,7 +20935,7 @@ + "community": "compose", + "status": "configured", + "meta": { +- "image": "git.send.nrw/sendnrw/glpi-neuroforge-mega-agent-data-init:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.5.0}" ++ "image": "git.send.nrw/sendnrw/glpi-neuroforge-mega-agent-data-init:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.6.0}" + } + }, + { +@@ -19302,7 +20946,7 @@ + "community": "compose", + "status": "configured", + "meta": { +- "image": "git.send.nrw/sendnrw/glpi-neuroforge-mega-control:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.5.0}" ++ "image": "git.send.nrw/sendnrw/glpi-neuroforge-mega-control:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.6.0}" + } + }, + { +@@ -19313,7 +20957,7 @@ + "community": "compose", + "status": "configured", + "meta": { +- "image": "git.send.nrw/sendnrw/glpi-neuroforge-mega-knowledge:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.5.0}" ++ "image": "git.send.nrw/sendnrw/glpi-neuroforge-mega-knowledge:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.6.0}" + } + }, + { +@@ -19324,18 +20968,29 @@ + "community": "compose", + "status": "configured", + "meta": { +- "image": "git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.5.0}" ++ "image": "git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.6.0}" ++ } ++ }, ++ { ++ "id": "service:neuroforge-worker-cpu", ++ "kind": "service", ++ "label": "neuroforge-worker-cpu", ++ "group": "runtime", ++ "community": "compose", ++ "status": "configured", ++ "meta": { ++ "image": "git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge-worker:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.6.0}" + } + }, + { +- "id": "service:neuroforge-worker", ++ "id": "service:neuroforge-worker-gpu", + "kind": "service", +- "label": "neuroforge-worker", ++ "label": "neuroforge-worker-gpu", + "group": "runtime", + "community": "compose", + "status": "configured", + "meta": { +- "image": "git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge-worker:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.5.0}" ++ "image": "git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge-worker:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.6.0}" + } + }, + { +@@ -19680,12 +21335,24 @@ + "to": "function:neuroforge/cmd/server:envBool", + "kind": "defines" + }, ++ { ++ "id": "file:platform/neuroforge/cmd/server/main.go-\u003efunction:neuroforge/cmd/server:envFloat:defines", ++ "from": "file:platform/neuroforge/cmd/server/main.go", ++ "to": "function:neuroforge/cmd/server:envFloat", ++ "kind": "defines" ++ }, + { + "id": "file:platform/neuroforge/cmd/server/main.go-\u003efunction:neuroforge/cmd/server:envInt:defines", + "from": "file:platform/neuroforge/cmd/server/main.go", + "to": "function:neuroforge/cmd/server:envInt", + "kind": "defines" + }, ++ { ++ "id": "file:platform/neuroforge/cmd/server/main.go-\u003efunction:neuroforge/cmd/server:firstNonEmptyMain:defines", ++ "from": "file:platform/neuroforge/cmd/server/main.go", ++ "to": "function:neuroforge/cmd/server:firstNonEmptyMain", ++ "kind": "defines" ++ }, + { + "id": "file:platform/neuroforge/cmd/server/main.go-\u003efunction:neuroforge/cmd/server:main:defines", + "from": "file:platform/neuroforge/cmd/server/main.go", +@@ -19842,30 +21509,144 @@ + "to": "function:neuroforge/cmd/worker:complete", + "kind": "defines" + }, ++ { ++ "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:csv:defines", ++ "from": "file:platform/neuroforge/cmd/worker/main.go", ++ "to": "function:neuroforge/cmd/worker:csv", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:envDuration:defines", ++ "from": "file:platform/neuroforge/cmd/worker/main.go", ++ "to": "function:neuroforge/cmd/worker:envDuration", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:envInt:defines", ++ "from": "file:platform/neuroforge/cmd/worker/main.go", ++ "to": "function:neuroforge/cmd/worker:envInt", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:firstNonEmpty:defines", ++ "from": "file:platform/neuroforge/cmd/worker/main.go", ++ "to": "function:neuroforge/cmd/worker:firstNonEmpty", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:hasCap:defines", ++ "from": "file:platform/neuroforge/cmd/worker/main.go", ++ "to": "function:neuroforge/cmd/worker:hasCap", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:heartbeatLoop:defines", ++ "from": "file:platform/neuroforge/cmd/worker/main.go", ++ "to": "function:neuroforge/cmd/worker:heartbeatLoop", ++ "kind": "defines" ++ }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:hostname:defines", + "from": "file:platform/neuroforge/cmd/worker/main.go", + "to": "function:neuroforge/cmd/worker:hostname", + "kind": "defines" + }, ++ { ++ "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:leaseSet.add:defines", ++ "from": "file:platform/neuroforge/cmd/worker/main.go", ++ "to": "function:neuroforge/cmd/worker:leaseSet.add", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:leaseSet.del:defines", ++ "from": "file:platform/neuroforge/cmd/worker/main.go", ++ "to": "function:neuroforge/cmd/worker:leaseSet.del", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:leaseSet.snapshot:defines", ++ "from": "file:platform/neuroforge/cmd/worker/main.go", ++ "to": "function:neuroforge/cmd/worker:leaseSet.snapshot", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:lowerDefault:defines", ++ "from": "file:platform/neuroforge/cmd/worker/main.go", ++ "to": "function:neuroforge/cmd/worker:lowerDefault", ++ "kind": "defines" ++ }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:main:defines", + "from": "file:platform/neuroforge/cmd/worker/main.go", + "to": "function:neuroforge/cmd/worker:main", + "kind": "defines" + }, ++ { ++ "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:ollamaChat:defines", ++ "from": "file:platform/neuroforge/cmd/worker/main.go", ++ "to": "function:neuroforge/cmd/worker:ollamaChat", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:ollamaEmbed:defines", ++ "from": "file:platform/neuroforge/cmd/worker/main.go", ++ "to": "function:neuroforge/cmd/worker:ollamaEmbed", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:pollLoop:defines", ++ "from": "file:platform/neuroforge/cmd/worker/main.go", ++ "to": "function:neuroforge/cmd/worker:pollLoop", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:postJSON:defines", ++ "from": "file:platform/neuroforge/cmd/worker/main.go", ++ "to": "function:neuroforge/cmd/worker:postJSON", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:postOllama:defines", ++ "from": "file:platform/neuroforge/cmd/worker/main.go", ++ "to": "function:neuroforge/cmd/worker:postOllama", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:register:defines", ++ "from": "file:platform/neuroforge/cmd/worker/main.go", ++ "to": "function:neuroforge/cmd/worker:register", ++ "kind": "defines" ++ }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:run:defines", + "from": "file:platform/neuroforge/cmd/worker/main.go", + "to": "function:neuroforge/cmd/worker:run", + "kind": "defines" + }, ++ { ++ "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:waitOrDone:defines", ++ "from": "file:platform/neuroforge/cmd/worker/main.go", ++ "to": "function:neuroforge/cmd/worker:waitOrDone", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:workerBody:defines", ++ "from": "file:platform/neuroforge/cmd/worker/main.go", ++ "to": "function:neuroforge/cmd/worker:workerBody", ++ "kind": "defines" ++ }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003epackage:bytes:imports", + "from": "file:platform/neuroforge/cmd/worker/main.go", + "to": "package:bytes", + "kind": "imports" + }, ++ { ++ "id": "file:platform/neuroforge/cmd/worker/main.go-\u003epackage:context:imports", ++ "from": "file:platform/neuroforge/cmd/worker/main.go", ++ "to": "package:context", ++ "kind": "imports" ++ }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003epackage:encoding/json:imports", + "from": "file:platform/neuroforge/cmd/worker/main.go", +@@ -19914,6 +21695,12 @@ + "to": "package:neuroforge/internal/vector", + "kind": "imports" + }, ++ { ++ "id": "file:platform/neuroforge/cmd/worker/main.go-\u003epackage:os/signal:imports", ++ "from": "file:platform/neuroforge/cmd/worker/main.go", ++ "to": "package:os/signal", ++ "kind": "imports" ++ }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003epackage:os:imports", + "from": "file:platform/neuroforge/cmd/worker/main.go", +@@ -19926,12 +21713,30 @@ + "to": "package:sort", + "kind": "imports" + }, ++ { ++ "id": "file:platform/neuroforge/cmd/worker/main.go-\u003epackage:strconv:imports", ++ "from": "file:platform/neuroforge/cmd/worker/main.go", ++ "to": "package:strconv", ++ "kind": "imports" ++ }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003epackage:strings:imports", + "from": "file:platform/neuroforge/cmd/worker/main.go", + "to": "package:strings", + "kind": "imports" + }, ++ { ++ "id": "file:platform/neuroforge/cmd/worker/main.go-\u003epackage:sync:imports", ++ "from": "file:platform/neuroforge/cmd/worker/main.go", ++ "to": "package:sync", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/cmd/worker/main.go-\u003epackage:syscall:imports", ++ "from": "file:platform/neuroforge/cmd/worker/main.go", ++ "to": "package:syscall", ++ "kind": "imports" ++ }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003epackage:time:imports", + "from": "file:platform/neuroforge/cmd/worker/main.go", +@@ -20010,6 +21815,12 @@ + "to": "function:neuroforge/internal/brain:Engine.chatModel", + "kind": "defines" + }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/brain.go-\u003efunction:neuroforge/internal/brain:Engine.chatModelJSONLimitOn:defines", ++ "from": "file:platform/neuroforge/internal/brain/brain.go", ++ "to": "function:neuroforge/internal/brain:Engine.chatModelJSONLimitOn", ++ "kind": "defines" ++ }, + { + "id": "file:platform/neuroforge/internal/brain/brain.go-\u003efunction:neuroforge/internal/brain:Engine.chatModelLimit:defines", + "from": "file:platform/neuroforge/internal/brain/brain.go", +@@ -20022,6 +21833,12 @@ + "to": "function:neuroforge/internal/brain:Engine.chatModelLimitOn", + "kind": "defines" + }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/brain.go-\u003efunction:neuroforge/internal/brain:Engine.chatModelLimitOnMode:defines", ++ "from": "file:platform/neuroforge/internal/brain/brain.go", ++ "to": "function:neuroforge/internal/brain:Engine.chatModelLimitOnMode", ++ "kind": "defines" ++ }, + { + "id": "file:platform/neuroforge/internal/brain/brain.go-\u003efunction:neuroforge/internal/brain:Engine.embed:defines", + "from": "file:platform/neuroforge/internal/brain/brain.go", +@@ -20130,6 +21947,12 @@ + "to": "function:neuroforge/internal/brain:vectorCentroid", + "kind": "defines" + }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/brain.go-\u003efunction:neuroforge/internal/brain:vectorFingerprint:defines", ++ "from": "file:platform/neuroforge/internal/brain/brain.go", ++ "to": "function:neuroforge/internal/brain:vectorFingerprint", ++ "kind": "defines" ++ }, + { + "id": "file:platform/neuroforge/internal/brain/brain.go-\u003epackage:bytes:imports", + "from": "file:platform/neuroforge/internal/brain/brain.go", +@@ -20142,6 +21965,24 @@ + "to": "package:context", + "kind": "imports" + }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/brain.go-\u003epackage:crypto/sha256:imports", ++ "from": "file:platform/neuroforge/internal/brain/brain.go", ++ "to": "package:crypto/sha256", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/brain.go-\u003epackage:encoding/binary:imports", ++ "from": "file:platform/neuroforge/internal/brain/brain.go", ++ "to": "package:encoding/binary", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/brain.go-\u003epackage:encoding/hex:imports", ++ "from": "file:platform/neuroforge/internal/brain/brain.go", ++ "to": "package:encoding/hex", ++ "kind": "imports" ++ }, + { + "id": "file:platform/neuroforge/internal/brain/brain.go-\u003epackage:encoding/json:imports", + "from": "file:platform/neuroforge/internal/brain/brain.go", +@@ -20166,6 +22007,12 @@ + "to": "package:io", + "kind": "imports" + }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/brain.go-\u003epackage:math:imports", ++ "from": "file:platform/neuroforge/internal/brain/brain.go", ++ "to": "package:math", ++ "kind": "imports" ++ }, + { + "id": "file:platform/neuroforge/internal/brain/brain.go-\u003epackage:net/http:imports", + "from": "file:platform/neuroforge/internal/brain/brain.go", +@@ -20238,6 +22085,66 @@ + "to": "package:time", + "kind": "imports" + }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/distributed.go-\u003efunction:neuroforge/internal/brain:Engine.distributedChat:defines", ++ "from": "file:platform/neuroforge/internal/brain/distributed.go", ++ "to": "function:neuroforge/internal/brain:Engine.distributedChat", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/distributed.go-\u003efunction:neuroforge/internal/brain:Engine.distributedEmbed:defines", ++ "from": "file:platform/neuroforge/internal/brain/distributed.go", ++ "to": "function:neuroforge/internal/brain:Engine.distributedEmbed", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/distributed.go-\u003efunction:neuroforge/internal/brain:Engine.waitDistributedJob:defines", ++ "from": "file:platform/neuroforge/internal/brain/distributed.go", ++ "to": "function:neuroforge/internal/brain:Engine.waitDistributedJob", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/distributed.go-\u003epackage:context:imports", ++ "from": "file:platform/neuroforge/internal/brain/distributed.go", ++ "to": "package:context", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/distributed.go-\u003epackage:encoding/json:imports", ++ "from": "file:platform/neuroforge/internal/brain/distributed.go", ++ "to": "package:encoding/json", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/distributed.go-\u003epackage:errors:imports", ++ "from": "file:platform/neuroforge/internal/brain/distributed.go", ++ "to": "package:errors", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/distributed.go-\u003epackage:fmt:imports", ++ "from": "file:platform/neuroforge/internal/brain/distributed.go", ++ "to": "package:fmt", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/distributed.go-\u003epackage:neuroforge/internal/provider:imports", ++ "from": "file:platform/neuroforge/internal/brain/distributed.go", ++ "to": "package:neuroforge/internal/provider", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/distributed.go-\u003epackage:neuroforge/internal/store:imports", ++ "from": "file:platform/neuroforge/internal/brain/distributed.go", ++ "to": "package:neuroforge/internal/store", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/distributed.go-\u003epackage:time:imports", ++ "from": "file:platform/neuroforge/internal/brain/distributed.go", ++ "to": "package:time", ++ "kind": "imports" ++ }, + { + "id": "file:platform/neuroforge/internal/brain/goal_progress.go-\u003efunction:neuroforge/internal/brain:Engine.ReconcileGoalProgress:defines", + "from": "file:platform/neuroforge/internal/brain/goal_progress.go", +@@ -20298,6 +22205,54 @@ + "to": "package:strings", + "kind": "imports" + }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/orchestrator.go-\u003efunction:neuroforge/internal/brain:Engine.ApplyAndFinalizeJob:defines", ++ "from": "file:platform/neuroforge/internal/brain/orchestrator.go", ++ "to": "function:neuroforge/internal/brain:Engine.ApplyAndFinalizeJob", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/orchestrator.go-\u003efunction:neuroforge/internal/brain:Engine.ApplyPendingJobResults:defines", ++ "from": "file:platform/neuroforge/internal/brain/orchestrator.go", ++ "to": "function:neuroforge/internal/brain:Engine.ApplyPendingJobResults", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/orchestrator.go-\u003efunction:neuroforge/internal/brain:Engine.PlanGraphBackfill:defines", ++ "from": "file:platform/neuroforge/internal/brain/orchestrator.go", ++ "to": "function:neuroforge/internal/brain:Engine.PlanGraphBackfill", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/orchestrator.go-\u003efunction:neuroforge/internal/brain:Engine.RunOrchestrator:defines", ++ "from": "file:platform/neuroforge/internal/brain/orchestrator.go", ++ "to": "function:neuroforge/internal/brain:Engine.RunOrchestrator", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/orchestrator.go-\u003epackage:context:imports", ++ "from": "file:platform/neuroforge/internal/brain/orchestrator.go", ++ "to": "package:context", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/orchestrator.go-\u003epackage:errors:imports", ++ "from": "file:platform/neuroforge/internal/brain/orchestrator.go", ++ "to": "package:errors", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/orchestrator.go-\u003epackage:neuroforge/internal/core:imports", ++ "from": "file:platform/neuroforge/internal/brain/orchestrator.go", ++ "to": "package:neuroforge/internal/core", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/orchestrator.go-\u003epackage:time:imports", ++ "from": "file:platform/neuroforge/internal/brain/orchestrator.go", ++ "to": "package:time", ++ "kind": "imports" ++ }, + { + "id": "file:platform/neuroforge/internal/brain/policy.go-\u003efunction:neuroforge/internal/brain:Engine.duplicateMemory:defines", + "from": "file:platform/neuroforge/internal/brain/policy.go", +@@ -20346,6 +22301,72 @@ + "to": "package:unicode/utf8", + "kind": "imports" + }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/research_quality.go-\u003efunction:neuroforge/internal/brain:allDigits:defines", ++ "from": "file:platform/neuroforge/internal/brain/research_quality.go", ++ "to": "function:neuroforge/internal/brain:allDigits", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/research_quality.go-\u003efunction:neuroforge/internal/brain:goalAnchorTokens:defines", ++ "from": "file:platform/neuroforge/internal/brain/research_quality.go", ++ "to": "function:neuroforge/internal/brain:goalAnchorTokens", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/research_quality.go-\u003efunction:neuroforge/internal/brain:goalEvidenceRelevant:defines", ++ "from": "file:platform/neuroforge/internal/brain/research_quality.go", ++ "to": "function:neuroforge/internal/brain:goalEvidenceRelevant", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/research_quality.go-\u003efunction:neuroforge/internal/brain:goalIDFromTags:defines", ++ "from": "file:platform/neuroforge/internal/brain/research_quality.go", ++ "to": "function:neuroforge/internal/brain:goalIDFromTags", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/research_quality.go-\u003efunction:neuroforge/internal/brain:memoryBelongsToGoal:defines", ++ "from": "file:platform/neuroforge/internal/brain/research_quality.go", ++ "to": "function:neuroforge/internal/brain:memoryBelongsToGoal", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/research_quality.go-\u003efunction:neuroforge/internal/brain:normalizedResearchWords:defines", ++ "from": "file:platform/neuroforge/internal/brain/research_quality.go", ++ "to": "function:neuroforge/internal/brain:normalizedResearchWords", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/research_quality.go-\u003efunction:neuroforge/internal/brain:researchMaterialRelevant:defines", ++ "from": "file:platform/neuroforge/internal/brain/research_quality.go", ++ "to": "function:neuroforge/internal/brain:researchMaterialRelevant", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/research_quality.go-\u003epackage:neuroforge/internal/core:imports", ++ "from": "file:platform/neuroforge/internal/brain/research_quality.go", ++ "to": "package:neuroforge/internal/core", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/research_quality.go-\u003epackage:sort:imports", ++ "from": "file:platform/neuroforge/internal/brain/research_quality.go", ++ "to": "package:sort", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/research_quality.go-\u003epackage:strings:imports", ++ "from": "file:platform/neuroforge/internal/brain/research_quality.go", ++ "to": "package:strings", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/research_quality.go-\u003epackage:unicode:imports", ++ "from": "file:platform/neuroforge/internal/brain/research_quality.go", ++ "to": "package:unicode", ++ "kind": "imports" ++ }, + { + "id": "file:platform/neuroforge/internal/brain/research_trace.go-\u003efunction:neuroforge/internal/brain:Engine.newResearchTrace:defines", + "from": "file:platform/neuroforge/internal/brain/research_trace.go", +@@ -20412,12 +22433,24 @@ + "to": "function:neuroforge/internal/brain:Engine.collectGoalDraftEvidence", + "kind": "defines" + }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:Engine.ensureGoalDraftArticleDepth:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging.go", ++ "to": "function:neuroforge/internal/brain:Engine.ensureGoalDraftArticleDepth", ++ "kind": "defines" ++ }, + { + "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:Engine.maybePublishGoalDraft:defines", + "from": "file:platform/neuroforge/internal/brain/staging.go", + "to": "function:neuroforge/internal/brain:Engine.maybePublishGoalDraft", + "kind": "defines" + }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging.go", ++ "to": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", ++ "kind": "defines" ++ }, + { + "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:Engine.stagingConfig:defines", + "from": "file:platform/neuroforge/internal/brain/staging.go", +@@ -20430,18 +22463,72 @@ + "to": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", + "kind": "defines" + }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:articleQualityForDraft:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging.go", ++ "to": "function:neuroforge/internal/brain:articleQualityForDraft", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:articleRuneCount:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging.go", ++ "to": "function:neuroforge/internal/brain:articleRuneCount", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:decodeStagingSynthesisJSON:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging.go", ++ "to": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON", ++ "kind": "defines" ++ }, + { + "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:deterministicDraftAnswer:defines", + "from": "file:platform/neuroforge/internal/brain/staging.go", + "to": "function:neuroforge/internal/brain:deterministicDraftAnswer", + "kind": "defines" + }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:deterministicStagingFailure:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging.go", ++ "to": "function:neuroforge/internal/brain:deterministicStagingFailure", ++ "kind": "defines" ++ }, + { + "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:goalKeywords:defines", + "from": "file:platform/neuroforge/internal/brain/staging.go", + "to": "function:neuroforge/internal/brain:goalKeywords", + "kind": "defines" + }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:isJSONHex4:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging.go", ++ "to": "function:neuroforge/internal/brain:isJSONHex4", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:repairInvalidJSONStringEscapes:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging.go", ++ "to": "function:neuroforge/internal/brain:repairInvalidJSONStringEscapes", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:stagingEvidenceSignature:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging.go", ++ "to": "function:neuroforge/internal/brain:stagingEvidenceSignature", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:strictUnmarshalJSONObject:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging.go", ++ "to": "function:neuroforge/internal/brain:strictUnmarshalJSONObject", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:validateDraftArticleBounds:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging.go", ++ "to": "function:neuroforge/internal/brain:validateDraftArticleBounds", ++ "kind": "defines" ++ }, + { + "id": "file:platform/neuroforge/internal/brain/staging.go-\u003epackage:bytes:imports", + "from": "file:platform/neuroforge/internal/brain/staging.go", +@@ -20454,6 +22541,12 @@ + "to": "package:context", + "kind": "imports" + }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging.go-\u003epackage:crypto/sha256:imports", ++ "from": "file:platform/neuroforge/internal/brain/staging.go", ++ "to": "package:crypto/sha256", ++ "kind": "imports" ++ }, + { + "id": "file:platform/neuroforge/internal/brain/staging.go-\u003epackage:encoding/json:imports", + "from": "file:platform/neuroforge/internal/brain/staging.go", +@@ -20502,6 +22595,198 @@ + "to": "package:strings", + "kind": "imports" + }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:Engine.repairDraftGrounding:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:Engine.verifyDraftClaims:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "function:neuroforge/internal/brain:Engine.verifyDraftClaims", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:countDraftIndependentCorroborations:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "function:neuroforge/internal/brain:countDraftIndependentCorroborations", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:criticalIdentifiers:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "function:neuroforge/internal/brain:criticalIdentifiers", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:decodeVerifierJSON:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "function:neuroforge/internal/brain:decodeVerifierJSON", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:domainMatches:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "function:neuroforge/internal/brain:domainMatches", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:draftEvidenceAuthorityScore:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "function:neuroforge/internal/brain:draftEvidenceAuthorityScore", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:evidencePackForPrompt:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "function:neuroforge/internal/brain:evidencePackForPrompt", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:extractDraftStatements:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "function:neuroforge/internal/brain:extractDraftStatements", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:goalPreferredAuthorityDomains:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "function:neuroforge/internal/brain:goalPreferredAuthorityDomains", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:isActionableDraftStatement:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "function:neuroforge/internal/brain:isActionableDraftStatement", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:normalizeAuthoritativeDomain:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "function:neuroforge/internal/brain:normalizeAuthoritativeDomain", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:normalizeStatementText:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "function:neuroforge/internal/brain:normalizeStatementText", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:promptEvidenceTexts:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "function:neuroforge/internal/brain:promptEvidenceTexts", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:slashSwitchIdentifiers:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "function:neuroforge/internal/brain:slashSwitchIdentifiers", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:sortDraftEvidenceByAuthority:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "function:neuroforge/internal/brain:sortDraftEvidenceByAuthority", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:sourceAuditForEvidence:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "function:neuroforge/internal/brain:sourceAuditForEvidence", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:sourceAuthorityFor:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "function:neuroforge/internal/brain:sourceAuthorityFor", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:sourceOriginKey:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "function:neuroforge/internal/brain:sourceOriginKey", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:summarizeEvidenceAuthority:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "function:neuroforge/internal/brain:summarizeEvidenceAuthority", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:validateDraftCriticalIdentifiers:defines", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "function:neuroforge/internal/brain:validateDraftCriticalIdentifiers", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003epackage:context:imports", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "package:context", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003epackage:encoding/json:imports", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "package:encoding/json", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003epackage:errors:imports", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "package:errors", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003epackage:fmt:imports", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "package:fmt", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003epackage:net/url:imports", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "package:net/url", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003epackage:neuroforge/internal/core:imports", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "package:neuroforge/internal/core", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003epackage:regexp:imports", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "package:regexp", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003epackage:sort:imports", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "package:sort", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003epackage:strconv:imports", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "package:strconv", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003epackage:strings:imports", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "package:strings", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003epackage:unicode:imports", ++ "from": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "to": "package:unicode", ++ "kind": "imports" ++ }, + { + "id": "file:platform/neuroforge/internal/brain/v3.go-\u003efunction:neuroforge/internal/brain:Engine.RebalanceShards:defines", + "from": "file:platform/neuroforge/internal/brain/v3.go", +@@ -21036,6 +23321,12 @@ + "to": "function:neuroforge/internal/brain:defaultResearchTrust", + "kind": "defines" + }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/v8.go-\u003efunction:neuroforge/internal/brain:deterministicResearchQueries:defines", ++ "from": "file:platform/neuroforge/internal/brain/v8.go", ++ "to": "function:neuroforge/internal/brain:deterministicResearchQueries", ++ "kind": "defines" ++ }, + { + "id": "file:platform/neuroforge/internal/brain/v8.go-\u003efunction:neuroforge/internal/brain:firstNonEmpty:defines", + "from": "file:platform/neuroforge/internal/brain/v8.go", +@@ -21054,6 +23345,12 @@ + "to": "function:neuroforge/internal/brain:researchQueryUseful", + "kind": "defines" + }, ++ { ++ "id": "file:platform/neuroforge/internal/brain/v8.go-\u003efunction:neuroforge/internal/brain:researchResultsContainRelevant:defines", ++ "from": "file:platform/neuroforge/internal/brain/v8.go", ++ "to": "function:neuroforge/internal/brain:researchResultsContainRelevant", ++ "kind": "defines" ++ }, + { + "id": "file:platform/neuroforge/internal/brain/v8.go-\u003efunction:neuroforge/internal/brain:sourcePolicyKey:defines", + "from": "file:platform/neuroforge/internal/brain/v8.go", +@@ -21426,6 +23723,18 @@ + "to": "function:neuroforge/internal/httpapi:Server.integrationAuth", + "kind": "defines" + }, ++ { ++ "id": "file:platform/neuroforge/internal/httpapi/httpapi.go-\u003efunction:neuroforge/internal/httpapi:Server.integrationGraphStatus:defines", ++ "from": "file:platform/neuroforge/internal/httpapi/httpapi.go", ++ "to": "function:neuroforge/internal/httpapi:Server.integrationGraphStatus", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/httpapi/httpapi.go-\u003efunction:neuroforge/internal/httpapi:Server.integrationOrchestratorStatus:defines", ++ "from": "file:platform/neuroforge/internal/httpapi/httpapi.go", ++ "to": "function:neuroforge/internal/httpapi:Server.integrationOrchestratorStatus", ++ "kind": "defines" ++ }, + { + "id": "file:platform/neuroforge/internal/httpapi/httpapi.go-\u003efunction:neuroforge/internal/httpapi:Server.json:defines", + "from": "file:platform/neuroforge/internal/httpapi/httpapi.go", +@@ -21510,6 +23819,18 @@ + "to": "function:neuroforge/internal/httpapi:Server.workerComplete", + "kind": "defines" + }, ++ { ++ "id": "file:platform/neuroforge/internal/httpapi/httpapi.go-\u003efunction:neuroforge/internal/httpapi:Server.workerHeartbeat:defines", ++ "from": "file:platform/neuroforge/internal/httpapi/httpapi.go", ++ "to": "function:neuroforge/internal/httpapi:Server.workerHeartbeat", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/httpapi/httpapi.go-\u003efunction:neuroforge/internal/httpapi:Server.workerRegister:defines", ++ "from": "file:platform/neuroforge/internal/httpapi/httpapi.go", ++ "to": "function:neuroforge/internal/httpapi:Server.workerRegister", ++ "kind": "defines" ++ }, + { + "id": "file:platform/neuroforge/internal/httpapi/httpapi.go-\u003efunction:neuroforge/internal/httpapi:bearer:defines", + "from": "file:platform/neuroforge/internal/httpapi/httpapi.go", +@@ -21570,6 +23891,12 @@ + "to": "function:neuroforge/internal/httpapi:statusWriter.WriteHeader", + "kind": "defines" + }, ++ { ++ "id": "file:platform/neuroforge/internal/httpapi/httpapi.go-\u003efunction:neuroforge/internal/httpapi:workerHeartbeatFromRequest:defines", ++ "from": "file:platform/neuroforge/internal/httpapi/httpapi.go", ++ "to": "function:neuroforge/internal/httpapi:workerHeartbeatFromRequest", ++ "kind": "defines" ++ }, + { + "id": "file:platform/neuroforge/internal/httpapi/httpapi.go-\u003epackage:context:imports", + "from": "file:platform/neuroforge/internal/httpapi/httpapi.go", +@@ -21870,6 +24197,18 @@ + "to": "function:neuroforge/internal/httpapi:Server.adminGetLearningPolicy", + "kind": "defines" + }, ++ { ++ "id": "file:platform/neuroforge/internal/httpapi/knowledge.go-\u003efunction:neuroforge/internal/httpapi:Server.adminGraphBackfill:defines", ++ "from": "file:platform/neuroforge/internal/httpapi/knowledge.go", ++ "to": "function:neuroforge/internal/httpapi:Server.adminGraphBackfill", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/httpapi/knowledge.go-\u003efunction:neuroforge/internal/httpapi:Server.adminGraphStatus:defines", ++ "from": "file:platform/neuroforge/internal/httpapi/knowledge.go", ++ "to": "function:neuroforge/internal/httpapi:Server.adminGraphStatus", ++ "kind": "defines" ++ }, + { + "id": "file:platform/neuroforge/internal/httpapi/knowledge.go-\u003efunction:neuroforge/internal/httpapi:Server.adminKnowledgeEvents:defines", + "from": "file:platform/neuroforge/internal/httpapi/knowledge.go", +@@ -21906,6 +24245,30 @@ + "to": "function:neuroforge/internal/httpapi:Server.adminKnowledgeSummary", + "kind": "defines" + }, ++ { ++ "id": "file:platform/neuroforge/internal/httpapi/knowledge.go-\u003efunction:neuroforge/internal/httpapi:Server.adminOrchestratorCancel:defines", ++ "from": "file:platform/neuroforge/internal/httpapi/knowledge.go", ++ "to": "function:neuroforge/internal/httpapi:Server.adminOrchestratorCancel", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/httpapi/knowledge.go-\u003efunction:neuroforge/internal/httpapi:Server.adminOrchestratorJobs:defines", ++ "from": "file:platform/neuroforge/internal/httpapi/knowledge.go", ++ "to": "function:neuroforge/internal/httpapi:Server.adminOrchestratorJobs", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/httpapi/knowledge.go-\u003efunction:neuroforge/internal/httpapi:Server.adminOrchestratorRetry:defines", ++ "from": "file:platform/neuroforge/internal/httpapi/knowledge.go", ++ "to": "function:neuroforge/internal/httpapi:Server.adminOrchestratorRetry", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/httpapi/knowledge.go-\u003efunction:neuroforge/internal/httpapi:Server.adminOrchestratorStatus:defines", ++ "from": "file:platform/neuroforge/internal/httpapi/knowledge.go", ++ "to": "function:neuroforge/internal/httpapi:Server.adminOrchestratorStatus", ++ "kind": "defines" ++ }, + { + "id": "file:platform/neuroforge/internal/httpapi/knowledge.go-\u003efunction:neuroforge/internal/httpapi:Server.adminPutLearningPolicy:defines", + "from": "file:platform/neuroforge/internal/httpapi/knowledge.go", +@@ -22278,6 +24641,12 @@ + "to": "package:neuroforge/internal/core", + "kind": "imports" + }, ++ { ++ "id": "file:platform/neuroforge/internal/httpapi/v3.go-\u003epackage:neuroforge/internal/store:imports", ++ "from": "file:platform/neuroforge/internal/httpapi/v3.go", ++ "to": "package:neuroforge/internal/store", ++ "kind": "imports" ++ }, + { + "id": "file:platform/neuroforge/internal/httpapi/v3.go-\u003epackage:strconv:imports", + "from": "file:platform/neuroforge/internal/httpapi/v3.go", +@@ -22698,6 +25067,12 @@ + "to": "function:neuroforge/internal/provider:Router.Chat", + "kind": "defines" + }, ++ { ++ "id": "file:platform/neuroforge/internal/provider/provider.go-\u003efunction:neuroforge/internal/provider:Router.ChatJSONOn:defines", ++ "from": "file:platform/neuroforge/internal/provider/provider.go", ++ "to": "function:neuroforge/internal/provider:Router.ChatJSONOn", ++ "kind": "defines" ++ }, + { + "id": "file:platform/neuroforge/internal/provider/provider.go-\u003efunction:neuroforge/internal/provider:Router.ChatOn:defines", + "from": "file:platform/neuroforge/internal/provider/provider.go", +@@ -22728,6 +25103,12 @@ + "to": "function:neuroforge/internal/provider:Router.chatOllama", + "kind": "defines" + }, ++ { ++ "id": "file:platform/neuroforge/internal/provider/provider.go-\u003efunction:neuroforge/internal/provider:Router.chatOn:defines", ++ "from": "file:platform/neuroforge/internal/provider/provider.go", ++ "to": "function:neuroforge/internal/provider:Router.chatOn", ++ "kind": "defines" ++ }, + { + "id": "file:platform/neuroforge/internal/provider/provider.go-\u003efunction:neuroforge/internal/provider:Router.chatOpenAI:defines", + "from": "file:platform/neuroforge/internal/provider/provider.go", +@@ -23340,6 +25721,102 @@ + "to": "package:time", + "kind": "imports" + }, ++ { ++ "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003efunction:neuroforge/internal/store:Store.GraphBackfillCandidates:defines", ++ "from": "file:platform/neuroforge/internal/store/graph_index.go", ++ "to": "function:neuroforge/internal/store:Store.GraphBackfillCandidates", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003efunction:neuroforge/internal/store:Store.GraphDegree:defines", ++ "from": "file:platform/neuroforge/internal/store/graph_index.go", ++ "to": "function:neuroforge/internal/store:Store.GraphDegree", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003efunction:neuroforge/internal/store:Store.GraphStats:defines", ++ "from": "file:platform/neuroforge/internal/store/graph_index.go", ++ "to": "function:neuroforge/internal/store:Store.GraphStats", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003efunction:neuroforge/internal/store:Store.MarkGraphLinked:defines", ++ "from": "file:platform/neuroforge/internal/store/graph_index.go", ++ "to": "function:neuroforge/internal/store:Store.MarkGraphLinked", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003efunction:neuroforge/internal/store:Store.ReinforceRelation:defines", ++ "from": "file:platform/neuroforge/internal/store/graph_index.go", ++ "to": "function:neuroforge/internal/store:Store.ReinforceRelation", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003efunction:neuroforge/internal/store:Store.UpsertRelationEvidence:defines", ++ "from": "file:platform/neuroforge/internal/store/graph_index.go", ++ "to": "function:neuroforge/internal/store:Store.UpsertRelationEvidence", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003efunction:neuroforge/internal/store:Store.graphExpandLocked:defines", ++ "from": "file:platform/neuroforge/internal/store/graph_index.go", ++ "to": "function:neuroforge/internal/store:Store.graphExpandLocked", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003efunction:neuroforge/internal/store:Store.indexSynapseLocked:defines", ++ "from": "file:platform/neuroforge/internal/store/graph_index.go", ++ "to": "function:neuroforge/internal/store:Store.indexSynapseLocked", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003efunction:neuroforge/internal/store:Store.rebuildSynapseAdjLocked:defines", ++ "from": "file:platform/neuroforge/internal/store/graph_index.go", ++ "to": "function:neuroforge/internal/store:Store.rebuildSynapseAdjLocked", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003efunction:neuroforge/internal/store:Store.unindexSynapseLocked:defines", ++ "from": "file:platform/neuroforge/internal/store/graph_index.go", ++ "to": "function:neuroforge/internal/store:Store.unindexSynapseLocked", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003efunction:neuroforge/internal/store:appendUniqueRelation:defines", ++ "from": "file:platform/neuroforge/internal/store/graph_index.go", ++ "to": "function:neuroforge/internal/store:appendUniqueRelation", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003epackage:neuroforge/internal/core:imports", ++ "from": "file:platform/neuroforge/internal/store/graph_index.go", ++ "to": "package:neuroforge/internal/core", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003epackage:neuroforge/internal/vector:imports", ++ "from": "file:platform/neuroforge/internal/store/graph_index.go", ++ "to": "package:neuroforge/internal/vector", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003epackage:sort:imports", ++ "from": "file:platform/neuroforge/internal/store/graph_index.go", ++ "to": "package:sort", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003epackage:strings:imports", ++ "from": "file:platform/neuroforge/internal/store/graph_index.go", ++ "to": "package:strings", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003epackage:time:imports", ++ "from": "file:platform/neuroforge/internal/store/graph_index.go", ++ "to": "package:time", ++ "kind": "imports" ++ }, + { + "id": "file:platform/neuroforge/internal/store/index_segments.go-\u003efunction:neuroforge/internal/store:Store.CompactIndexSegments:defines", + "from": "file:platform/neuroforge/internal/store/index_segments.go", +@@ -23640,6 +26117,210 @@ + "to": "package:time", + "kind": "imports" + }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.CancelJob:defines", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "function:neuroforge/internal/store:Store.CancelJob", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.ClaimJob:defines", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "function:neuroforge/internal/store:Store.ClaimJob", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.ClaimJobForWorker:defines", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "function:neuroforge/internal/store:Store.ClaimJobForWorker", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.CompleteJob:defines", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "function:neuroforge/internal/store:Store.CompleteJob", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.CompleteJobLease:defines", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "function:neuroforge/internal/store:Store.CompleteJobLease", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.EnqueueJob:defines", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "function:neuroforge/internal/store:Store.EnqueueJob", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.EnqueueJobSpec:defines", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "function:neuroforge/internal/store:Store.EnqueueJobSpec", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.FinishMasterApply:defines", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "function:neuroforge/internal/store:Store.FinishMasterApply", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.HasLiveWorker:defines", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "function:neuroforge/internal/store:Store.HasLiveWorker", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.HeartbeatWorker:defines", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "function:neuroforge/internal/store:Store.HeartbeatWorker", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.IsOrchestratorLeader:defines", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "function:neuroforge/internal/store:Store.IsOrchestratorLeader", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.Job:defines", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "function:neuroforge/internal/store:Store.Job", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.JobsSnapshot:defines", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "function:neuroforge/internal/store:Store.JobsSnapshot", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.OrchestratorStatus:defines", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "function:neuroforge/internal/store:Store.OrchestratorStatus", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.PendingJobCount:defines", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "function:neuroforge/internal/store:Store.PendingJobCount", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.PendingMasterApplyJobs:defines", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "function:neuroforge/internal/store:Store.PendingMasterApplyJobs", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.PruneTerminalJobs:defines", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "function:neuroforge/internal/store:Store.PruneTerminalJobs", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.RegisterWorker:defines", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "function:neuroforge/internal/store:Store.RegisterWorker", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.RetryJob:defines", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "function:neuroforge/internal/store:Store.RetryJob", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.WorkersSnapshot:defines", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "function:neuroforge/internal/store:Store.WorkersSnapshot", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.dependencyStateLocked:defines", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "function:neuroforge/internal/store:Store.dependencyStateLocked", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.normalizeJobForClaimLocked:defines", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "function:neuroforge/internal/store:Store.normalizeJobForClaimLocked", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.registerWorkerLocked:defines", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "function:neuroforge/internal/store:Store.registerWorkerLocked", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:capabilitiesContain:defines", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "function:neuroforge/internal/store:capabilitiesContain", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:maxInt:defines", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "function:neuroforge/internal/store:maxInt", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:normalizeCapabilities:defines", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "function:neuroforge/internal/store:normalizeCapabilities", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:workerMatchesJob:defines", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "function:neuroforge/internal/store:workerMatchesJob", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003epackage:encoding/json:imports", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "package:encoding/json", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003epackage:errors:imports", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "package:errors", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003epackage:fmt:imports", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "package:fmt", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003epackage:neuroforge/internal/core:imports", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "package:neuroforge/internal/core", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003epackage:sort:imports", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "package:sort", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003epackage:strings:imports", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "package:strings", ++ "kind": "imports" ++ }, ++ { ++ "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003epackage:time:imports", ++ "from": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "to": "package:time", ++ "kind": "imports" ++ }, + { + "id": "file:platform/neuroforge/internal/store/pagecache.go-\u003efunction:neuroforge/internal/store:MemoryPageCache.Delete:defines", + "from": "file:platform/neuroforge/internal/store/pagecache.go", +@@ -24468,12 +27149,6 @@ + "to": "function:neuroforge/internal/store:Store.AddUsage", + "kind": "defines" + }, +- { +- "id": "file:platform/neuroforge/internal/store/store.go-\u003efunction:neuroforge/internal/store:Store.ClaimJob:defines", +- "from": "file:platform/neuroforge/internal/store/store.go", +- "to": "function:neuroforge/internal/store:Store.ClaimJob", +- "kind": "defines" +- }, + { + "id": "file:platform/neuroforge/internal/store/store.go-\u003efunction:neuroforge/internal/store:Store.Close:defines", + "from": "file:platform/neuroforge/internal/store/store.go", +@@ -24486,12 +27161,6 @@ + "to": "function:neuroforge/internal/store:Store.CompactMemorySegments", + "kind": "defines" + }, +- { +- "id": "file:platform/neuroforge/internal/store/store.go-\u003efunction:neuroforge/internal/store:Store.CompleteJob:defines", +- "from": "file:platform/neuroforge/internal/store/store.go", +- "to": "function:neuroforge/internal/store:Store.CompleteJob", +- "kind": "defines" +- }, + { + "id": "file:platform/neuroforge/internal/store/store.go-\u003efunction:neuroforge/internal/store:Store.Config:defines", + "from": "file:platform/neuroforge/internal/store/store.go", +@@ -24516,12 +27185,6 @@ + "to": "function:neuroforge/internal/store:Store.DeleteMemory", + "kind": "defines" + }, +- { +- "id": "file:platform/neuroforge/internal/store/store.go-\u003efunction:neuroforge/internal/store:Store.EnqueueJob:defines", +- "from": "file:platform/neuroforge/internal/store/store.go", +- "to": "function:neuroforge/internal/store:Store.EnqueueJob", +- "kind": "defines" +- }, + { + "id": "file:platform/neuroforge/internal/store/store.go-\u003efunction:neuroforge/internal/store:Store.ExportSafe:defines", + "from": "file:platform/neuroforge/internal/store/store.go", +@@ -25056,6 +27719,12 @@ + "to": "function:neuroforge/internal/store:cloneGoal", + "kind": "defines" + }, ++ { ++ "id": "file:platform/neuroforge/internal/store/v3.go-\u003efunction:neuroforge/internal/store:goalFingerprint:defines", ++ "from": "file:platform/neuroforge/internal/store/v3.go", ++ "to": "function:neuroforge/internal/store:goalFingerprint", ++ "kind": "defines" ++ }, + { + "id": "file:platform/neuroforge/internal/store/v3.go-\u003efunction:neuroforge/internal/store:knowledgeScore:defines", + "from": "file:platform/neuroforge/internal/store/v3.go", +@@ -31446,6 +34115,18 @@ + "to": "function:mega-control:server.handleConfig", + "kind": "defines" + }, ++ { ++ "id": "file:services/control/main.go-\u003efunction:mega-control:server.handleNeuroForgeGraphStatus:defines", ++ "from": "file:services/control/main.go", ++ "to": "function:mega-control:server.handleNeuroForgeGraphStatus", ++ "kind": "defines" ++ }, ++ { ++ "id": "file:services/control/main.go-\u003efunction:mega-control:server.handleNeuroForgeOrchestrator:defines", ++ "from": "file:services/control/main.go", ++ "to": "function:mega-control:server.handleNeuroForgeOrchestrator", ++ "kind": "defines" ++ }, + { + "id": "file:services/control/main.go-\u003efunction:mega-control:server.handleStatus:defines", + "from": "file:services/control/main.go", +@@ -46291,6 +48972,30 @@ + "kind": "calls_package", + "label": "Itoa" + }, ++ { ++ "id": "function:mega-control:server.handleNeuroForgeGraphStatus-\u003efunction:mega-control:bearerHeader:calls", ++ "from": "function:mega-control:server.handleNeuroForgeGraphStatus", ++ "to": "function:mega-control:bearerHeader", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:mega-control:server.handleNeuroForgeGraphStatus-\u003efunction:mega-control:server.proxyJSON:calls", ++ "from": "function:mega-control:server.handleNeuroForgeGraphStatus", ++ "to": "function:mega-control:server.proxyJSON", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:mega-control:server.handleNeuroForgeOrchestrator-\u003efunction:mega-control:bearerHeader:calls", ++ "from": "function:mega-control:server.handleNeuroForgeOrchestrator", ++ "to": "function:mega-control:bearerHeader", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:mega-control:server.handleNeuroForgeOrchestrator-\u003efunction:mega-control:server.proxyJSON:calls", ++ "from": "function:mega-control:server.handleNeuroForgeOrchestrator", ++ "to": "function:mega-control:server.proxyJSON", ++ "kind": "calls" ++ }, + { + "id": "function:mega-control:server.handleResearchGraph-\u003efunction:mega-control:bearerHeader:calls", + "from": "function:mega-control:server.handleResearchGraph", +@@ -46590,6 +49295,27 @@ + "kind": "calls_package", + "label": "TrimSpace" + }, ++ { ++ "id": "function:neuroforge/cmd/server:envFloat-\u003epackage:os:calls_package", ++ "from": "function:neuroforge/cmd/server:envFloat", ++ "to": "package:os", ++ "kind": "calls_package", ++ "label": "LookupEnv" ++ }, ++ { ++ "id": "function:neuroforge/cmd/server:envFloat-\u003epackage:strconv:calls_package", ++ "from": "function:neuroforge/cmd/server:envFloat", ++ "to": "package:strconv", ++ "kind": "calls_package", ++ "label": "ParseFloat" ++ }, ++ { ++ "id": "function:neuroforge/cmd/server:envFloat-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/cmd/server:envFloat", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, + { + "id": "function:neuroforge/cmd/server:envInt-\u003epackage:os:calls_package", + "from": "function:neuroforge/cmd/server:envInt", +@@ -46611,6 +49337,13 @@ + "kind": "calls_package", + "label": "TrimSpace" + }, ++ { ++ "id": "function:neuroforge/cmd/server:firstNonEmptyMain-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/cmd/server:firstNonEmptyMain", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, + { + "id": "function:neuroforge/cmd/server:main-\u003efunction:neuroforge/cmd/server:run:calls", + "from": "function:neuroforge/cmd/server:main", +@@ -46637,12 +49370,24 @@ + "to": "function:neuroforge/cmd/server:envBool", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/cmd/server:run-\u003efunction:neuroforge/cmd/server:envFloat:calls", ++ "from": "function:neuroforge/cmd/server:run", ++ "to": "function:neuroforge/cmd/server:envFloat", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/cmd/server:run-\u003efunction:neuroforge/cmd/server:envInt:calls", + "from": "function:neuroforge/cmd/server:run", + "to": "function:neuroforge/cmd/server:envInt", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/cmd/server:run-\u003efunction:neuroforge/cmd/server:firstNonEmptyMain:calls", ++ "from": "function:neuroforge/cmd/server:run", ++ "to": "function:neuroforge/cmd/server:firstNonEmptyMain", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/cmd/server:run-\u003efunction:neuroforge/cmd/server:maxIntMain:calls", + "from": "function:neuroforge/cmd/server:run", +@@ -46787,6 +49532,12 @@ + "kind": "calls_package", + "label": "Getenv" + }, ++ { ++ "id": "function:neuroforge/cmd/worker:claim-\u003efunction:neuroforge/cmd/worker:workerBody:calls", ++ "from": "function:neuroforge/cmd/worker:claim", ++ "to": "function:neuroforge/cmd/worker:workerBody", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/cmd/worker:claim-\u003epackage:bytes:calls_package", + "from": "function:neuroforge/cmd/worker:claim", +@@ -46823,53 +49574,112 @@ + "label": "NewRequest" + }, + { +- "id": "function:neuroforge/cmd/worker:claim-\u003epackage:strings:calls_package", +- "from": "function:neuroforge/cmd/worker:claim", ++ "id": "function:neuroforge/cmd/worker:complete-\u003efunction:neuroforge/cmd/worker:postJSON:calls", ++ "from": "function:neuroforge/cmd/worker:complete", ++ "to": "function:neuroforge/cmd/worker:postJSON", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:csv-\u003epackage:sort:calls_package", ++ "from": "function:neuroforge/cmd/worker:csv", ++ "to": "package:sort", ++ "kind": "calls_package", ++ "label": "Strings" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:csv-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/cmd/worker:csv", + "to": "package:strings", + "kind": "calls_package", +- "label": "TrimRight" ++ "label": "Split" + }, + { +- "id": "function:neuroforge/cmd/worker:complete-\u003epackage:bytes:calls_package", +- "from": "function:neuroforge/cmd/worker:complete", +- "to": "package:bytes", ++ "id": "function:neuroforge/cmd/worker:envDuration-\u003epackage:os:calls_package", ++ "from": "function:neuroforge/cmd/worker:envDuration", ++ "to": "package:os", + "kind": "calls_package", +- "label": "NewReader" ++ "label": "Getenv" + }, + { +- "id": "function:neuroforge/cmd/worker:complete-\u003epackage:encoding/json:calls_package", +- "from": "function:neuroforge/cmd/worker:complete", +- "to": "package:encoding/json", ++ "id": "function:neuroforge/cmd/worker:envDuration-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/cmd/worker:envDuration", ++ "to": "package:strings", + "kind": "calls_package", +- "label": "Marshal" ++ "label": "TrimSpace" + }, + { +- "id": "function:neuroforge/cmd/worker:complete-\u003epackage:fmt:calls_package", +- "from": "function:neuroforge/cmd/worker:complete", +- "to": "package:fmt", ++ "id": "function:neuroforge/cmd/worker:envDuration-\u003epackage:time:calls_package", ++ "from": "function:neuroforge/cmd/worker:envDuration", ++ "to": "package:time", + "kind": "calls_package", +- "label": "Errorf" ++ "label": "ParseDuration" + }, + { +- "id": "function:neuroforge/cmd/worker:complete-\u003epackage:io:calls_package", +- "from": "function:neuroforge/cmd/worker:complete", +- "to": "package:io", ++ "id": "function:neuroforge/cmd/worker:envInt-\u003epackage:os:calls_package", ++ "from": "function:neuroforge/cmd/worker:envInt", ++ "to": "package:os", + "kind": "calls_package", +- "label": "ReadAll" ++ "label": "Getenv" + }, + { +- "id": "function:neuroforge/cmd/worker:complete-\u003epackage:net/http:calls_package", +- "from": "function:neuroforge/cmd/worker:complete", +- "to": "package:net/http", ++ "id": "function:neuroforge/cmd/worker:envInt-\u003epackage:strconv:calls_package", ++ "from": "function:neuroforge/cmd/worker:envInt", ++ "to": "package:strconv", + "kind": "calls_package", +- "label": "NewRequest" ++ "label": "Atoi" + }, + { +- "id": "function:neuroforge/cmd/worker:complete-\u003epackage:strings:calls_package", +- "from": "function:neuroforge/cmd/worker:complete", ++ "id": "function:neuroforge/cmd/worker:envInt-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/cmd/worker:envInt", + "to": "package:strings", + "kind": "calls_package", +- "label": "TrimRight" ++ "label": "TrimSpace" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:firstNonEmpty-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/cmd/worker:firstNonEmpty", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:hasCap-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/cmd/worker:hasCap", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "ToLower" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:heartbeatLoop-\u003efunction:neuroforge/cmd/worker:leaseSet.snapshot:calls", ++ "from": "function:neuroforge/cmd/worker:heartbeatLoop", ++ "to": "function:neuroforge/cmd/worker:leaseSet.snapshot", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:heartbeatLoop-\u003efunction:neuroforge/cmd/worker:postJSON:calls", ++ "from": "function:neuroforge/cmd/worker:heartbeatLoop", ++ "to": "function:neuroforge/cmd/worker:postJSON", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:heartbeatLoop-\u003efunction:neuroforge/cmd/worker:workerBody:calls", ++ "from": "function:neuroforge/cmd/worker:heartbeatLoop", ++ "to": "function:neuroforge/cmd/worker:workerBody", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:heartbeatLoop-\u003epackage:log:calls_package", ++ "from": "function:neuroforge/cmd/worker:heartbeatLoop", ++ "to": "package:log", ++ "kind": "calls_package", ++ "label": "Printf" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:heartbeatLoop-\u003epackage:time:calls_package", ++ "from": "function:neuroforge/cmd/worker:heartbeatLoop", ++ "to": "package:time", ++ "kind": "calls_package", ++ "label": "NewTicker" + }, + { + "id": "function:neuroforge/cmd/worker:hostname-\u003epackage:os:calls_package", +@@ -46879,15 +49689,40 @@ + "label": "Hostname" + }, + { +- "id": "function:neuroforge/cmd/worker:main-\u003efunction:neuroforge/cmd/worker:claim:calls", ++ "id": "function:neuroforge/cmd/worker:lowerDefault-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/cmd/worker:lowerDefault", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "ToLower" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:main-\u003efunction:neuroforge/cmd/worker:csv:calls", + "from": "function:neuroforge/cmd/worker:main", +- "to": "function:neuroforge/cmd/worker:claim", ++ "to": "function:neuroforge/cmd/worker:csv", + "kind": "calls" + }, + { +- "id": "function:neuroforge/cmd/worker:main-\u003efunction:neuroforge/cmd/worker:complete:calls", ++ "id": "function:neuroforge/cmd/worker:main-\u003efunction:neuroforge/cmd/worker:envDuration:calls", + "from": "function:neuroforge/cmd/worker:main", +- "to": "function:neuroforge/cmd/worker:complete", ++ "to": "function:neuroforge/cmd/worker:envDuration", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:main-\u003efunction:neuroforge/cmd/worker:envInt:calls", ++ "from": "function:neuroforge/cmd/worker:main", ++ "to": "function:neuroforge/cmd/worker:envInt", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:main-\u003efunction:neuroforge/cmd/worker:firstNonEmpty:calls", ++ "from": "function:neuroforge/cmd/worker:main", ++ "to": "function:neuroforge/cmd/worker:firstNonEmpty", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:main-\u003efunction:neuroforge/cmd/worker:heartbeatLoop:calls", ++ "from": "function:neuroforge/cmd/worker:main", ++ "to": "function:neuroforge/cmd/worker:heartbeatLoop", + "kind": "calls" + }, + { +@@ -46897,11 +49732,30 @@ + "kind": "calls" + }, + { +- "id": "function:neuroforge/cmd/worker:main-\u003efunction:neuroforge/cmd/worker:run:calls", ++ "id": "function:neuroforge/cmd/worker:main-\u003efunction:neuroforge/cmd/worker:lowerDefault:calls", + "from": "function:neuroforge/cmd/worker:main", +- "to": "function:neuroforge/cmd/worker:run", ++ "to": "function:neuroforge/cmd/worker:lowerDefault", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/cmd/worker:main-\u003efunction:neuroforge/cmd/worker:pollLoop:calls", ++ "from": "function:neuroforge/cmd/worker:main", ++ "to": "function:neuroforge/cmd/worker:pollLoop", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:main-\u003efunction:neuroforge/cmd/worker:register:calls", ++ "from": "function:neuroforge/cmd/worker:main", ++ "to": "function:neuroforge/cmd/worker:register", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:main-\u003epackage:context:calls_package", ++ "from": "function:neuroforge/cmd/worker:main", ++ "to": "package:context", ++ "kind": "calls_package", ++ "label": "Background" ++ }, + { + "id": "function:neuroforge/cmd/worker:main-\u003epackage:flag:calls_package", + "from": "function:neuroforge/cmd/worker:main", +@@ -46916,6 +49770,13 @@ + "kind": "calls_package", + "label": "Fatal" + }, ++ { ++ "id": "function:neuroforge/cmd/worker:main-\u003epackage:os/signal:calls_package", ++ "from": "function:neuroforge/cmd/worker:main", ++ "to": "package:os/signal", ++ "kind": "calls_package", ++ "label": "NotifyContext" ++ }, + { + "id": "function:neuroforge/cmd/worker:main-\u003epackage:os:calls_package", + "from": "function:neuroforge/cmd/worker:main", +@@ -46931,11 +49792,227 @@ + "label": "TrimSpace" + }, + { +- "id": "function:neuroforge/cmd/worker:main-\u003epackage:time:calls_package", +- "from": "function:neuroforge/cmd/worker:main", ++ "id": "function:neuroforge/cmd/worker:ollamaChat-\u003efunction:neuroforge/cmd/worker:firstNonEmpty:calls", ++ "from": "function:neuroforge/cmd/worker:ollamaChat", ++ "to": "function:neuroforge/cmd/worker:firstNonEmpty", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:ollamaChat-\u003efunction:neuroforge/cmd/worker:postOllama:calls", ++ "from": "function:neuroforge/cmd/worker:ollamaChat", ++ "to": "function:neuroforge/cmd/worker:postOllama", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:ollamaChat-\u003epackage:fmt:calls_package", ++ "from": "function:neuroforge/cmd/worker:ollamaChat", ++ "to": "package:fmt", ++ "kind": "calls_package", ++ "label": "Errorf" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:ollamaChat-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/cmd/worker:ollamaChat", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:ollamaEmbed-\u003efunction:neuroforge/cmd/worker:firstNonEmpty:calls", ++ "from": "function:neuroforge/cmd/worker:ollamaEmbed", ++ "to": "function:neuroforge/cmd/worker:firstNonEmpty", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:ollamaEmbed-\u003efunction:neuroforge/cmd/worker:postOllama:calls", ++ "from": "function:neuroforge/cmd/worker:ollamaEmbed", ++ "to": "function:neuroforge/cmd/worker:postOllama", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:ollamaEmbed-\u003epackage:fmt:calls_package", ++ "from": "function:neuroforge/cmd/worker:ollamaEmbed", ++ "to": "package:fmt", ++ "kind": "calls_package", ++ "label": "Errorf" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:ollamaEmbed-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/cmd/worker:ollamaEmbed", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:pollLoop-\u003efunction:neuroforge/cmd/worker:claim:calls", ++ "from": "function:neuroforge/cmd/worker:pollLoop", ++ "to": "function:neuroforge/cmd/worker:claim", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:pollLoop-\u003efunction:neuroforge/cmd/worker:complete:calls", ++ "from": "function:neuroforge/cmd/worker:pollLoop", ++ "to": "function:neuroforge/cmd/worker:complete", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:pollLoop-\u003efunction:neuroforge/cmd/worker:leaseSet.add:calls", ++ "from": "function:neuroforge/cmd/worker:pollLoop", ++ "to": "function:neuroforge/cmd/worker:leaseSet.add", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:pollLoop-\u003efunction:neuroforge/cmd/worker:leaseSet.del:calls", ++ "from": "function:neuroforge/cmd/worker:pollLoop", ++ "to": "function:neuroforge/cmd/worker:leaseSet.del", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:pollLoop-\u003efunction:neuroforge/cmd/worker:run:calls", ++ "from": "function:neuroforge/cmd/worker:pollLoop", ++ "to": "function:neuroforge/cmd/worker:run", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:pollLoop-\u003efunction:neuroforge/cmd/worker:waitOrDone:calls", ++ "from": "function:neuroforge/cmd/worker:pollLoop", ++ "to": "function:neuroforge/cmd/worker:waitOrDone", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:pollLoop-\u003epackage:context:calls_package", ++ "from": "function:neuroforge/cmd/worker:pollLoop", ++ "to": "package:context", ++ "kind": "calls_package", ++ "label": "WithTimeout" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:pollLoop-\u003epackage:log:calls_package", ++ "from": "function:neuroforge/cmd/worker:pollLoop", ++ "to": "package:log", ++ "kind": "calls_package", ++ "label": "Printf" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:pollLoop-\u003epackage:time:calls_package", ++ "from": "function:neuroforge/cmd/worker:pollLoop", + "to": "package:time", + "kind": "calls_package", +- "label": "Sleep" ++ "label": "Duration" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:postJSON-\u003epackage:bytes:calls_package", ++ "from": "function:neuroforge/cmd/worker:postJSON", ++ "to": "package:bytes", ++ "kind": "calls_package", ++ "label": "NewReader" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:postJSON-\u003epackage:encoding/json:calls_package", ++ "from": "function:neuroforge/cmd/worker:postJSON", ++ "to": "package:encoding/json", ++ "kind": "calls_package", ++ "label": "Marshal" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:postJSON-\u003epackage:fmt:calls_package", ++ "from": "function:neuroforge/cmd/worker:postJSON", ++ "to": "package:fmt", ++ "kind": "calls_package", ++ "label": "Errorf" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:postJSON-\u003epackage:io:calls_package", ++ "from": "function:neuroforge/cmd/worker:postJSON", ++ "to": "package:io", ++ "kind": "calls_package", ++ "label": "ReadAll" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:postJSON-\u003epackage:net/http:calls_package", ++ "from": "function:neuroforge/cmd/worker:postJSON", ++ "to": "package:net/http", ++ "kind": "calls_package", ++ "label": "NewRequest" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:postJSON-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/cmd/worker:postJSON", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:postOllama-\u003epackage:bytes:calls_package", ++ "from": "function:neuroforge/cmd/worker:postOllama", ++ "to": "package:bytes", ++ "kind": "calls_package", ++ "label": "NewReader" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:postOllama-\u003epackage:encoding/json:calls_package", ++ "from": "function:neuroforge/cmd/worker:postOllama", ++ "to": "package:encoding/json", ++ "kind": "calls_package", ++ "label": "Marshal" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:postOllama-\u003epackage:fmt:calls_package", ++ "from": "function:neuroforge/cmd/worker:postOllama", ++ "to": "package:fmt", ++ "kind": "calls_package", ++ "label": "Errorf" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:postOllama-\u003epackage:io:calls_package", ++ "from": "function:neuroforge/cmd/worker:postOllama", ++ "to": "package:io", ++ "kind": "calls_package", ++ "label": "ReadAll" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:postOllama-\u003epackage:net/http:calls_package", ++ "from": "function:neuroforge/cmd/worker:postOllama", ++ "to": "package:net/http", ++ "kind": "calls_package", ++ "label": "NewRequestWithContext" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:postOllama-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/cmd/worker:postOllama", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:register-\u003efunction:neuroforge/cmd/worker:postJSON:calls", ++ "from": "function:neuroforge/cmd/worker:register", ++ "to": "function:neuroforge/cmd/worker:postJSON", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:register-\u003efunction:neuroforge/cmd/worker:workerBody:calls", ++ "from": "function:neuroforge/cmd/worker:register", ++ "to": "function:neuroforge/cmd/worker:workerBody", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:run-\u003efunction:neuroforge/cmd/worker:hasCap:calls", ++ "from": "function:neuroforge/cmd/worker:run", ++ "to": "function:neuroforge/cmd/worker:hasCap", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:run-\u003efunction:neuroforge/cmd/worker:ollamaChat:calls", ++ "from": "function:neuroforge/cmd/worker:run", ++ "to": "function:neuroforge/cmd/worker:ollamaChat", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/cmd/worker:run-\u003efunction:neuroforge/cmd/worker:ollamaEmbed:calls", ++ "from": "function:neuroforge/cmd/worker:run", ++ "to": "function:neuroforge/cmd/worker:ollamaEmbed", ++ "kind": "calls" + }, + { + "id": "function:neuroforge/cmd/worker:run-\u003epackage:encoding/json:calls_package", +@@ -46959,9 +50036,29 @@ + "label": "Slice" + }, + { +- "id": "function:neuroforge/internal/brain:Engine.ApplyJobResult-\u003efunction:neuroforge/internal/brain:Engine.reinforcePair:calls", ++ "id": "function:neuroforge/cmd/worker:waitOrDone-\u003epackage:time:calls_package", ++ "from": "function:neuroforge/cmd/worker:waitOrDone", ++ "to": "package:time", ++ "kind": "calls_package", ++ "label": "NewTimer" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.ApplyAndFinalizeJob-\u003efunction:neuroforge/internal/brain:Engine.ApplyJobResult:calls", ++ "from": "function:neuroforge/internal/brain:Engine.ApplyAndFinalizeJob", ++ "to": "function:neuroforge/internal/brain:Engine.ApplyJobResult", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.ApplyAndFinalizeJob-\u003epackage:errors:calls_package", ++ "from": "function:neuroforge/internal/brain:Engine.ApplyAndFinalizeJob", ++ "to": "package:errors", ++ "kind": "calls_package", ++ "label": "Is" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.ApplyJobResult-\u003efunction:neuroforge/internal/brain:vectorFingerprint:calls", + "from": "function:neuroforge/internal/brain:Engine.ApplyJobResult", +- "to": "function:neuroforge/internal/brain:Engine.reinforcePair", ++ "to": "function:neuroforge/internal/brain:vectorFingerprint", + "kind": "calls" + }, + { +@@ -46971,6 +50068,13 @@ + "kind": "calls_package", + "label": "Unmarshal" + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.ApplyJobResult-\u003epackage:fmt:calls_package", ++ "from": "function:neuroforge/internal/brain:Engine.ApplyJobResult", ++ "to": "package:fmt", ++ "kind": "calls_package", ++ "label": "Errorf" ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.ApplyJobResult-\u003epackage:sort:calls_package", + "from": "function:neuroforge/internal/brain:Engine.ApplyJobResult", +@@ -46978,6 +50082,12 @@ + "kind": "calls_package", + "label": "Slice" + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.ApplyPendingJobResults-\u003efunction:neuroforge/internal/brain:Engine.ApplyAndFinalizeJob:calls", ++ "from": "function:neuroforge/internal/brain:Engine.ApplyPendingJobResults", ++ "to": "function:neuroforge/internal/brain:Engine.ApplyAndFinalizeJob", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.Chat-\u003efunction:neuroforge/internal/brain:Engine.addMemory:calls", + "from": "function:neuroforge/internal/brain:Engine.Chat", +@@ -47142,6 +50252,25 @@ + "kind": "calls_package", + "label": "NewID" + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.ConfigureStagingPublisher-\u003efunction:neuroforge/internal/brain:dedupeStrings:calls", ++ "from": "function:neuroforge/internal/brain:Engine.ConfigureStagingPublisher", ++ "to": "function:neuroforge/internal/brain:dedupeStrings", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.ConfigureStagingPublisher-\u003efunction:neuroforge/internal/brain:normalizeAuthoritativeDomain:calls", ++ "from": "function:neuroforge/internal/brain:Engine.ConfigureStagingPublisher", ++ "to": "function:neuroforge/internal/brain:normalizeAuthoritativeDomain", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.ConfigureStagingPublisher-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/brain:Engine.ConfigureStagingPublisher", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "ToLower" ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.Consolidate-\u003efunction:neuroforge/internal/brain:Engine.SearchVector:calls", + "from": "function:neuroforge/internal/brain:Engine.Consolidate", +@@ -47423,6 +50552,19 @@ + "kind": "calls_package", + "label": "TrimSpace" + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.PlanGraphBackfill-\u003efunction:neuroforge/internal/brain:Engine.enqueueRelink:calls", ++ "from": "function:neuroforge/internal/brain:Engine.PlanGraphBackfill", ++ "to": "function:neuroforge/internal/brain:Engine.enqueueRelink", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.PlanGraphBackfill-\u003epackage:time:calls_package", ++ "from": "function:neuroforge/internal/brain:Engine.PlanGraphBackfill", ++ "to": "package:time", ++ "kind": "calls_package", ++ "label": "Duration" ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.RebalanceShards-\u003efunction:neuroforge/internal/brain:Engine.replicateMemoryToShard:calls", + "from": "function:neuroforge/internal/brain:Engine.RebalanceShards", +@@ -47520,6 +50662,12 @@ + "to": "function:neuroforge/internal/brain:Engine.ingestText", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.Research-\u003efunction:neuroforge/internal/brain:Engine.stagingConfig:calls", ++ "from": "function:neuroforge/internal/brain:Engine.Research", ++ "to": "function:neuroforge/internal/brain:Engine.stagingConfig", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.Research-\u003efunction:neuroforge/internal/brain:New:calls", + "from": "function:neuroforge/internal/brain:Engine.Research", +@@ -47538,6 +50686,18 @@ + "to": "function:neuroforge/internal/brain:firstNonEmpty", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.Research-\u003efunction:neuroforge/internal/brain:researchMaterialRelevant:calls", ++ "from": "function:neuroforge/internal/brain:Engine.Research", ++ "to": "function:neuroforge/internal/brain:researchMaterialRelevant", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.Research-\u003efunction:neuroforge/internal/brain:researchResultsContainRelevant:calls", ++ "from": "function:neuroforge/internal/brain:Engine.Research", ++ "to": "function:neuroforge/internal/brain:researchResultsContainRelevant", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.Research-\u003efunction:neuroforge/internal/brain:researchTrace.emit:calls", + "from": "function:neuroforge/internal/brain:Engine.Research", +@@ -47550,6 +50710,12 @@ + "to": "function:neuroforge/internal/brain:shortPreview", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.Research-\u003efunction:neuroforge/internal/brain:sourceAuthorityFor:calls", ++ "from": "function:neuroforge/internal/brain:Engine.Research", ++ "to": "function:neuroforge/internal/brain:sourceAuthorityFor", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.Research-\u003epackage:fmt:calls_package", + "from": "function:neuroforge/internal/brain:Engine.Research", +@@ -47564,6 +50730,13 @@ + "kind": "calls_package", + "label": "ResultLooksLikeDocument" + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.Research-\u003epackage:sort:calls_package", ++ "from": "function:neuroforge/internal/brain:Engine.Research", ++ "to": "package:sort", ++ "kind": "calls_package", ++ "label": "SliceStable" ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.Research-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:Engine.Research", +@@ -47790,6 +50963,25 @@ + "kind": "calls_package", + "label": "NewTicker" + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.RunOrchestrator-\u003efunction:neuroforge/internal/brain:Engine.ApplyPendingJobResults:calls", ++ "from": "function:neuroforge/internal/brain:Engine.RunOrchestrator", ++ "to": "function:neuroforge/internal/brain:Engine.ApplyPendingJobResults", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.RunOrchestrator-\u003efunction:neuroforge/internal/brain:Engine.PlanGraphBackfill:calls", ++ "from": "function:neuroforge/internal/brain:Engine.RunOrchestrator", ++ "to": "function:neuroforge/internal/brain:Engine.PlanGraphBackfill", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.RunOrchestrator-\u003epackage:time:calls_package", ++ "from": "function:neuroforge/internal/brain:Engine.RunOrchestrator", ++ "to": "package:time", ++ "kind": "calls_package", ++ "label": "NewTimer" ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.RunV3Maintenance-\u003efunction:neuroforge/internal/brain:Engine.Consolidate:calls", + "from": "function:neuroforge/internal/brain:Engine.RunV3Maintenance", +@@ -48002,12 +51194,30 @@ + "to": "function:neuroforge/internal/brain:Engine.chatModelLimit", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.chatModelJSONLimitOn-\u003efunction:neuroforge/internal/brain:Engine.chatModelLimitOnMode:calls", ++ "from": "function:neuroforge/internal/brain:Engine.chatModelJSONLimitOn", ++ "to": "function:neuroforge/internal/brain:Engine.chatModelLimitOnMode", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.chatModelLimit-\u003efunction:neuroforge/internal/brain:Engine.chatModelLimitOn:calls", + "from": "function:neuroforge/internal/brain:Engine.chatModelLimit", + "to": "function:neuroforge/internal/brain:Engine.chatModelLimitOn", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.chatModelLimitOn-\u003efunction:neuroforge/internal/brain:Engine.chatModelLimitOnMode:calls", ++ "from": "function:neuroforge/internal/brain:Engine.chatModelLimitOn", ++ "to": "function:neuroforge/internal/brain:Engine.chatModelLimitOnMode", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.chatModelLimitOnMode-\u003efunction:neuroforge/internal/brain:Engine.distributedChat:calls", ++ "from": "function:neuroforge/internal/brain:Engine.chatModelLimitOnMode", ++ "to": "function:neuroforge/internal/brain:Engine.distributedChat", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.clusterPost-\u003epackage:bytes:calls_package", + "from": "function:neuroforge/internal/brain:Engine.clusterPost", +@@ -48064,6 +51274,90 @@ + "kind": "calls_package", + "label": "Duration" + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.collectGoalDraftEvidence-\u003efunction:neuroforge/internal/brain:goalEvidenceRelevant:calls", ++ "from": "function:neuroforge/internal/brain:Engine.collectGoalDraftEvidence", ++ "to": "function:neuroforge/internal/brain:goalEvidenceRelevant", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.collectGoalDraftEvidence-\u003efunction:neuroforge/internal/brain:memoryBelongsToGoal:calls", ++ "from": "function:neuroforge/internal/brain:Engine.collectGoalDraftEvidence", ++ "to": "function:neuroforge/internal/brain:memoryBelongsToGoal", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.collectGoalDraftEvidence-\u003efunction:neuroforge/internal/brain:sortDraftEvidenceByAuthority:calls", ++ "from": "function:neuroforge/internal/brain:Engine.collectGoalDraftEvidence", ++ "to": "function:neuroforge/internal/brain:sortDraftEvidenceByAuthority", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.collectGoalDraftEvidence-\u003efunction:neuroforge/internal/brain:sourceAuthorityFor:calls", ++ "from": "function:neuroforge/internal/brain:Engine.collectGoalDraftEvidence", ++ "to": "function:neuroforge/internal/brain:sourceAuthorityFor", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.collectGoalDraftEvidence-\u003epackage:sort:calls_package", ++ "from": "function:neuroforge/internal/brain:Engine.collectGoalDraftEvidence", ++ "to": "package:sort", ++ "kind": "calls_package", ++ "label": "SliceStable" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.collectGoalDraftEvidence-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/brain:Engine.collectGoalDraftEvidence", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.distributedChat-\u003efunction:neuroforge/internal/brain:Engine.waitDistributedJob:calls", ++ "from": "function:neuroforge/internal/brain:Engine.distributedChat", ++ "to": "function:neuroforge/internal/brain:Engine.waitDistributedJob", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.distributedChat-\u003epackage:context:calls_package", ++ "from": "function:neuroforge/internal/brain:Engine.distributedChat", ++ "to": "package:context", ++ "kind": "calls_package", ++ "label": "WithTimeout" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.distributedChat-\u003epackage:time:calls_package", ++ "from": "function:neuroforge/internal/brain:Engine.distributedChat", ++ "to": "package:time", ++ "kind": "calls_package", ++ "label": "Duration" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.distributedEmbed-\u003efunction:neuroforge/internal/brain:Engine.waitDistributedJob:calls", ++ "from": "function:neuroforge/internal/brain:Engine.distributedEmbed", ++ "to": "function:neuroforge/internal/brain:Engine.waitDistributedJob", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.distributedEmbed-\u003efunction:neuroforge/internal/brain:New:calls", ++ "from": "function:neuroforge/internal/brain:Engine.distributedEmbed", ++ "to": "function:neuroforge/internal/brain:New", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.distributedEmbed-\u003epackage:context:calls_package", ++ "from": "function:neuroforge/internal/brain:Engine.distributedEmbed", ++ "to": "package:context", ++ "kind": "calls_package", ++ "label": "WithTimeout" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.distributedEmbed-\u003epackage:time:calls_package", ++ "from": "function:neuroforge/internal/brain:Engine.distributedEmbed", ++ "to": "package:time", ++ "kind": "calls_package", ++ "label": "Duration" ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.duplicateMemory-\u003efunction:neuroforge/internal/brain:Engine.SearchVector:calls", + "from": "function:neuroforge/internal/brain:Engine.duplicateMemory", +@@ -48083,6 +51377,55 @@ + "kind": "calls_package", + "label": "Now" + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.embed-\u003efunction:neuroforge/internal/brain:Engine.distributedEmbed:calls", ++ "from": "function:neuroforge/internal/brain:Engine.embed", ++ "to": "function:neuroforge/internal/brain:Engine.distributedEmbed", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.enqueueRelink-\u003efunction:neuroforge/internal/brain:Engine.SearchVector:calls", ++ "from": "function:neuroforge/internal/brain:Engine.enqueueRelink", ++ "to": "function:neuroforge/internal/brain:Engine.SearchVector", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.enqueueRelink-\u003efunction:neuroforge/internal/brain:New:calls", ++ "from": "function:neuroforge/internal/brain:Engine.enqueueRelink", ++ "to": "function:neuroforge/internal/brain:New", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.enqueueRelink-\u003efunction:neuroforge/internal/brain:vectorFingerprint:calls", ++ "from": "function:neuroforge/internal/brain:Engine.enqueueRelink", ++ "to": "function:neuroforge/internal/brain:vectorFingerprint", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.enqueueRelink-\u003epackage:strconv:calls_package", ++ "from": "function:neuroforge/internal/brain:Engine.enqueueRelink", ++ "to": "package:strconv", ++ "kind": "calls_package", ++ "label": "FormatInt" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.ensureGoalDraftArticleDepth-\u003efunction:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle:calls", ++ "from": "function:neuroforge/internal/brain:Engine.ensureGoalDraftArticleDepth", ++ "to": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.ensureGoalDraftArticleDepth-\u003efunction:neuroforge/internal/brain:Engine.stagingConfig:calls", ++ "from": "function:neuroforge/internal/brain:Engine.ensureGoalDraftArticleDepth", ++ "to": "function:neuroforge/internal/brain:Engine.stagingConfig", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.ensureGoalDraftArticleDepth-\u003efunction:neuroforge/internal/brain:validateDraftArticleBounds:calls", ++ "from": "function:neuroforge/internal/brain:Engine.ensureGoalDraftArticleDepth", ++ "to": "function:neuroforge/internal/brain:validateDraftArticleBounds", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.evaluateReward-\u003efunction:neuroforge/internal/brain:Engine.chatModelLimitOn:calls", + "from": "function:neuroforge/internal/brain:Engine.evaluateReward", +@@ -48146,6 +51489,12 @@ + "to": "function:neuroforge/internal/brain:dedupeStrings", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.goalResearchQueries-\u003efunction:neuroforge/internal/brain:deterministicResearchQueries:calls", ++ "from": "function:neuroforge/internal/brain:Engine.goalResearchQueries", ++ "to": "function:neuroforge/internal/brain:deterministicResearchQueries", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.goalResearchQueries-\u003efunction:neuroforge/internal/brain:researchQueryUseful:calls", + "from": "function:neuroforge/internal/brain:Engine.goalResearchQueries", +@@ -48297,6 +51646,12 @@ + "to": "function:neuroforge/internal/brain:claimPreview", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.ingestSourceText-\u003efunction:neuroforge/internal/brain:goalIDFromTags:calls", ++ "from": "function:neuroforge/internal/brain:Engine.ingestSourceText", ++ "to": "function:neuroforge/internal/brain:goalIDFromTags", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.ingestSourceText-\u003efunction:neuroforge/internal/brain:hashText:calls", + "from": "function:neuroforge/internal/brain:Engine.ingestSourceText", +@@ -48422,12 +51777,36 @@ + "to": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.maybePublishGoalDraft-\u003efunction:neuroforge/internal/brain:countDraftIndependentCorroborations:calls", ++ "from": "function:neuroforge/internal/brain:Engine.maybePublishGoalDraft", ++ "to": "function:neuroforge/internal/brain:countDraftIndependentCorroborations", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.maybePublishGoalDraft-\u003efunction:neuroforge/internal/brain:deterministicStagingFailure:calls", ++ "from": "function:neuroforge/internal/brain:Engine.maybePublishGoalDraft", ++ "to": "function:neuroforge/internal/brain:deterministicStagingFailure", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.maybePublishGoalDraft-\u003efunction:neuroforge/internal/brain:firstNonEmpty:calls", + "from": "function:neuroforge/internal/brain:Engine.maybePublishGoalDraft", + "to": "function:neuroforge/internal/brain:firstNonEmpty", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.maybePublishGoalDraft-\u003efunction:neuroforge/internal/brain:stagingEvidenceSignature:calls", ++ "from": "function:neuroforge/internal/brain:Engine.maybePublishGoalDraft", ++ "to": "function:neuroforge/internal/brain:stagingEvidenceSignature", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.maybePublishGoalDraft-\u003efunction:neuroforge/internal/brain:summarizeEvidenceAuthority:calls", ++ "from": "function:neuroforge/internal/brain:Engine.maybePublishGoalDraft", ++ "to": "function:neuroforge/internal/brain:summarizeEvidenceAuthority", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.maybePublishGoalDraft-\u003epackage:bytes:calls_package", + "from": "function:neuroforge/internal/brain:Engine.maybePublishGoalDraft", +@@ -48536,6 +51915,24 @@ + "kind": "calls_package", + "label": "Now" + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.refreshGoalResearchProgress-\u003efunction:neuroforge/internal/brain:goalEvidenceRelevant:calls", ++ "from": "function:neuroforge/internal/brain:Engine.refreshGoalResearchProgress", ++ "to": "function:neuroforge/internal/brain:goalEvidenceRelevant", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.refreshGoalResearchProgress-\u003efunction:neuroforge/internal/brain:memoryBelongsToGoal:calls", ++ "from": "function:neuroforge/internal/brain:Engine.refreshGoalResearchProgress", ++ "to": "function:neuroforge/internal/brain:memoryBelongsToGoal", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.refreshGoalResearchProgress-\u003efunction:neuroforge/internal/brain:sourceOriginKey:calls", ++ "from": "function:neuroforge/internal/brain:Engine.refreshGoalResearchProgress", ++ "to": "function:neuroforge/internal/brain:sourceOriginKey", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.refreshGoalResearchProgress-\u003epackage:fmt:calls_package", + "from": "function:neuroforge/internal/brain:Engine.refreshGoalResearchProgress", +@@ -48562,7 +51959,7 @@ + "from": "function:neuroforge/internal/brain:Engine.refreshGoalResearchProgress", + "to": "package:strings", + "kind": "calls_package", +- "label": "TrimSpace" ++ "label": "ToLower" + }, + { + "id": "function:neuroforge/internal/brain:Engine.remoteVectorSearch-\u003efunction:neuroforge/internal/brain:New:calls", +@@ -48626,6 +52023,81 @@ + "kind": "calls_package", + "label": "Duration" + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.repairDraftGrounding-\u003efunction:neuroforge/internal/brain:Engine.chatModelJSONLimitOn:calls", ++ "from": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", ++ "to": "function:neuroforge/internal/brain:Engine.chatModelJSONLimitOn", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.repairDraftGrounding-\u003efunction:neuroforge/internal/brain:Engine.stagingConfig:calls", ++ "from": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", ++ "to": "function:neuroforge/internal/brain:Engine.stagingConfig", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.repairDraftGrounding-\u003efunction:neuroforge/internal/brain:New:calls", ++ "from": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", ++ "to": "function:neuroforge/internal/brain:New", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.repairDraftGrounding-\u003efunction:neuroforge/internal/brain:decodeStagingSynthesisJSON:calls", ++ "from": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", ++ "to": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.repairDraftGrounding-\u003efunction:neuroforge/internal/brain:evidencePackForPrompt:calls", ++ "from": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", ++ "to": "function:neuroforge/internal/brain:evidencePackForPrompt", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.repairDraftGrounding-\u003efunction:neuroforge/internal/brain:goalKeywords:calls", ++ "from": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", ++ "to": "function:neuroforge/internal/brain:goalKeywords", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.repairDraftGrounding-\u003efunction:neuroforge/internal/brain:researchMaterialRelevant:calls", ++ "from": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", ++ "to": "function:neuroforge/internal/brain:researchMaterialRelevant", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.repairDraftGrounding-\u003efunction:neuroforge/internal/brain:roleRoute:calls", ++ "from": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", ++ "to": "function:neuroforge/internal/brain:roleRoute", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.repairDraftGrounding-\u003efunction:neuroforge/internal/brain:validateDraftCriticalIdentifiers:calls", ++ "from": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", ++ "to": "function:neuroforge/internal/brain:validateDraftCriticalIdentifiers", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.repairDraftGrounding-\u003epackage:encoding/json:calls_package", ++ "from": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", ++ "to": "package:encoding/json", ++ "kind": "calls_package", ++ "label": "Marshal" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.repairDraftGrounding-\u003epackage:fmt:calls_package", ++ "from": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", ++ "to": "package:fmt", ++ "kind": "calls_package", ++ "label": "Sprintf" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.repairDraftGrounding-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.replicateMemory-\u003epackage:bytes:calls_package", + "from": "function:neuroforge/internal/brain:Engine.replicateMemory", +@@ -48781,6 +52253,81 @@ + "to": "function:neuroforge/internal/brain:electionTimeout", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle-\u003efunction:neuroforge/internal/brain:Engine.chatModelJSONLimitOn:calls", ++ "from": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", ++ "to": "function:neuroforge/internal/brain:Engine.chatModelJSONLimitOn", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle-\u003efunction:neuroforge/internal/brain:Engine.stagingConfig:calls", ++ "from": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", ++ "to": "function:neuroforge/internal/brain:Engine.stagingConfig", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle-\u003efunction:neuroforge/internal/brain:New:calls", ++ "from": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", ++ "to": "function:neuroforge/internal/brain:New", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle-\u003efunction:neuroforge/internal/brain:decodeStagingSynthesisJSON:calls", ++ "from": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", ++ "to": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle-\u003efunction:neuroforge/internal/brain:evidencePackForPrompt:calls", ++ "from": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", ++ "to": "function:neuroforge/internal/brain:evidencePackForPrompt", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle-\u003efunction:neuroforge/internal/brain:goalKeywords:calls", ++ "from": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", ++ "to": "function:neuroforge/internal/brain:goalKeywords", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle-\u003efunction:neuroforge/internal/brain:researchMaterialRelevant:calls", ++ "from": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", ++ "to": "function:neuroforge/internal/brain:researchMaterialRelevant", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle-\u003efunction:neuroforge/internal/brain:roleRoute:calls", ++ "from": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", ++ "to": "function:neuroforge/internal/brain:roleRoute", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle-\u003efunction:neuroforge/internal/brain:validateDraftArticleBounds:calls", ++ "from": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", ++ "to": "function:neuroforge/internal/brain:validateDraftArticleBounds", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle-\u003epackage:encoding/json:calls_package", ++ "from": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", ++ "to": "package:encoding/json", ++ "kind": "calls_package", ++ "label": "Marshal" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle-\u003epackage:fmt:calls_package", ++ "from": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", ++ "to": "package:fmt", ++ "kind": "calls_package", ++ "label": "Sprintf" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.searchVectorFederated-\u003efunction:neuroforge/internal/brain:Engine.SearchVector:calls", + "from": "function:neuroforge/internal/brain:Engine.searchVectorFederated", +@@ -48839,9 +52386,33 @@ + "label": "TrimSpace" + }, + { +- "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:Engine.chatModelLimitOn:calls", ++ "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:Engine.chatModelJSONLimitOn:calls", + "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", +- "to": "function:neuroforge/internal/brain:Engine.chatModelLimitOn", ++ "to": "function:neuroforge/internal/brain:Engine.chatModelJSONLimitOn", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:Engine.ensureGoalDraftArticleDepth:calls", ++ "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", ++ "to": "function:neuroforge/internal/brain:Engine.ensureGoalDraftArticleDepth", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:Engine.repairDraftGrounding:calls", ++ "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", ++ "to": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:Engine.stagingConfig:calls", ++ "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", ++ "to": "function:neuroforge/internal/brain:Engine.stagingConfig", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:Engine.verifyDraftClaims:calls", ++ "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", ++ "to": "function:neuroforge/internal/brain:Engine.verifyDraftClaims", + "kind": "calls" + }, + { +@@ -48850,18 +52421,42 @@ + "to": "function:neuroforge/internal/brain:New", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:articleQualityForDraft:calls", ++ "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", ++ "to": "function:neuroforge/internal/brain:articleQualityForDraft", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:decodeStagingSynthesisJSON:calls", ++ "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", ++ "to": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:deterministicDraftAnswer:calls", + "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", + "to": "function:neuroforge/internal/brain:deterministicDraftAnswer", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:evidencePackForPrompt:calls", ++ "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", ++ "to": "function:neuroforge/internal/brain:evidencePackForPrompt", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:goalKeywords:calls", + "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", + "to": "function:neuroforge/internal/brain:goalKeywords", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:researchMaterialRelevant:calls", ++ "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", ++ "to": "function:neuroforge/internal/brain:researchMaterialRelevant", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:roleRoute:calls", + "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", +@@ -48869,18 +52464,23 @@ + "kind": "calls" + }, + { +- "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003epackage:encoding/json:calls_package", ++ "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:summarizeEvidenceAuthority:calls", + "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", +- "to": "package:encoding/json", +- "kind": "calls_package", +- "label": "Unmarshal" ++ "to": "function:neuroforge/internal/brain:summarizeEvidenceAuthority", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:validateDraftCriticalIdentifiers:calls", ++ "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", ++ "to": "function:neuroforge/internal/brain:validateDraftCriticalIdentifiers", ++ "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003epackage:fmt:calls_package", + "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", + "to": "package:fmt", + "kind": "calls_package", +- "label": "Fprintf" ++ "label": "Errorf" + }, + { + "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003epackage:strings:calls_package", +@@ -48889,6 +52489,95 @@ + "kind": "calls_package", + "label": "TrimSpace" + }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.verifyDraftClaims-\u003efunction:neuroforge/internal/brain:Engine.chatModelJSONLimitOn:calls", ++ "from": "function:neuroforge/internal/brain:Engine.verifyDraftClaims", ++ "to": "function:neuroforge/internal/brain:Engine.chatModelJSONLimitOn", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.verifyDraftClaims-\u003efunction:neuroforge/internal/brain:Engine.stagingConfig:calls", ++ "from": "function:neuroforge/internal/brain:Engine.verifyDraftClaims", ++ "to": "function:neuroforge/internal/brain:Engine.stagingConfig", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.verifyDraftClaims-\u003efunction:neuroforge/internal/brain:New:calls", ++ "from": "function:neuroforge/internal/brain:Engine.verifyDraftClaims", ++ "to": "function:neuroforge/internal/brain:New", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.verifyDraftClaims-\u003efunction:neuroforge/internal/brain:decodeVerifierJSON:calls", ++ "from": "function:neuroforge/internal/brain:Engine.verifyDraftClaims", ++ "to": "function:neuroforge/internal/brain:decodeVerifierJSON", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.verifyDraftClaims-\u003efunction:neuroforge/internal/brain:evidencePackForPrompt:calls", ++ "from": "function:neuroforge/internal/brain:Engine.verifyDraftClaims", ++ "to": "function:neuroforge/internal/brain:evidencePackForPrompt", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.verifyDraftClaims-\u003efunction:neuroforge/internal/brain:extractDraftStatements:calls", ++ "from": "function:neuroforge/internal/brain:Engine.verifyDraftClaims", ++ "to": "function:neuroforge/internal/brain:extractDraftStatements", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.verifyDraftClaims-\u003efunction:neuroforge/internal/brain:roleRoute:calls", ++ "from": "function:neuroforge/internal/brain:Engine.verifyDraftClaims", ++ "to": "function:neuroforge/internal/brain:roleRoute", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.verifyDraftClaims-\u003efunction:neuroforge/internal/brain:sourceAuditForEvidence:calls", ++ "from": "function:neuroforge/internal/brain:Engine.verifyDraftClaims", ++ "to": "function:neuroforge/internal/brain:sourceAuditForEvidence", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.verifyDraftClaims-\u003epackage:fmt:calls_package", ++ "from": "function:neuroforge/internal/brain:Engine.verifyDraftClaims", ++ "to": "package:fmt", ++ "kind": "calls_package", ++ "label": "Errorf" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.verifyDraftClaims-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/brain:Engine.verifyDraftClaims", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.waitDistributedJob-\u003efunction:neuroforge/internal/brain:New:calls", ++ "from": "function:neuroforge/internal/brain:Engine.waitDistributedJob", ++ "to": "function:neuroforge/internal/brain:New", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.waitDistributedJob-\u003epackage:encoding/json:calls_package", ++ "from": "function:neuroforge/internal/brain:Engine.waitDistributedJob", ++ "to": "package:encoding/json", ++ "kind": "calls_package", ++ "label": "Unmarshal" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.waitDistributedJob-\u003epackage:fmt:calls_package", ++ "from": "function:neuroforge/internal/brain:Engine.waitDistributedJob", ++ "to": "package:fmt", ++ "kind": "calls_package", ++ "label": "Errorf" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:Engine.waitDistributedJob-\u003epackage:time:calls_package", ++ "from": "function:neuroforge/internal/brain:Engine.waitDistributedJob", ++ "to": "package:time", ++ "kind": "calls_package", ++ "label": "NewTicker" ++ }, + { + "id": "function:neuroforge/internal/brain:ResearchDomain-\u003epackage:net/url:calls_package", + "from": "function:neuroforge/internal/brain:ResearchDomain", +@@ -48910,6 +52599,26 @@ + "kind": "calls_package", + "label": "Slice" + }, ++ { ++ "id": "function:neuroforge/internal/brain:allDigits-\u003epackage:unicode:calls_package", ++ "from": "function:neuroforge/internal/brain:allDigits", ++ "to": "package:unicode", ++ "kind": "calls_package", ++ "label": "IsDigit" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:articleQualityForDraft-\u003efunction:neuroforge/internal/brain:articleRuneCount:calls", ++ "from": "function:neuroforge/internal/brain:articleQualityForDraft", ++ "to": "function:neuroforge/internal/brain:articleRuneCount", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:articleRuneCount-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/brain:articleRuneCount", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, + { + "id": "function:neuroforge/internal/brain:buildContext-\u003epackage:fmt:calls_package", + "from": "function:neuroforge/internal/brain:buildContext", +@@ -48937,6 +52646,89 @@ + "kind": "calls_package", + "label": "Join" + }, ++ { ++ "id": "function:neuroforge/internal/brain:countDraftIndependentCorroborations-\u003efunction:neuroforge/internal/brain:sourceOriginKey:calls", ++ "from": "function:neuroforge/internal/brain:countDraftIndependentCorroborations", ++ "to": "function:neuroforge/internal/brain:sourceOriginKey", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:criticalIdentifiers-\u003efunction:neuroforge/internal/brain:slashSwitchIdentifiers:calls", ++ "from": "function:neuroforge/internal/brain:criticalIdentifiers", ++ "to": "function:neuroforge/internal/brain:slashSwitchIdentifiers", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:criticalIdentifiers-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/brain:criticalIdentifiers", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "ToLower" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON-\u003efunction:neuroforge/internal/brain:New:calls", ++ "from": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON", ++ "to": "function:neuroforge/internal/brain:New", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON-\u003efunction:neuroforge/internal/brain:repairInvalidJSONStringEscapes:calls", ++ "from": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON", ++ "to": "function:neuroforge/internal/brain:repairInvalidJSONStringEscapes", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON-\u003efunction:neuroforge/internal/brain:strictUnmarshalJSONObject:calls", ++ "from": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON", ++ "to": "function:neuroforge/internal/brain:strictUnmarshalJSONObject", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON-\u003epackage:fmt:calls_package", ++ "from": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON", ++ "to": "package:fmt", ++ "kind": "calls_package", ++ "label": "Errorf" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:decodeVerifierJSON-\u003efunction:neuroforge/internal/brain:New:calls", ++ "from": "function:neuroforge/internal/brain:decodeVerifierJSON", ++ "to": "function:neuroforge/internal/brain:New", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:decodeVerifierJSON-\u003efunction:neuroforge/internal/brain:repairInvalidJSONStringEscapes:calls", ++ "from": "function:neuroforge/internal/brain:decodeVerifierJSON", ++ "to": "function:neuroforge/internal/brain:repairInvalidJSONStringEscapes", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:decodeVerifierJSON-\u003efunction:neuroforge/internal/brain:strictUnmarshalJSONObject:calls", ++ "from": "function:neuroforge/internal/brain:decodeVerifierJSON", ++ "to": "function:neuroforge/internal/brain:strictUnmarshalJSONObject", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:decodeVerifierJSON-\u003epackage:fmt:calls_package", ++ "from": "function:neuroforge/internal/brain:decodeVerifierJSON", ++ "to": "package:fmt", ++ "kind": "calls_package", ++ "label": "Errorf" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:decodeVerifierJSON-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/brain:decodeVerifierJSON", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, + { + "id": "function:neuroforge/internal/brain:dedupeStrings-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:dedupeStrings", +@@ -48965,6 +52757,45 @@ + "kind": "calls_package", + "label": "Sprintf" + }, ++ { ++ "id": "function:neuroforge/internal/brain:deterministicResearchQueries-\u003efunction:neuroforge/internal/brain:dedupeStrings:calls", ++ "from": "function:neuroforge/internal/brain:deterministicResearchQueries", ++ "to": "function:neuroforge/internal/brain:dedupeStrings", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:deterministicResearchQueries-\u003efunction:neuroforge/internal/brain:goalPreferredAuthorityDomains:calls", ++ "from": "function:neuroforge/internal/brain:deterministicResearchQueries", ++ "to": "function:neuroforge/internal/brain:goalPreferredAuthorityDomains", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:deterministicResearchQueries-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/brain:deterministicResearchQueries", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "Join" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:deterministicStagingFailure-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/brain:deterministicStagingFailure", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "ToLower" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:domainMatches-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/brain:domainMatches", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "ToLower" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:draftEvidenceAuthorityScore-\u003efunction:neuroforge/internal/brain:sourceAuthorityFor:calls", ++ "from": "function:neuroforge/internal/brain:draftEvidenceAuthorityScore", ++ "to": "function:neuroforge/internal/brain:sourceAuthorityFor", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/brain:due-\u003epackage:time:calls_package", + "from": "function:neuroforge/internal/brain:due", +@@ -49007,6 +52838,51 @@ + "kind": "calls_package", + "label": "Clamp" + }, ++ { ++ "id": "function:neuroforge/internal/brain:evidencePackForPrompt-\u003efunction:neuroforge/internal/brain:promptEvidenceTexts:calls", ++ "from": "function:neuroforge/internal/brain:evidencePackForPrompt", ++ "to": "function:neuroforge/internal/brain:promptEvidenceTexts", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:evidencePackForPrompt-\u003efunction:neuroforge/internal/brain:sourceAuditForEvidence:calls", ++ "from": "function:neuroforge/internal/brain:evidencePackForPrompt", ++ "to": "function:neuroforge/internal/brain:sourceAuditForEvidence", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:evidencePackForPrompt-\u003epackage:fmt:calls_package", ++ "from": "function:neuroforge/internal/brain:evidencePackForPrompt", ++ "to": "package:fmt", ++ "kind": "calls_package", ++ "label": "Sprintf" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:extractDraftStatements-\u003efunction:neuroforge/internal/brain:isActionableDraftStatement:calls", ++ "from": "function:neuroforge/internal/brain:extractDraftStatements", ++ "to": "function:neuroforge/internal/brain:isActionableDraftStatement", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:extractDraftStatements-\u003efunction:neuroforge/internal/brain:normalizeStatementText:calls", ++ "from": "function:neuroforge/internal/brain:extractDraftStatements", ++ "to": "function:neuroforge/internal/brain:normalizeStatementText", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:extractDraftStatements-\u003epackage:strconv:calls_package", ++ "from": "function:neuroforge/internal/brain:extractDraftStatements", ++ "to": "package:strconv", ++ "kind": "calls_package", ++ "label": "Itoa" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:extractDraftStatements-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/brain:extractDraftStatements", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "ToLower" ++ }, + { + "id": "function:neuroforge/internal/brain:firstNonEmpty-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:firstNonEmpty", +@@ -49014,6 +52890,47 @@ + "kind": "calls_package", + "label": "TrimSpace" + }, ++ { ++ "id": "function:neuroforge/internal/brain:goalAnchorTokens-\u003epackage:sort:calls_package", ++ "from": "function:neuroforge/internal/brain:goalAnchorTokens", ++ "to": "package:sort", ++ "kind": "calls_package", ++ "label": "SliceStable" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:goalAnchorTokens-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/brain:goalAnchorTokens", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "Map" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:goalAnchorTokens-\u003epackage:unicode:calls_package", ++ "from": "function:neuroforge/internal/brain:goalAnchorTokens", ++ "to": "package:unicode", ++ "kind": "calls_package", ++ "label": "IsLetter" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:goalEvidenceRelevant-\u003efunction:neuroforge/internal/brain:researchMaterialRelevant:calls", ++ "from": "function:neuroforge/internal/brain:goalEvidenceRelevant", ++ "to": "function:neuroforge/internal/brain:researchMaterialRelevant", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:goalEvidenceRelevant-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/brain:goalEvidenceRelevant", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:goalIDFromTags-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/brain:goalIDFromTags", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "HasPrefix" ++ }, + { + "id": "function:neuroforge/internal/brain:goalKeywords-\u003epackage:sort:calls_package", + "from": "function:neuroforge/internal/brain:goalKeywords", +@@ -49028,6 +52945,18 @@ + "kind": "calls_package", + "label": "Fields" + }, ++ { ++ "id": "function:neuroforge/internal/brain:goalPreferredAuthorityDomains-\u003efunction:neuroforge/internal/brain:dedupeStrings:calls", ++ "from": "function:neuroforge/internal/brain:goalPreferredAuthorityDomains", ++ "to": "function:neuroforge/internal/brain:dedupeStrings", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:goalPreferredAuthorityDomains-\u003efunction:neuroforge/internal/brain:normalizedResearchWords:calls", ++ "from": "function:neuroforge/internal/brain:goalPreferredAuthorityDomains", ++ "to": "function:neuroforge/internal/brain:normalizedResearchWords", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/brain:hashText-\u003epackage:crypto/sha256:calls_package", + "from": "function:neuroforge/internal/brain:hashText", +@@ -49042,6 +52971,20 @@ + "kind": "calls_package", + "label": "EncodeToString" + }, ++ { ++ "id": "function:neuroforge/internal/brain:isActionableDraftStatement-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/brain:isActionableDraftStatement", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "ToLower" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:memoryBelongsToGoal-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/brain:memoryBelongsToGoal", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, + { + "id": "function:neuroforge/internal/brain:memoryTypeForKind-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:memoryTypeForKind", +@@ -49049,6 +52992,34 @@ + "kind": "calls_package", + "label": "ToLower" + }, ++ { ++ "id": "function:neuroforge/internal/brain:normalizeAuthoritativeDomain-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/brain:normalizeAuthoritativeDomain", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "ToLower" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:normalizeStatementText-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/brain:normalizeStatementText", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:normalizedResearchWords-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/brain:normalizedResearchWords", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "Map" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:normalizedResearchWords-\u003epackage:unicode:calls_package", ++ "from": "function:neuroforge/internal/brain:normalizedResearchWords", ++ "to": "package:unicode", ++ "kind": "calls_package", ++ "label": "IsLetter" ++ }, + { + "id": "function:neuroforge/internal/brain:parsePrediction-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:parsePrediction", +@@ -49090,6 +53061,13 @@ + "kind": "calls_package", + "label": "Clamp" + }, ++ { ++ "id": "function:neuroforge/internal/brain:promptEvidenceTexts-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/brain:promptEvidenceTexts", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, + { + "id": "function:neuroforge/internal/brain:rendezvousScore-\u003epackage:hash/fnv:calls_package", + "from": "function:neuroforge/internal/brain:rendezvousScore", +@@ -49110,6 +53088,30 @@ + "to": "function:neuroforge/internal/brain:rendezvousScore", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/brain:repairInvalidJSONStringEscapes-\u003efunction:neuroforge/internal/brain:isJSONHex4:calls", ++ "from": "function:neuroforge/internal/brain:repairInvalidJSONStringEscapes", ++ "to": "function:neuroforge/internal/brain:isJSONHex4", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:researchMaterialRelevant-\u003efunction:neuroforge/internal/brain:allDigits:calls", ++ "from": "function:neuroforge/internal/brain:researchMaterialRelevant", ++ "to": "function:neuroforge/internal/brain:allDigits", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:researchMaterialRelevant-\u003efunction:neuroforge/internal/brain:goalAnchorTokens:calls", ++ "from": "function:neuroforge/internal/brain:researchMaterialRelevant", ++ "to": "function:neuroforge/internal/brain:goalAnchorTokens", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:researchMaterialRelevant-\u003efunction:neuroforge/internal/brain:normalizedResearchWords:calls", ++ "from": "function:neuroforge/internal/brain:researchMaterialRelevant", ++ "to": "function:neuroforge/internal/brain:normalizedResearchWords", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/brain:researchQueryUseful-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:researchQueryUseful", +@@ -49117,6 +53119,12 @@ + "kind": "calls_package", + "label": "ToLower" + }, ++ { ++ "id": "function:neuroforge/internal/brain:researchResultsContainRelevant-\u003efunction:neuroforge/internal/brain:researchMaterialRelevant:calls", ++ "from": "function:neuroforge/internal/brain:researchResultsContainRelevant", ++ "to": "function:neuroforge/internal/brain:researchMaterialRelevant", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/brain:researchTrace.emit-\u003epackage:time:calls_package", + "from": "function:neuroforge/internal/brain:researchTrace.emit", +@@ -49150,6 +53158,26 @@ + "kind": "calls_package", + "label": "Join" + }, ++ { ++ "id": "function:neuroforge/internal/brain:slashSwitchIdentifiers-\u003epackage:unicode:calls_package", ++ "from": "function:neuroforge/internal/brain:slashSwitchIdentifiers", ++ "to": "package:unicode", ++ "kind": "calls_package", ++ "label": "IsSpace" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:sortDraftEvidenceByAuthority-\u003efunction:neuroforge/internal/brain:draftEvidenceAuthorityScore:calls", ++ "from": "function:neuroforge/internal/brain:sortDraftEvidenceByAuthority", ++ "to": "function:neuroforge/internal/brain:draftEvidenceAuthorityScore", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:sortDraftEvidenceByAuthority-\u003epackage:sort:calls_package", ++ "from": "function:neuroforge/internal/brain:sortDraftEvidenceByAuthority", ++ "to": "package:sort", ++ "kind": "calls_package", ++ "label": "SliceStable" ++ }, + { + "id": "function:neuroforge/internal/brain:sortedGoalIDs-\u003epackage:sort:calls_package", + "from": "function:neuroforge/internal/brain:sortedGoalIDs", +@@ -49157,6 +53185,53 @@ + "kind": "calls_package", + "label": "Strings" + }, ++ { ++ "id": "function:neuroforge/internal/brain:sourceAuditForEvidence-\u003efunction:neuroforge/internal/brain:sourceAuthorityFor:calls", ++ "from": "function:neuroforge/internal/brain:sourceAuditForEvidence", ++ "to": "function:neuroforge/internal/brain:sourceAuthorityFor", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:sourceAuditForEvidence-\u003epackage:fmt:calls_package", ++ "from": "function:neuroforge/internal/brain:sourceAuditForEvidence", ++ "to": "package:fmt", ++ "kind": "calls_package", ++ "label": "Sprintf" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:sourceAuthorityFor-\u003efunction:neuroforge/internal/brain:domainMatches:calls", ++ "from": "function:neuroforge/internal/brain:sourceAuthorityFor", ++ "to": "function:neuroforge/internal/brain:domainMatches", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:sourceAuthorityFor-\u003epackage:net/url:calls_package", ++ "from": "function:neuroforge/internal/brain:sourceAuthorityFor", ++ "to": "package:net/url", ++ "kind": "calls_package", ++ "label": "Parse" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:sourceAuthorityFor-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/brain:sourceAuthorityFor", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:sourceOriginKey-\u003epackage:net/url:calls_package", ++ "from": "function:neuroforge/internal/brain:sourceOriginKey", ++ "to": "package:net/url", ++ "kind": "calls_package", ++ "label": "Parse" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:sourceOriginKey-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/brain:sourceOriginKey", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, + { + "id": "function:neuroforge/internal/brain:sourcePolicyKey-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:sourcePolicyKey", +@@ -49185,6 +53260,66 @@ + "kind": "calls_package", + "label": "ToLower" + }, ++ { ++ "id": "function:neuroforge/internal/brain:stagingEvidenceSignature-\u003epackage:crypto/sha256:calls_package", ++ "from": "function:neuroforge/internal/brain:stagingEvidenceSignature", ++ "to": "package:crypto/sha256", ++ "kind": "calls_package", ++ "label": "Sum256" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:stagingEvidenceSignature-\u003epackage:fmt:calls_package", ++ "from": "function:neuroforge/internal/brain:stagingEvidenceSignature", ++ "to": "package:fmt", ++ "kind": "calls_package", ++ "label": "Sprintf" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:stagingEvidenceSignature-\u003epackage:sort:calls_package", ++ "from": "function:neuroforge/internal/brain:stagingEvidenceSignature", ++ "to": "package:sort", ++ "kind": "calls_package", ++ "label": "Strings" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:stagingEvidenceSignature-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/brain:stagingEvidenceSignature", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "Join" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:strictUnmarshalJSONObject-\u003efunction:neuroforge/internal/brain:New:calls", ++ "from": "function:neuroforge/internal/brain:strictUnmarshalJSONObject", ++ "to": "function:neuroforge/internal/brain:New", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:strictUnmarshalJSONObject-\u003epackage:encoding/json:calls_package", ++ "from": "function:neuroforge/internal/brain:strictUnmarshalJSONObject", ++ "to": "package:encoding/json", ++ "kind": "calls_package", ++ "label": "NewDecoder" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:strictUnmarshalJSONObject-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/brain:strictUnmarshalJSONObject", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "NewReader" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:summarizeEvidenceAuthority-\u003efunction:neuroforge/internal/brain:sourceAuditForEvidence:calls", ++ "from": "function:neuroforge/internal/brain:summarizeEvidenceAuthority", ++ "to": "function:neuroforge/internal/brain:sourceAuditForEvidence", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:summarizeEvidenceAuthority-\u003efunction:neuroforge/internal/brain:sourceOriginKey:calls", ++ "from": "function:neuroforge/internal/brain:summarizeEvidenceAuthority", ++ "to": "function:neuroforge/internal/brain:sourceOriginKey", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/brain:summarizeObservation-\u003epackage:fmt:calls_package", + "from": "function:neuroforge/internal/brain:summarizeObservation", +@@ -49199,6 +53334,59 @@ + "kind": "calls_package", + "label": "Join" + }, ++ { ++ "id": "function:neuroforge/internal/brain:validateDraftArticleBounds-\u003efunction:neuroforge/internal/brain:articleRuneCount:calls", ++ "from": "function:neuroforge/internal/brain:validateDraftArticleBounds", ++ "to": "function:neuroforge/internal/brain:articleRuneCount", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:validateDraftArticleBounds-\u003epackage:fmt:calls_package", ++ "from": "function:neuroforge/internal/brain:validateDraftArticleBounds", ++ "to": "package:fmt", ++ "kind": "calls_package", ++ "label": "Errorf" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:validateDraftCriticalIdentifiers-\u003efunction:neuroforge/internal/brain:criticalIdentifiers:calls", ++ "from": "function:neuroforge/internal/brain:validateDraftCriticalIdentifiers", ++ "to": "function:neuroforge/internal/brain:criticalIdentifiers", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:validateDraftCriticalIdentifiers-\u003epackage:fmt:calls_package", ++ "from": "function:neuroforge/internal/brain:validateDraftCriticalIdentifiers", ++ "to": "package:fmt", ++ "kind": "calls_package", ++ "label": "Errorf" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:validateDraftCriticalIdentifiers-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/brain:validateDraftCriticalIdentifiers", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "ToLower" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:vectorFingerprint-\u003efunction:neuroforge/internal/brain:New:calls", ++ "from": "function:neuroforge/internal/brain:vectorFingerprint", ++ "to": "function:neuroforge/internal/brain:New", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:vectorFingerprint-\u003epackage:encoding/hex:calls_package", ++ "from": "function:neuroforge/internal/brain:vectorFingerprint", ++ "to": "package:encoding/hex", ++ "kind": "calls_package", ++ "label": "EncodeToString" ++ }, ++ { ++ "id": "function:neuroforge/internal/brain:vectorFingerprint-\u003epackage:math:calls_package", ++ "from": "function:neuroforge/internal/brain:vectorFingerprint", ++ "to": "package:math", ++ "kind": "calls_package", ++ "label": "Float32bits" ++ }, + { + "id": "function:neuroforge/internal/cost:Manager.ActualCost-\u003efunction:neuroforge/internal/cost:New:calls", + "from": "function:neuroforge/internal/cost:Manager.ActualCost", +@@ -49451,6 +53639,24 @@ + "to": "function:neuroforge/internal/httpapi:maskedSecret", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.adminGraphBackfill-\u003efunction:neuroforge/internal/httpapi:Server.err:calls", ++ "from": "function:neuroforge/internal/httpapi:Server.adminGraphBackfill", ++ "to": "function:neuroforge/internal/httpapi:Server.err", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.adminGraphBackfill-\u003efunction:neuroforge/internal/httpapi:Server.json:calls", ++ "from": "function:neuroforge/internal/httpapi:Server.adminGraphBackfill", ++ "to": "function:neuroforge/internal/httpapi:Server.json", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.adminGraphStatus-\u003efunction:neuroforge/internal/httpapi:Server.json:calls", ++ "from": "function:neuroforge/internal/httpapi:Server.adminGraphStatus", ++ "to": "function:neuroforge/internal/httpapi:Server.json", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/httpapi:Server.adminKnowledgeEvents-\u003efunction:neuroforge/internal/httpapi:Server.json:calls", + "from": "function:neuroforge/internal/httpapi:Server.adminKnowledgeEvents", +@@ -49596,6 +53802,62 @@ + "to": "function:neuroforge/internal/httpapi:Server.json", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.adminOrchestratorCancel-\u003efunction:neuroforge/internal/httpapi:Server.err:calls", ++ "from": "function:neuroforge/internal/httpapi:Server.adminOrchestratorCancel", ++ "to": "function:neuroforge/internal/httpapi:Server.err", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.adminOrchestratorCancel-\u003efunction:neuroforge/internal/httpapi:Server.json:calls", ++ "from": "function:neuroforge/internal/httpapi:Server.adminOrchestratorCancel", ++ "to": "function:neuroforge/internal/httpapi:Server.json", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.adminOrchestratorCancel-\u003efunction:neuroforge/internal/httpapi:decode:calls", ++ "from": "function:neuroforge/internal/httpapi:Server.adminOrchestratorCancel", ++ "to": "function:neuroforge/internal/httpapi:decode", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.adminOrchestratorCancel-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/httpapi:Server.adminOrchestratorCancel", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.adminOrchestratorJobs-\u003efunction:neuroforge/internal/httpapi:Server.json:calls", ++ "from": "function:neuroforge/internal/httpapi:Server.adminOrchestratorJobs", ++ "to": "function:neuroforge/internal/httpapi:Server.json", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.adminOrchestratorJobs-\u003epackage:strconv:calls_package", ++ "from": "function:neuroforge/internal/httpapi:Server.adminOrchestratorJobs", ++ "to": "package:strconv", ++ "kind": "calls_package", ++ "label": "Atoi" ++ }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.adminOrchestratorRetry-\u003efunction:neuroforge/internal/httpapi:Server.err:calls", ++ "from": "function:neuroforge/internal/httpapi:Server.adminOrchestratorRetry", ++ "to": "function:neuroforge/internal/httpapi:Server.err", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.adminOrchestratorRetry-\u003efunction:neuroforge/internal/httpapi:Server.json:calls", ++ "from": "function:neuroforge/internal/httpapi:Server.adminOrchestratorRetry", ++ "to": "function:neuroforge/internal/httpapi:Server.json", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.adminOrchestratorStatus-\u003efunction:neuroforge/internal/httpapi:Server.json:calls", ++ "from": "function:neuroforge/internal/httpapi:Server.adminOrchestratorStatus", ++ "to": "function:neuroforge/internal/httpapi:Server.json", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/httpapi:Server.adminProviderHealth-\u003efunction:neuroforge/internal/httpapi:Server.json:calls", + "from": "function:neuroforge/internal/httpapi:Server.adminProviderHealth", +@@ -50300,6 +54562,13 @@ + "to": "function:neuroforge/internal/httpapi:decode", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.goalsCreate-\u003epackage:errors:calls_package", ++ "from": "function:neuroforge/internal/httpapi:Server.goalsCreate", ++ "to": "package:errors", ++ "kind": "calls_package", ++ "label": "Is" ++ }, + { + "id": "function:neuroforge/internal/httpapi:Server.goalsDelete-\u003efunction:neuroforge/internal/httpapi:Server.err:calls", + "from": "function:neuroforge/internal/httpapi:Server.goalsDelete", +@@ -50561,6 +54830,12 @@ + "kind": "calls_package", + "label": "TrimSpace" + }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.integrationGraphStatus-\u003efunction:neuroforge/internal/httpapi:Server.json:calls", ++ "from": "function:neuroforge/internal/httpapi:Server.integrationGraphStatus", ++ "to": "function:neuroforge/internal/httpapi:Server.json", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/httpapi:Server.integrationKnowledgeDelete-\u003efunction:neuroforge/internal/httpapi:New:calls", + "from": "function:neuroforge/internal/httpapi:Server.integrationKnowledgeDelete", +@@ -50711,6 +54986,12 @@ + "kind": "calls_package", + "label": "TrimSpace" + }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.integrationOrchestratorStatus-\u003efunction:neuroforge/internal/httpapi:Server.json:calls", ++ "from": "function:neuroforge/internal/httpapi:Server.integrationOrchestratorStatus", ++ "to": "function:neuroforge/internal/httpapi:Server.json", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/httpapi:Server.integrationResearchGraph-\u003efunction:neuroforge/internal/httpapi:Server.json:calls", + "from": "function:neuroforge/internal/httpapi:Server.integrationResearchGraph", +@@ -51014,6 +55295,13 @@ + "kind": "calls_package", + "label": "FormatFloat" + }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.metricsEndpoint-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/httpapi:Server.metricsEndpoint", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "SplitN" ++ }, + { + "id": "function:neuroforge/internal/httpapi:Server.readyz-\u003efunction:neuroforge/internal/httpapi:Server.json:calls", + "from": "function:neuroforge/internal/httpapi:Server.readyz", +@@ -51294,6 +55582,12 @@ + "to": "function:neuroforge/internal/httpapi:statusWriter.WriteHeader", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.workerClaim-\u003efunction:neuroforge/internal/httpapi:workerHeartbeatFromRequest:calls", ++ "from": "function:neuroforge/internal/httpapi:Server.workerClaim", ++ "to": "function:neuroforge/internal/httpapi:workerHeartbeatFromRequest", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/httpapi:Server.workerClaim-\u003epackage:time:calls_package", + "from": "function:neuroforge/internal/httpapi:Server.workerClaim", +@@ -51319,6 +55613,61 @@ + "to": "function:neuroforge/internal/httpapi:decode", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.workerHeartbeat-\u003efunction:neuroforge/internal/httpapi:Server.err:calls", ++ "from": "function:neuroforge/internal/httpapi:Server.workerHeartbeat", ++ "to": "function:neuroforge/internal/httpapi:Server.err", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.workerHeartbeat-\u003efunction:neuroforge/internal/httpapi:Server.json:calls", ++ "from": "function:neuroforge/internal/httpapi:Server.workerHeartbeat", ++ "to": "function:neuroforge/internal/httpapi:Server.json", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.workerHeartbeat-\u003efunction:neuroforge/internal/httpapi:decode:calls", ++ "from": "function:neuroforge/internal/httpapi:Server.workerHeartbeat", ++ "to": "function:neuroforge/internal/httpapi:decode", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.workerHeartbeat-\u003efunction:neuroforge/internal/httpapi:workerHeartbeatFromRequest:calls", ++ "from": "function:neuroforge/internal/httpapi:Server.workerHeartbeat", ++ "to": "function:neuroforge/internal/httpapi:workerHeartbeatFromRequest", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.workerHeartbeat-\u003epackage:time:calls_package", ++ "from": "function:neuroforge/internal/httpapi:Server.workerHeartbeat", ++ "to": "package:time", ++ "kind": "calls_package", ++ "label": "Duration" ++ }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.workerRegister-\u003efunction:neuroforge/internal/httpapi:Server.err:calls", ++ "from": "function:neuroforge/internal/httpapi:Server.workerRegister", ++ "to": "function:neuroforge/internal/httpapi:Server.err", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.workerRegister-\u003efunction:neuroforge/internal/httpapi:Server.json:calls", ++ "from": "function:neuroforge/internal/httpapi:Server.workerRegister", ++ "to": "function:neuroforge/internal/httpapi:Server.json", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.workerRegister-\u003efunction:neuroforge/internal/httpapi:decode:calls", ++ "from": "function:neuroforge/internal/httpapi:Server.workerRegister", ++ "to": "function:neuroforge/internal/httpapi:decode", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/httpapi:Server.workerRegister-\u003efunction:neuroforge/internal/httpapi:workerHeartbeatFromRequest:calls", ++ "from": "function:neuroforge/internal/httpapi:Server.workerRegister", ++ "to": "function:neuroforge/internal/httpapi:workerHeartbeatFromRequest", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/httpapi:approxP95-\u003epackage:math:calls_package", + "from": "function:neuroforge/internal/httpapi:approxP95", +@@ -51808,44 +56157,17 @@ + "kind": "calls" + }, + { +- "id": "function:neuroforge/internal/provider:Router.ChatOn-\u003efunction:neuroforge/internal/provider:Router.chatOllama:calls", +- "from": "function:neuroforge/internal/provider:Router.ChatOn", +- "to": "function:neuroforge/internal/provider:Router.chatOllama", +- "kind": "calls" +- }, +- { +- "id": "function:neuroforge/internal/provider:Router.ChatOn-\u003efunction:neuroforge/internal/provider:Router.chatOpenAI:calls", +- "from": "function:neuroforge/internal/provider:Router.ChatOn", +- "to": "function:neuroforge/internal/provider:Router.chatOpenAI", ++ "id": "function:neuroforge/internal/provider:Router.ChatJSONOn-\u003efunction:neuroforge/internal/provider:Router.chatOn:calls", ++ "from": "function:neuroforge/internal/provider:Router.ChatJSONOn", ++ "to": "function:neuroforge/internal/provider:Router.chatOn", + "kind": "calls" + }, + { +- "id": "function:neuroforge/internal/provider:Router.ChatOn-\u003efunction:neuroforge/internal/provider:Router.ollamaOrderFor:calls", ++ "id": "function:neuroforge/internal/provider:Router.ChatOn-\u003efunction:neuroforge/internal/provider:Router.chatOn:calls", + "from": "function:neuroforge/internal/provider:Router.ChatOn", +- "to": "function:neuroforge/internal/provider:Router.ollamaOrderFor", ++ "to": "function:neuroforge/internal/provider:Router.chatOn", + "kind": "calls" + }, +- { +- "id": "function:neuroforge/internal/provider:Router.ChatOn-\u003epackage:errors:calls_package", +- "from": "function:neuroforge/internal/provider:Router.ChatOn", +- "to": "package:errors", +- "kind": "calls_package", +- "label": "New" +- }, +- { +- "id": "function:neuroforge/internal/provider:Router.ChatOn-\u003epackage:fmt:calls_package", +- "from": "function:neuroforge/internal/provider:Router.ChatOn", +- "to": "package:fmt", +- "kind": "calls_package", +- "label": "Errorf" +- }, +- { +- "id": "function:neuroforge/internal/provider:Router.ChatOn-\u003epackage:strings:calls_package", +- "from": "function:neuroforge/internal/provider:Router.ChatOn", +- "to": "package:strings", +- "kind": "calls_package", +- "label": "TrimSpace" +- }, + { + "id": "function:neuroforge/internal/provider:Router.Embed-\u003efunction:neuroforge/internal/provider:Router.EmbedOn:calls", + "from": "function:neuroforge/internal/provider:Router.Embed", +@@ -51970,6 +56292,45 @@ + "kind": "calls_package", + "label": "TrimSpace" + }, ++ { ++ "id": "function:neuroforge/internal/provider:Router.chatOn-\u003efunction:neuroforge/internal/provider:Router.chatOllama:calls", ++ "from": "function:neuroforge/internal/provider:Router.chatOn", ++ "to": "function:neuroforge/internal/provider:Router.chatOllama", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/provider:Router.chatOn-\u003efunction:neuroforge/internal/provider:Router.chatOpenAI:calls", ++ "from": "function:neuroforge/internal/provider:Router.chatOn", ++ "to": "function:neuroforge/internal/provider:Router.chatOpenAI", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/provider:Router.chatOn-\u003efunction:neuroforge/internal/provider:Router.ollamaOrderFor:calls", ++ "from": "function:neuroforge/internal/provider:Router.chatOn", ++ "to": "function:neuroforge/internal/provider:Router.ollamaOrderFor", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/provider:Router.chatOn-\u003epackage:errors:calls_package", ++ "from": "function:neuroforge/internal/provider:Router.chatOn", ++ "to": "package:errors", ++ "kind": "calls_package", ++ "label": "New" ++ }, ++ { ++ "id": "function:neuroforge/internal/provider:Router.chatOn-\u003epackage:fmt:calls_package", ++ "from": "function:neuroforge/internal/provider:Router.chatOn", ++ "to": "package:fmt", ++ "kind": "calls_package", ++ "label": "Errorf" ++ }, ++ { ++ "id": "function:neuroforge/internal/provider:Router.chatOn-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/provider:Router.chatOn", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, + { + "id": "function:neuroforge/internal/provider:Router.chatOpenAI-\u003efunction:neuroforge/internal/provider:Router.doJSON:calls", + "from": "function:neuroforge/internal/provider:Router.chatOpenAI", +@@ -52810,6 +57171,12 @@ + "to": "function:neuroforge/internal/store:Store.rebuildProvenanceSourceIndexLocked", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/store:New-\u003efunction:neuroforge/internal/store:Store.rebuildSynapseAdjLocked:calls", ++ "from": "function:neuroforge/internal/store:New", ++ "to": "function:neuroforge/internal/store:Store.rebuildSynapseAdjLocked", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/store:New-\u003efunction:neuroforge/internal/store:Store.replayWAL:calls", + "from": "function:neuroforge/internal/store:New", +@@ -53598,14 +57965,83 @@ + "label": "Now" + }, + { +- "id": "function:neuroforge/internal/store:Store.ClaimJob-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", +- "from": "function:neuroforge/internal/store:Store.ClaimJob", ++ "id": "function:neuroforge/internal/store:Store.CancelJob-\u003efunction:neuroforge/internal/store:New:calls", ++ "from": "function:neuroforge/internal/store:Store.CancelJob", ++ "to": "function:neuroforge/internal/store:New", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.CancelJob-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.CancelJob", + "to": "function:neuroforge/internal/store:Store.commitLocked", + "kind": "calls" + }, + { +- "id": "function:neuroforge/internal/store:Store.ClaimJob-\u003epackage:time:calls_package", ++ "id": "function:neuroforge/internal/store:Store.CancelJob-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/store:Store.CancelJob", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.CancelJob-\u003epackage:time:calls_package", ++ "from": "function:neuroforge/internal/store:Store.CancelJob", ++ "to": "package:time", ++ "kind": "calls_package", ++ "label": "Now" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.ClaimJob-\u003efunction:neuroforge/internal/store:Store.ClaimJobForWorker:calls", + "from": "function:neuroforge/internal/store:Store.ClaimJob", ++ "to": "function:neuroforge/internal/store:Store.ClaimJobForWorker", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.ClaimJobForWorker-\u003efunction:neuroforge/internal/store:New:calls", ++ "from": "function:neuroforge/internal/store:Store.ClaimJobForWorker", ++ "to": "function:neuroforge/internal/store:New", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.ClaimJobForWorker-\u003efunction:neuroforge/internal/store:NewID:calls", ++ "from": "function:neuroforge/internal/store:Store.ClaimJobForWorker", ++ "to": "function:neuroforge/internal/store:NewID", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.ClaimJobForWorker-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.ClaimJobForWorker", ++ "to": "function:neuroforge/internal/store:Store.commitLocked", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.ClaimJobForWorker-\u003efunction:neuroforge/internal/store:Store.normalizeJobForClaimLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.ClaimJobForWorker", ++ "to": "function:neuroforge/internal/store:Store.normalizeJobForClaimLocked", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.ClaimJobForWorker-\u003efunction:neuroforge/internal/store:Store.registerWorkerLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.ClaimJobForWorker", ++ "to": "function:neuroforge/internal/store:Store.registerWorkerLocked", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.ClaimJobForWorker-\u003efunction:neuroforge/internal/store:workerMatchesJob:calls", ++ "from": "function:neuroforge/internal/store:Store.ClaimJobForWorker", ++ "to": "function:neuroforge/internal/store:workerMatchesJob", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.ClaimJobForWorker-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/store:Store.ClaimJobForWorker", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.ClaimJobForWorker-\u003epackage:time:calls_package", ++ "from": "function:neuroforge/internal/store:Store.ClaimJobForWorker", + "to": "package:time", + "kind": "calls_package", + "label": "Now" +@@ -53761,20 +58197,46 @@ + "kind": "calls" + }, + { +- "id": "function:neuroforge/internal/store:Store.CompleteJob-\u003efunction:neuroforge/internal/store:New:calls", ++ "id": "function:neuroforge/internal/store:Store.CompleteJob-\u003efunction:neuroforge/internal/store:Store.CompleteJobLease:calls", + "from": "function:neuroforge/internal/store:Store.CompleteJob", ++ "to": "function:neuroforge/internal/store:Store.CompleteJobLease", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.CompleteJobLease-\u003efunction:neuroforge/internal/store:New:calls", ++ "from": "function:neuroforge/internal/store:Store.CompleteJobLease", + "to": "function:neuroforge/internal/store:New", + "kind": "calls" + }, + { +- "id": "function:neuroforge/internal/store:Store.CompleteJob-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", +- "from": "function:neuroforge/internal/store:Store.CompleteJob", ++ "id": "function:neuroforge/internal/store:Store.CompleteJobLease-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.CompleteJobLease", + "to": "function:neuroforge/internal/store:Store.commitLocked", + "kind": "calls" + }, + { +- "id": "function:neuroforge/internal/store:Store.CompleteJob-\u003epackage:time:calls_package", +- "from": "function:neuroforge/internal/store:Store.CompleteJob", ++ "id": "function:neuroforge/internal/store:Store.CompleteJobLease-\u003efunction:neuroforge/internal/store:maxInt:calls", ++ "from": "function:neuroforge/internal/store:Store.CompleteJobLease", ++ "to": "function:neuroforge/internal/store:maxInt", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.CompleteJobLease-\u003epackage:fmt:calls_package", ++ "from": "function:neuroforge/internal/store:Store.CompleteJobLease", ++ "to": "package:fmt", ++ "kind": "calls_package", ++ "label": "Errorf" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.CompleteJobLease-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/store:Store.CompleteJobLease", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.CompleteJobLease-\u003epackage:time:calls_package", ++ "from": "function:neuroforge/internal/store:Store.CompleteJobLease", + "to": "package:time", + "kind": "calls_package", + "label": "Now" +@@ -53860,6 +58322,12 @@ + "to": "function:neuroforge/internal/store:Store.commitLocked", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.DecayAndPruneSynapses-\u003efunction:neuroforge/internal/store:Store.unindexSynapseLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.DecayAndPruneSynapses", ++ "to": "function:neuroforge/internal/store:Store.unindexSynapseLocked", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/store:Store.DecayAndPruneSynapses-\u003efunction:neuroforge/internal/store:pow:calls", + "from": "function:neuroforge/internal/store:Store.DecayAndPruneSynapses", +@@ -53922,6 +58390,12 @@ + "to": "function:neuroforge/internal/store:Store.unindexProvenanceSourceLocked", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.DeleteMemoriesBatch-\u003efunction:neuroforge/internal/store:Store.unindexSynapseLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.DeleteMemoriesBatch", ++ "to": "function:neuroforge/internal/store:Store.unindexSynapseLocked", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/store:Store.DeleteMemoriesBatch-\u003efunction:neuroforge/internal/store:Store.untrackHotMemoryLocked:calls", + "from": "function:neuroforge/internal/store:Store.DeleteMemoriesBatch", +@@ -53959,6 +58433,12 @@ + "to": "function:neuroforge/internal/store:Store.unindexProvenanceSourceLocked", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.DeleteMemory-\u003efunction:neuroforge/internal/store:Store.unindexSynapseLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.DeleteMemory", ++ "to": "function:neuroforge/internal/store:Store.unindexSynapseLocked", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/store:Store.DeleteMemory-\u003efunction:neuroforge/internal/store:Store.untrackHotMemoryLocked:calls", + "from": "function:neuroforge/internal/store:Store.DeleteMemory", +@@ -54028,27 +58508,65 @@ + "label": "Itoa" + }, + { +- "id": "function:neuroforge/internal/store:Store.EnqueueJob-\u003efunction:neuroforge/internal/store:NewID:calls", ++ "id": "function:neuroforge/internal/store:Store.EnqueueJob-\u003efunction:neuroforge/internal/store:Store.EnqueueJobSpec:calls", + "from": "function:neuroforge/internal/store:Store.EnqueueJob", ++ "to": "function:neuroforge/internal/store:Store.EnqueueJobSpec", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.EnqueueJobSpec-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", ++ "from": "function:neuroforge/internal/store:Store.EnqueueJobSpec", ++ "to": "function:neuroforge/internal/store:ClusterLog.append", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.EnqueueJobSpec-\u003efunction:neuroforge/internal/store:New:calls", ++ "from": "function:neuroforge/internal/store:Store.EnqueueJobSpec", ++ "to": "function:neuroforge/internal/store:New", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.EnqueueJobSpec-\u003efunction:neuroforge/internal/store:NewID:calls", ++ "from": "function:neuroforge/internal/store:Store.EnqueueJobSpec", + "to": "function:neuroforge/internal/store:NewID", + "kind": "calls" + }, + { +- "id": "function:neuroforge/internal/store:Store.EnqueueJob-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", +- "from": "function:neuroforge/internal/store:Store.EnqueueJob", ++ "id": "function:neuroforge/internal/store:Store.EnqueueJobSpec-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.EnqueueJobSpec", + "to": "function:neuroforge/internal/store:Store.commitLocked", + "kind": "calls" + }, + { +- "id": "function:neuroforge/internal/store:Store.EnqueueJob-\u003epackage:encoding/json:calls_package", +- "from": "function:neuroforge/internal/store:Store.EnqueueJob", ++ "id": "function:neuroforge/internal/store:Store.EnqueueJobSpec-\u003efunction:neuroforge/internal/store:normalizeCapabilities:calls", ++ "from": "function:neuroforge/internal/store:Store.EnqueueJobSpec", ++ "to": "function:neuroforge/internal/store:normalizeCapabilities", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.EnqueueJobSpec-\u003epackage:encoding/json:calls_package", ++ "from": "function:neuroforge/internal/store:Store.EnqueueJobSpec", + "to": "package:encoding/json", + "kind": "calls_package", + "label": "Marshal" + }, + { +- "id": "function:neuroforge/internal/store:Store.EnqueueJob-\u003epackage:time:calls_package", +- "from": "function:neuroforge/internal/store:Store.EnqueueJob", ++ "id": "function:neuroforge/internal/store:Store.EnqueueJobSpec-\u003epackage:fmt:calls_package", ++ "from": "function:neuroforge/internal/store:Store.EnqueueJobSpec", ++ "to": "package:fmt", ++ "kind": "calls_package", ++ "label": "Errorf" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.EnqueueJobSpec-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/store:Store.EnqueueJobSpec", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.EnqueueJobSpec-\u003epackage:time:calls_package", ++ "from": "function:neuroforge/internal/store:Store.EnqueueJobSpec", + "to": "package:time", + "kind": "calls_package", + "label": "Now" +@@ -54072,6 +58590,45 @@ + "kind": "calls_package", + "label": "Marshal" + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.FinishMasterApply-\u003efunction:neuroforge/internal/store:New:calls", ++ "from": "function:neuroforge/internal/store:Store.FinishMasterApply", ++ "to": "function:neuroforge/internal/store:New", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.FinishMasterApply-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.FinishMasterApply", ++ "to": "function:neuroforge/internal/store:Store.commitLocked", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.FinishMasterApply-\u003efunction:neuroforge/internal/store:maxInt:calls", ++ "from": "function:neuroforge/internal/store:Store.FinishMasterApply", ++ "to": "function:neuroforge/internal/store:maxInt", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.FinishMasterApply-\u003epackage:fmt:calls_package", ++ "from": "function:neuroforge/internal/store:Store.FinishMasterApply", ++ "to": "package:fmt", ++ "kind": "calls_package", ++ "label": "Errorf" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.FinishMasterApply-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/store:Store.FinishMasterApply", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.FinishMasterApply-\u003epackage:time:calls_package", ++ "from": "function:neuroforge/internal/store:Store.FinishMasterApply", ++ "to": "package:time", ++ "kind": "calls_package", ++ "label": "Now" ++ }, + { + "id": "function:neuroforge/internal/store:Store.FinishResearchRun-\u003efunction:neuroforge/internal/store:New:calls", + "from": "function:neuroforge/internal/store:Store.FinishResearchRun", +@@ -54172,6 +58729,108 @@ + "kind": "calls_package", + "label": "Now" + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.GraphBackfillCandidates-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", ++ "from": "function:neuroforge/internal/store:Store.GraphBackfillCandidates", ++ "to": "function:neuroforge/internal/store:ClusterLog.append", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.GraphBackfillCandidates-\u003efunction:neuroforge/internal/store:Store.fullMemoryForReadLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.GraphBackfillCandidates", ++ "to": "function:neuroforge/internal/store:Store.fullMemoryForReadLocked", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.GraphBackfillCandidates-\u003efunction:neuroforge/internal/store:cloneMemory:calls", ++ "from": "function:neuroforge/internal/store:Store.GraphBackfillCandidates", ++ "to": "function:neuroforge/internal/store:cloneMemory", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.GraphBackfillCandidates-\u003efunction:neuroforge/internal/store:memorySearchable:calls", ++ "from": "function:neuroforge/internal/store:Store.GraphBackfillCandidates", ++ "to": "function:neuroforge/internal/store:memorySearchable", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.GraphBackfillCandidates-\u003epackage:sort:calls_package", ++ "from": "function:neuroforge/internal/store:Store.GraphBackfillCandidates", ++ "to": "package:sort", ++ "kind": "calls_package", ++ "label": "Strings" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.GraphBackfillCandidates-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/store:Store.GraphBackfillCandidates", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "HasPrefix" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.GraphBackfillCandidates-\u003epackage:time:calls_package", ++ "from": "function:neuroforge/internal/store:Store.GraphBackfillCandidates", ++ "to": "package:time", ++ "kind": "calls_package", ++ "label": "Now" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.GraphStats-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", ++ "from": "function:neuroforge/internal/store:Store.GraphStats", ++ "to": "function:neuroforge/internal/store:ClusterLog.append", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.HasLiveWorker-\u003efunction:neuroforge/internal/store:Store.WorkersSnapshot:calls", ++ "from": "function:neuroforge/internal/store:Store.HasLiveWorker", ++ "to": "function:neuroforge/internal/store:Store.WorkersSnapshot", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.HasLiveWorker-\u003efunction:neuroforge/internal/store:capabilitiesContain:calls", ++ "from": "function:neuroforge/internal/store:Store.HasLiveWorker", ++ "to": "function:neuroforge/internal/store:capabilitiesContain", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.HasLiveWorker-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/store:Store.HasLiveWorker", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "ToLower" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.HeartbeatWorker-\u003efunction:neuroforge/internal/store:New:calls", ++ "from": "function:neuroforge/internal/store:Store.HeartbeatWorker", ++ "to": "function:neuroforge/internal/store:New", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.HeartbeatWorker-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.HeartbeatWorker", ++ "to": "function:neuroforge/internal/store:Store.commitLocked", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.HeartbeatWorker-\u003efunction:neuroforge/internal/store:Store.registerWorkerLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.HeartbeatWorker", ++ "to": "function:neuroforge/internal/store:Store.registerWorkerLocked", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.HeartbeatWorker-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/store:Store.HeartbeatWorker", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.HeartbeatWorker-\u003epackage:time:calls_package", ++ "from": "function:neuroforge/internal/store:Store.HeartbeatWorker", ++ "to": "package:time", ++ "kind": "calls_package", ++ "label": "Now" ++ }, + { + "id": "function:neuroforge/internal/store:Store.IndexSnapshotStatus-\u003efunction:neuroforge/internal/store:Store.IndexSnapshotStatusUnlocked:calls", + "from": "function:neuroforge/internal/store:Store.IndexSnapshotStatus", +@@ -54185,6 +58844,39 @@ + "kind": "calls_package", + "label": "Marshal" + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.Job-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", ++ "from": "function:neuroforge/internal/store:Store.Job", ++ "to": "function:neuroforge/internal/store:ClusterLog.append", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.Job-\u003epackage:encoding/json:calls_package", ++ "from": "function:neuroforge/internal/store:Store.Job", ++ "to": "package:encoding/json", ++ "kind": "calls_package", ++ "label": "RawMessage" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.JobsSnapshot-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", ++ "from": "function:neuroforge/internal/store:Store.JobsSnapshot", ++ "to": "function:neuroforge/internal/store:ClusterLog.append", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.JobsSnapshot-\u003epackage:sort:calls_package", ++ "from": "function:neuroforge/internal/store:Store.JobsSnapshot", ++ "to": "package:sort", ++ "kind": "calls_package", ++ "label": "Slice" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.JobsSnapshot-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/store:Store.JobsSnapshot", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, + { + "id": "function:neuroforge/internal/store:Store.KnowledgeGraph-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", + "from": "function:neuroforge/internal/store:Store.KnowledgeGraph", +@@ -54341,6 +59033,31 @@ + "to": "function:neuroforge/internal/store:cloneMemory", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.MarkGraphLinked-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.MarkGraphLinked", ++ "to": "function:neuroforge/internal/store:Store.commitLocked", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.MarkGraphLinked-\u003efunction:neuroforge/internal/store:Store.materializeMemoryLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.MarkGraphLinked", ++ "to": "function:neuroforge/internal/store:Store.materializeMemoryLocked", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.MarkGraphLinked-\u003efunction:neuroforge/internal/store:cloneMemory:calls", ++ "from": "function:neuroforge/internal/store:Store.MarkGraphLinked", ++ "to": "function:neuroforge/internal/store:cloneMemory", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.MarkGraphLinked-\u003epackage:time:calls_package", ++ "from": "function:neuroforge/internal/store:Store.MarkGraphLinked", ++ "to": "package:time", ++ "kind": "calls_package", ++ "label": "Now" ++ }, + { + "id": "function:neuroforge/internal/store:Store.MemoriesSnapshot-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", + "from": "function:neuroforge/internal/store:Store.MemoriesSnapshot", +@@ -54416,6 +59133,12 @@ + "to": "function:neuroforge/internal/store:previewHeap.Len", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.OrchestratorStatus-\u003efunction:neuroforge/internal/store:Store.WorkersSnapshot:calls", ++ "from": "function:neuroforge/internal/store:Store.OrchestratorStatus", ++ "to": "function:neuroforge/internal/store:Store.WorkersSnapshot", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/store:Store.PauseGoal-\u003efunction:neuroforge/internal/store:New:calls", + "from": "function:neuroforge/internal/store:Store.PauseGoal", +@@ -54494,6 +59217,33 @@ + "kind": "calls_package", + "label": "HasSuffix" + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.PendingMasterApplyJobs-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", ++ "from": "function:neuroforge/internal/store:Store.PendingMasterApplyJobs", ++ "to": "function:neuroforge/internal/store:ClusterLog.append", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.PendingMasterApplyJobs-\u003epackage:encoding/json:calls_package", ++ "from": "function:neuroforge/internal/store:Store.PendingMasterApplyJobs", ++ "to": "package:encoding/json", ++ "kind": "calls_package", ++ "label": "RawMessage" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.PendingMasterApplyJobs-\u003epackage:sort:calls_package", ++ "from": "function:neuroforge/internal/store:Store.PendingMasterApplyJobs", ++ "to": "package:sort", ++ "kind": "calls_package", ++ "label": "Slice" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.PendingMasterApplyJobs-\u003epackage:time:calls_package", ++ "from": "function:neuroforge/internal/store:Store.PendingMasterApplyJobs", ++ "to": "package:time", ++ "kind": "calls_package", ++ "label": "Now" ++ }, + { + "id": "function:neuroforge/internal/store:Store.PrepareClusterEntry-\u003efunction:neuroforge/internal/store:New:calls", + "from": "function:neuroforge/internal/store:Store.PrepareClusterEntry", +@@ -54538,6 +59288,32 @@ + "kind": "calls_package", + "label": "Join" + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.PruneTerminalJobs-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", ++ "from": "function:neuroforge/internal/store:Store.PruneTerminalJobs", ++ "to": "function:neuroforge/internal/store:ClusterLog.append", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.PruneTerminalJobs-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.PruneTerminalJobs", ++ "to": "function:neuroforge/internal/store:Store.commitLocked", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.PruneTerminalJobs-\u003epackage:sort:calls_package", ++ "from": "function:neuroforge/internal/store:Store.PruneTerminalJobs", ++ "to": "package:sort", ++ "kind": "calls_package", ++ "label": "Slice" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.PruneTerminalJobs-\u003epackage:time:calls_package", ++ "from": "function:neuroforge/internal/store:Store.PruneTerminalJobs", ++ "to": "package:time", ++ "kind": "calls_package", ++ "label": "Now" ++ }, + { + "id": "function:neuroforge/internal/store:Store.RebuildDiskANN-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", + "from": "function:neuroforge/internal/store:Store.RebuildDiskANN", +@@ -54747,33 +59523,77 @@ + "label": "Now" + }, + { +- "id": "function:neuroforge/internal/store:Store.Reinforce-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", ++ "id": "function:neuroforge/internal/store:Store.RegisterWorker-\u003efunction:neuroforge/internal/store:New:calls", ++ "from": "function:neuroforge/internal/store:Store.RegisterWorker", ++ "to": "function:neuroforge/internal/store:New", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.RegisterWorker-\u003efunction:neuroforge/internal/store:Store.registerWorkerLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.RegisterWorker", ++ "to": "function:neuroforge/internal/store:Store.registerWorkerLocked", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.RegisterWorker-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/store:Store.RegisterWorker", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "TrimSpace" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.RegisterWorker-\u003epackage:time:calls_package", ++ "from": "function:neuroforge/internal/store:Store.RegisterWorker", ++ "to": "package:time", ++ "kind": "calls_package", ++ "label": "Now" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.Reinforce-\u003efunction:neuroforge/internal/store:Store.ReinforceRelation:calls", + "from": "function:neuroforge/internal/store:Store.Reinforce", ++ "to": "function:neuroforge/internal/store:Store.ReinforceRelation", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.ReinforceRelation-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.ReinforceRelation", + "to": "function:neuroforge/internal/store:Store.commitLocked", + "kind": "calls" + }, + { +- "id": "function:neuroforge/internal/store:Store.Reinforce-\u003efunction:neuroforge/internal/store:edgeKey:calls", +- "from": "function:neuroforge/internal/store:Store.Reinforce", ++ "id": "function:neuroforge/internal/store:Store.ReinforceRelation-\u003efunction:neuroforge/internal/store:Store.indexSynapseLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.ReinforceRelation", ++ "to": "function:neuroforge/internal/store:Store.indexSynapseLocked", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.ReinforceRelation-\u003efunction:neuroforge/internal/store:appendUniqueRelation:calls", ++ "from": "function:neuroforge/internal/store:Store.ReinforceRelation", ++ "to": "function:neuroforge/internal/store:appendUniqueRelation", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.ReinforceRelation-\u003efunction:neuroforge/internal/store:edgeKey:calls", ++ "from": "function:neuroforge/internal/store:Store.ReinforceRelation", + "to": "function:neuroforge/internal/store:edgeKey", + "kind": "calls" + }, + { +- "id": "function:neuroforge/internal/store:Store.Reinforce-\u003efunction:neuroforge/internal/store:pow:calls", +- "from": "function:neuroforge/internal/store:Store.Reinforce", ++ "id": "function:neuroforge/internal/store:Store.ReinforceRelation-\u003efunction:neuroforge/internal/store:pow:calls", ++ "from": "function:neuroforge/internal/store:Store.ReinforceRelation", + "to": "function:neuroforge/internal/store:pow", + "kind": "calls" + }, + { +- "id": "function:neuroforge/internal/store:Store.Reinforce-\u003epackage:neuroforge/internal/vector:calls_package", +- "from": "function:neuroforge/internal/store:Store.Reinforce", ++ "id": "function:neuroforge/internal/store:Store.ReinforceRelation-\u003epackage:neuroforge/internal/vector:calls_package", ++ "from": "function:neuroforge/internal/store:Store.ReinforceRelation", + "to": "package:neuroforge/internal/vector", + "kind": "calls_package", + "label": "Clamp" + }, + { +- "id": "function:neuroforge/internal/store:Store.Reinforce-\u003epackage:time:calls_package", +- "from": "function:neuroforge/internal/store:Store.Reinforce", ++ "id": "function:neuroforge/internal/store:Store.ReinforceRelation-\u003epackage:time:calls_package", ++ "from": "function:neuroforge/internal/store:Store.ReinforceRelation", + "to": "package:time", + "kind": "calls_package", + "label": "Now" +@@ -54872,6 +59692,32 @@ + "kind": "calls_package", + "label": "Now" + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.RetryJob-\u003efunction:neuroforge/internal/store:New:calls", ++ "from": "function:neuroforge/internal/store:Store.RetryJob", ++ "to": "function:neuroforge/internal/store:New", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.RetryJob-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.RetryJob", ++ "to": "function:neuroforge/internal/store:Store.commitLocked", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.RetryJob-\u003epackage:fmt:calls_package", ++ "from": "function:neuroforge/internal/store:Store.RetryJob", ++ "to": "package:fmt", ++ "kind": "calls_package", ++ "label": "Errorf" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.RetryJob-\u003epackage:time:calls_package", ++ "from": "function:neuroforge/internal/store:Store.RetryJob", ++ "to": "package:time", ++ "kind": "calls_package", ++ "label": "Now" ++ }, + { + "id": "function:neuroforge/internal/store:Store.RunRetention-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", + "from": "function:neuroforge/internal/store:Store.RunRetention", +@@ -55504,6 +60350,19 @@ + "to": "function:neuroforge/internal/store:cloneGoal", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.UpsertGoal-\u003efunction:neuroforge/internal/store:goalFingerprint:calls", ++ "from": "function:neuroforge/internal/store:Store.UpsertGoal", ++ "to": "function:neuroforge/internal/store:goalFingerprint", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.UpsertGoal-\u003epackage:fmt:calls_package", ++ "from": "function:neuroforge/internal/store:Store.UpsertGoal", ++ "to": "package:fmt", ++ "kind": "calls_package", ++ "label": "Errorf" ++ }, + { + "id": "function:neuroforge/internal/store:Store.UpsertGoal-\u003epackage:neuroforge/internal/vector:calls_package", + "from": "function:neuroforge/internal/store:Store.UpsertGoal", +@@ -55525,6 +60384,44 @@ + "kind": "calls_package", + "label": "Now" + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.UpsertRelationEvidence-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.UpsertRelationEvidence", ++ "to": "function:neuroforge/internal/store:Store.commitLocked", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.UpsertRelationEvidence-\u003efunction:neuroforge/internal/store:Store.indexSynapseLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.UpsertRelationEvidence", ++ "to": "function:neuroforge/internal/store:Store.indexSynapseLocked", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.UpsertRelationEvidence-\u003efunction:neuroforge/internal/store:appendUniqueRelation:calls", ++ "from": "function:neuroforge/internal/store:Store.UpsertRelationEvidence", ++ "to": "function:neuroforge/internal/store:appendUniqueRelation", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.UpsertRelationEvidence-\u003efunction:neuroforge/internal/store:edgeKey:calls", ++ "from": "function:neuroforge/internal/store:Store.UpsertRelationEvidence", ++ "to": "function:neuroforge/internal/store:edgeKey", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.UpsertRelationEvidence-\u003epackage:neuroforge/internal/vector:calls_package", ++ "from": "function:neuroforge/internal/store:Store.UpsertRelationEvidence", ++ "to": "package:neuroforge/internal/vector", ++ "kind": "calls_package", ++ "label": "Clamp" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.UpsertRelationEvidence-\u003epackage:time:calls_package", ++ "from": "function:neuroforge/internal/store:Store.UpsertRelationEvidence", ++ "to": "package:time", ++ "kind": "calls_package", ++ "label": "Now" ++ }, + { + "id": "function:neuroforge/internal/store:Store.UpsertSource-\u003efunction:neuroforge/internal/store:New:calls", + "from": "function:neuroforge/internal/store:Store.UpsertSource", +@@ -55602,6 +60499,26 @@ + "kind": "calls_package", + "label": "HasSuffix" + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.WorkersSnapshot-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", ++ "from": "function:neuroforge/internal/store:Store.WorkersSnapshot", ++ "to": "function:neuroforge/internal/store:ClusterLog.append", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.WorkersSnapshot-\u003epackage:sort:calls_package", ++ "from": "function:neuroforge/internal/store:Store.WorkersSnapshot", ++ "to": "package:sort", ++ "kind": "calls_package", ++ "label": "Slice" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.WorkersSnapshot-\u003epackage:time:calls_package", ++ "from": "function:neuroforge/internal/store:Store.WorkersSnapshot", ++ "to": "package:time", ++ "kind": "calls_package", ++ "label": "Now" ++ }, + { + "id": "function:neuroforge/internal/store:Store.appendClusterLogDecision-\u003efunction:neuroforge/internal/store:ClusterLog.AppendDecision:calls", + "from": "function:neuroforge/internal/store:Store.appendClusterLogDecision", +@@ -55685,6 +60602,18 @@ + "to": "function:neuroforge/internal/store:ClusterLog.append", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.applyWALEvent-\u003efunction:neuroforge/internal/store:Store.indexSynapseLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.applyWALEvent", ++ "to": "function:neuroforge/internal/store:Store.indexSynapseLocked", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.applyWALEvent-\u003efunction:neuroforge/internal/store:Store.rebuildSynapseAdjLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.applyWALEvent", ++ "to": "function:neuroforge/internal/store:Store.rebuildSynapseAdjLocked", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/store:Store.applyWALEvent-\u003efunction:neuroforge/internal/store:applyResearchEvent:calls", + "from": "function:neuroforge/internal/store:Store.applyWALEvent", +@@ -55867,6 +60796,44 @@ + "to": "function:neuroforge/internal/store:cloneMemory", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.graphExpandLocked-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", ++ "from": "function:neuroforge/internal/store:Store.graphExpandLocked", ++ "to": "function:neuroforge/internal/store:ClusterLog.append", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.graphExpandLocked-\u003efunction:neuroforge/internal/store:Store.fullMemoryForReadLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.graphExpandLocked", ++ "to": "function:neuroforge/internal/store:Store.fullMemoryForReadLocked", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.graphExpandLocked-\u003efunction:neuroforge/internal/store:cloneMemory:calls", ++ "from": "function:neuroforge/internal/store:Store.graphExpandLocked", ++ "to": "function:neuroforge/internal/store:cloneMemory", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.graphExpandLocked-\u003efunction:neuroforge/internal/store:memorySearchable:calls", ++ "from": "function:neuroforge/internal/store:Store.graphExpandLocked", ++ "to": "function:neuroforge/internal/store:memorySearchable", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.graphExpandLocked-\u003epackage:neuroforge/internal/vector:calls_package", ++ "from": "function:neuroforge/internal/store:Store.graphExpandLocked", ++ "to": "package:neuroforge/internal/vector", ++ "kind": "calls_package", ++ "label": "Cosine" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.graphExpandLocked-\u003epackage:sort:calls_package", ++ "from": "function:neuroforge/internal/store:Store.graphExpandLocked", ++ "to": "package:sort", ++ "kind": "calls_package", ++ "label": "Slice" ++ }, + { + "id": "function:neuroforge/internal/store:Store.indexCountMatchesLocked-\u003efunction:neuroforge/internal/store:indexMode:calls", + "from": "function:neuroforge/internal/store:Store.indexCountMatchesLocked", +@@ -56111,6 +61078,31 @@ + "kind": "calls_package", + "label": "NewHNSW" + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.normalizeJobForClaimLocked-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.normalizeJobForClaimLocked", ++ "to": "function:neuroforge/internal/store:Store.commitLocked", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.normalizeJobForClaimLocked-\u003efunction:neuroforge/internal/store:Store.dependencyStateLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.normalizeJobForClaimLocked", ++ "to": "function:neuroforge/internal/store:Store.dependencyStateLocked", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.normalizeJobForClaimLocked-\u003efunction:neuroforge/internal/store:maxInt:calls", ++ "from": "function:neuroforge/internal/store:Store.normalizeJobForClaimLocked", ++ "to": "function:neuroforge/internal/store:maxInt", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.normalizeJobForClaimLocked-\u003epackage:time:calls_package", ++ "from": "function:neuroforge/internal/store:Store.normalizeJobForClaimLocked", ++ "to": "package:time", ++ "kind": "calls_package", ++ "label": "Duration" ++ }, + { + "id": "function:neuroforge/internal/store:Store.oldestHotLocked-\u003efunction:neuroforge/internal/store:previewHeap.Pop:calls", + "from": "function:neuroforge/internal/store:Store.oldestHotLocked", +@@ -56250,6 +61242,25 @@ + "to": "function:neuroforge/internal/store:Store.indexProvenanceSourceLocked", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.rebuildSynapseAdjLocked-\u003efunction:neuroforge/internal/store:Store.indexSynapseLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.rebuildSynapseAdjLocked", ++ "to": "function:neuroforge/internal/store:Store.indexSynapseLocked", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.registerWorkerLocked-\u003efunction:neuroforge/internal/store:normalizeCapabilities:calls", ++ "from": "function:neuroforge/internal/store:Store.registerWorkerLocked", ++ "to": "function:neuroforge/internal/store:normalizeCapabilities", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:Store.registerWorkerLocked-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/store:Store.registerWorkerLocked", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "ToLower" ++ }, + { + "id": "function:neuroforge/internal/store:Store.replayWAL-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", + "from": "function:neuroforge/internal/store:Store.replayWAL", +@@ -56406,6 +61417,12 @@ + "to": "function:neuroforge/internal/store:Store.fullMemoryForReadLocked", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/store:Store.searchVectorLocked-\u003efunction:neuroforge/internal/store:Store.graphExpandLocked:calls", ++ "from": "function:neuroforge/internal/store:Store.searchVectorLocked", ++ "to": "function:neuroforge/internal/store:Store.graphExpandLocked", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/store:Store.searchVectorLocked-\u003efunction:neuroforge/internal/store:cloneMemory:calls", + "from": "function:neuroforge/internal/store:Store.searchVectorLocked", +@@ -57112,6 +62129,12 @@ + "kind": "calls_package", + "label": "ReadFull" + }, ++ { ++ "id": "function:neuroforge/internal/store:appendUniqueRelation-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", ++ "from": "function:neuroforge/internal/store:appendUniqueRelation", ++ "to": "function:neuroforge/internal/store:ClusterLog.append", ++ "kind": "calls" ++ }, + { + "id": "function:neuroforge/internal/store:appendUniqueString-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", + "from": "function:neuroforge/internal/store:appendUniqueString", +@@ -57210,6 +62233,13 @@ + "kind": "calls_package", + "label": "Errorf" + }, ++ { ++ "id": "function:neuroforge/internal/store:capabilitiesContain-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/store:capabilitiesContain", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "ToLower" ++ }, + { + "id": "function:neuroforge/internal/store:cleanupOldIndexBases-\u003epackage:os:calls_package", + "from": "function:neuroforge/internal/store:cleanupOldIndexBases", +@@ -57329,6 +62359,13 @@ + "to": "function:neuroforge/internal/store:serializeVectorColumns", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/store:goalFingerprint-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/store:goalFingerprint", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "ToLower" ++ }, + { + "id": "function:neuroforge/internal/store:hashSnapshotNode-\u003epackage:neuroforge/internal/vector:calls_package", + "from": "function:neuroforge/internal/store:hashSnapshotNode", +@@ -57483,6 +62520,26 @@ + "to": "function:neuroforge/internal/store:New", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/store:normalizeCapabilities-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", ++ "from": "function:neuroforge/internal/store:normalizeCapabilities", ++ "to": "function:neuroforge/internal/store:ClusterLog.append", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:normalizeCapabilities-\u003epackage:sort:calls_package", ++ "from": "function:neuroforge/internal/store:normalizeCapabilities", ++ "to": "package:sort", ++ "kind": "calls_package", ++ "label": "Strings" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:normalizeCapabilities-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/store:normalizeCapabilities", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "ToLower" ++ }, + { + "id": "function:neuroforge/internal/store:openClusterLog-\u003efunction:neuroforge/internal/store:ClusterLog.scan:calls", + "from": "function:neuroforge/internal/store:openClusterLog", +@@ -57751,6 +62808,19 @@ + "to": "function:neuroforge/internal/store:paethByte", + "kind": "calls" + }, ++ { ++ "id": "function:neuroforge/internal/store:workerMatchesJob-\u003efunction:neuroforge/internal/store:capabilitiesContain:calls", ++ "from": "function:neuroforge/internal/store:workerMatchesJob", ++ "to": "function:neuroforge/internal/store:capabilitiesContain", ++ "kind": "calls" ++ }, ++ { ++ "id": "function:neuroforge/internal/store:workerMatchesJob-\u003epackage:strings:calls_package", ++ "from": "function:neuroforge/internal/store:workerMatchesJob", ++ "to": "package:strings", ++ "kind": "calls_package", ++ "label": "ToLower" ++ }, + { + "id": "function:neuroforge/internal/store:writeAtomic-\u003epackage:encoding/json:calls_package", + "from": "function:neuroforge/internal/store:writeAtomic", +@@ -59182,6 +64252,18 @@ + "to": "route:GET /api/graph/ticket", + "kind": "defines_route" + }, ++ { ++ "id": "package:mega-control-\u003eroute:GET /api/neuroforge/graph-status:defines_route", ++ "from": "package:mega-control", ++ "to": "route:GET /api/neuroforge/graph-status", ++ "kind": "defines_route" ++ }, ++ { ++ "id": "package:mega-control-\u003eroute:GET /api/neuroforge/orchestrator:defines_route", ++ "from": "package:mega-control", ++ "to": "route:GET /api/neuroforge/orchestrator", ++ "kind": "defines_route" ++ }, + { + "id": "package:mega-control-\u003eroute:GET /api/status:defines_route", + "from": "package:mega-control", +@@ -59224,18 +64306,36 @@ + "to": "file:platform/neuroforge/internal/brain/brain.go", + "kind": "contains_file" + }, ++ { ++ "id": "package:neuroforge/internal/brain-\u003efile:platform/neuroforge/internal/brain/distributed.go:contains_file", ++ "from": "package:neuroforge/internal/brain", ++ "to": "file:platform/neuroforge/internal/brain/distributed.go", ++ "kind": "contains_file" ++ }, + { + "id": "package:neuroforge/internal/brain-\u003efile:platform/neuroforge/internal/brain/goal_progress.go:contains_file", + "from": "package:neuroforge/internal/brain", + "to": "file:platform/neuroforge/internal/brain/goal_progress.go", + "kind": "contains_file" + }, ++ { ++ "id": "package:neuroforge/internal/brain-\u003efile:platform/neuroforge/internal/brain/orchestrator.go:contains_file", ++ "from": "package:neuroforge/internal/brain", ++ "to": "file:platform/neuroforge/internal/brain/orchestrator.go", ++ "kind": "contains_file" ++ }, + { + "id": "package:neuroforge/internal/brain-\u003efile:platform/neuroforge/internal/brain/policy.go:contains_file", + "from": "package:neuroforge/internal/brain", + "to": "file:platform/neuroforge/internal/brain/policy.go", + "kind": "contains_file" + }, ++ { ++ "id": "package:neuroforge/internal/brain-\u003efile:platform/neuroforge/internal/brain/research_quality.go:contains_file", ++ "from": "package:neuroforge/internal/brain", ++ "to": "file:platform/neuroforge/internal/brain/research_quality.go", ++ "kind": "contains_file" ++ }, + { + "id": "package:neuroforge/internal/brain-\u003efile:platform/neuroforge/internal/brain/research_trace.go:contains_file", + "from": "package:neuroforge/internal/brain", +@@ -59248,6 +64348,12 @@ + "to": "file:platform/neuroforge/internal/brain/staging.go", + "kind": "contains_file" + }, ++ { ++ "id": "package:neuroforge/internal/brain-\u003efile:platform/neuroforge/internal/brain/staging_quality.go:contains_file", ++ "from": "package:neuroforge/internal/brain", ++ "to": "file:platform/neuroforge/internal/brain/staging_quality.go", ++ "kind": "contains_file" ++ }, + { + "id": "package:neuroforge/internal/brain-\u003efile:platform/neuroforge/internal/brain/v3.go:contains_file", + "from": "package:neuroforge/internal/brain", +@@ -59404,6 +64510,12 @@ + "to": "route:GET /admin/api/export", + "kind": "defines_route" + }, ++ { ++ "id": "package:neuroforge/internal/httpapi-\u003eroute:GET /admin/api/graph/status:defines_route", ++ "from": "package:neuroforge/internal/httpapi", ++ "to": "route:GET /admin/api/graph/status", ++ "kind": "defines_route" ++ }, + { + "id": "package:neuroforge/internal/httpapi-\u003eroute:GET /admin/api/index/disk:defines_route", + "from": "package:neuroforge/internal/httpapi", +@@ -59458,6 +64570,18 @@ + "to": "route:GET /admin/api/model-routing", + "kind": "defines_route" + }, ++ { ++ "id": "package:neuroforge/internal/httpapi-\u003eroute:GET /admin/api/orchestrator/jobs:defines_route", ++ "from": "package:neuroforge/internal/httpapi", ++ "to": "route:GET /admin/api/orchestrator/jobs", ++ "kind": "defines_route" ++ }, ++ { ++ "id": "package:neuroforge/internal/httpapi-\u003eroute:GET /admin/api/orchestrator/status:defines_route", ++ "from": "package:neuroforge/internal/httpapi", ++ "to": "route:GET /admin/api/orchestrator/status", ++ "kind": "defines_route" ++ }, + { + "id": "package:neuroforge/internal/httpapi-\u003eroute:GET /admin/api/research:defines_route", + "from": "package:neuroforge/internal/httpapi", +@@ -59554,6 +64678,18 @@ + "to": "route:GET /api/v1/integrations/graph/research", + "kind": "defines_route" + }, ++ { ++ "id": "package:neuroforge/internal/httpapi-\u003eroute:GET /api/v1/integrations/graph/status:defines_route", ++ "from": "package:neuroforge/internal/httpapi", ++ "to": "route:GET /api/v1/integrations/graph/status", ++ "kind": "defines_route" ++ }, ++ { ++ "id": "package:neuroforge/internal/httpapi-\u003eroute:GET /api/v1/integrations/orchestrator/status:defines_route", ++ "from": "package:neuroforge/internal/httpapi", ++ "to": "route:GET /api/v1/integrations/orchestrator/status", ++ "kind": "defines_route" ++ }, + { + "id": "package:neuroforge/internal/httpapi-\u003eroute:GET /api/v1/learning-cycles:defines_route", + "from": "package:neuroforge/internal/httpapi", +@@ -59650,6 +64786,12 @@ + "to": "route:POST /admin/api/consolidate", + "kind": "defines_route" + }, ++ { ++ "id": "package:neuroforge/internal/httpapi-\u003eroute:POST /admin/api/graph/backfill:defines_route", ++ "from": "package:neuroforge/internal/httpapi", ++ "to": "route:POST /admin/api/graph/backfill", ++ "kind": "defines_route" ++ }, + { + "id": "package:neuroforge/internal/httpapi-\u003eroute:POST /admin/api/index/disk/rebuild:defines_route", + "from": "package:neuroforge/internal/httpapi", +@@ -59668,6 +64810,18 @@ + "to": "route:POST /admin/api/knowledge/search", + "kind": "defines_route" + }, ++ { ++ "id": "package:neuroforge/internal/httpapi-\u003eroute:POST /admin/api/orchestrator/jobs/{id}/cancel:defines_route", ++ "from": "package:neuroforge/internal/httpapi", ++ "to": "route:POST /admin/api/orchestrator/jobs/{id}/cancel", ++ "kind": "defines_route" ++ }, ++ { ++ "id": "package:neuroforge/internal/httpapi-\u003eroute:POST /admin/api/orchestrator/jobs/{id}/retry:defines_route", ++ "from": "package:neuroforge/internal/httpapi", ++ "to": "route:POST /admin/api/orchestrator/jobs/{id}/retry", ++ "kind": "defines_route" ++ }, + { + "id": "package:neuroforge/internal/httpapi-\u003eroute:POST /admin/api/provider-health:defines_route", + "from": "package:neuroforge/internal/httpapi", +@@ -59824,6 +64978,18 @@ + "to": "route:POST /api/v1/worker/complete", + "kind": "defines_route" + }, ++ { ++ "id": "package:neuroforge/internal/httpapi-\u003eroute:POST /api/v1/worker/heartbeat:defines_route", ++ "from": "package:neuroforge/internal/httpapi", ++ "to": "route:POST /api/v1/worker/heartbeat", ++ "kind": "defines_route" ++ }, ++ { ++ "id": "package:neuroforge/internal/httpapi-\u003eroute:POST /api/v1/worker/register:defines_route", ++ "from": "package:neuroforge/internal/httpapi", ++ "to": "route:POST /api/v1/worker/register", ++ "kind": "defines_route" ++ }, + { + "id": "package:neuroforge/internal/httpapi-\u003eroute:POST /internal/v1/cluster/abort:defines_route", + "from": "package:neuroforge/internal/httpapi", +@@ -59932,6 +65098,12 @@ + "to": "file:platform/neuroforge/internal/store/diskann.go", + "kind": "contains_file" + }, ++ { ++ "id": "package:neuroforge/internal/store-\u003efile:platform/neuroforge/internal/store/graph_index.go:contains_file", ++ "from": "package:neuroforge/internal/store", ++ "to": "file:platform/neuroforge/internal/store/graph_index.go", ++ "kind": "contains_file" ++ }, + { + "id": "package:neuroforge/internal/store-\u003efile:platform/neuroforge/internal/store/index_segments.go:contains_file", + "from": "package:neuroforge/internal/store", +@@ -59962,6 +65134,12 @@ + "to": "file:platform/neuroforge/internal/store/observability.go", + "kind": "contains_file" + }, ++ { ++ "id": "package:neuroforge/internal/store-\u003efile:platform/neuroforge/internal/store/orchestrator.go:contains_file", ++ "from": "package:neuroforge/internal/store", ++ "to": "file:platform/neuroforge/internal/store/orchestrator.go", ++ "kind": "contains_file" ++ }, + { + "id": "package:neuroforge/internal/store-\u003efile:platform/neuroforge/internal/store/pagecache.go:contains_file", + "from": "package:neuroforge/internal/store", +@@ -60136,6 +65314,12 @@ + "to": "function:neuroforge/internal/httpapi:Server.adminExport", + "kind": "handles" + }, ++ { ++ "id": "route:GET /admin/api/graph/status-\u003efunction:neuroforge/internal/httpapi:Server.adminGraphStatus:handles", ++ "from": "route:GET /admin/api/graph/status", ++ "to": "function:neuroforge/internal/httpapi:Server.adminGraphStatus", ++ "kind": "handles" ++ }, + { + "id": "route:GET /admin/api/index/disk-\u003efunction:neuroforge/internal/httpapi:Server.adminDiskANNStatus:handles", + "from": "route:GET /admin/api/index/disk", +@@ -60190,6 +65374,18 @@ + "to": "function:neuroforge/internal/httpapi:Server.adminGetModelRouting", + "kind": "handles" + }, ++ { ++ "id": "route:GET /admin/api/orchestrator/jobs-\u003efunction:neuroforge/internal/httpapi:Server.adminOrchestratorJobs:handles", ++ "from": "route:GET /admin/api/orchestrator/jobs", ++ "to": "function:neuroforge/internal/httpapi:Server.adminOrchestratorJobs", ++ "kind": "handles" ++ }, ++ { ++ "id": "route:GET /admin/api/orchestrator/status-\u003efunction:neuroforge/internal/httpapi:Server.adminOrchestratorStatus:handles", ++ "from": "route:GET /admin/api/orchestrator/status", ++ "to": "function:neuroforge/internal/httpapi:Server.adminOrchestratorStatus", ++ "kind": "handles" ++ }, + { + "id": "route:GET /admin/api/research-\u003efunction:neuroforge/internal/httpapi:Server.adminResearchGet:handles", + "from": "route:GET /admin/api/research", +@@ -60412,6 +65608,18 @@ + "to": "function:github.com/example/glpi-ai-agent/internal/web:Server.learningList", + "kind": "handles" + }, ++ { ++ "id": "route:GET /api/neuroforge/graph-status-\u003efunction:mega-control:server.handleNeuroForgeGraphStatus:handles", ++ "from": "route:GET /api/neuroforge/graph-status", ++ "to": "function:mega-control:server.handleNeuroForgeGraphStatus", ++ "kind": "handles" ++ }, ++ { ++ "id": "route:GET /api/neuroforge/orchestrator-\u003efunction:mega-control:server.handleNeuroForgeOrchestrator:handles", ++ "from": "route:GET /api/neuroforge/orchestrator", ++ "to": "function:mega-control:server.handleNeuroForgeOrchestrator", ++ "kind": "handles" ++ }, + { + "id": "route:GET /api/outcomes-\u003efunction:github.com/example/glpi-ai-agent/internal/web:Server.outcomeList:handles", + "from": "route:GET /api/outcomes", +@@ -60496,6 +65704,18 @@ + "to": "function:neuroforge/internal/httpapi:Server.integrationResearchGraph", + "kind": "handles" + }, ++ { ++ "id": "route:GET /api/v1/integrations/graph/status-\u003efunction:neuroforge/internal/httpapi:Server.integrationGraphStatus:handles", ++ "from": "route:GET /api/v1/integrations/graph/status", ++ "to": "function:neuroforge/internal/httpapi:Server.integrationGraphStatus", ++ "kind": "handles" ++ }, ++ { ++ "id": "route:GET /api/v1/integrations/orchestrator/status-\u003efunction:neuroforge/internal/httpapi:Server.integrationOrchestratorStatus:handles", ++ "from": "route:GET /api/v1/integrations/orchestrator/status", ++ "to": "function:neuroforge/internal/httpapi:Server.integrationOrchestratorStatus", ++ "kind": "handles" ++ }, + { + "id": "route:GET /api/v1/learning-cycles-\u003efunction:neuroforge/internal/httpapi:Server.learningCycles:handles", + "from": "route:GET /api/v1/learning-cycles", +@@ -60616,6 +65836,12 @@ + "to": "function:neuroforge/internal/httpapi:Server.adminConsolidate", + "kind": "handles" + }, ++ { ++ "id": "route:POST /admin/api/graph/backfill-\u003efunction:neuroforge/internal/httpapi:Server.adminGraphBackfill:handles", ++ "from": "route:POST /admin/api/graph/backfill", ++ "to": "function:neuroforge/internal/httpapi:Server.adminGraphBackfill", ++ "kind": "handles" ++ }, + { + "id": "route:POST /admin/api/index/disk/rebuild-\u003efunction:neuroforge/internal/httpapi:Server.adminDiskANNBuild:handles", + "from": "route:POST /admin/api/index/disk/rebuild", +@@ -60634,6 +65860,18 @@ + "to": "function:neuroforge/internal/httpapi:Server.adminKnowledgeSearch", + "kind": "handles" + }, ++ { ++ "id": "route:POST /admin/api/orchestrator/jobs/{id}/cancel-\u003efunction:neuroforge/internal/httpapi:Server.adminOrchestratorCancel:handles", ++ "from": "route:POST /admin/api/orchestrator/jobs/{id}/cancel", ++ "to": "function:neuroforge/internal/httpapi:Server.adminOrchestratorCancel", ++ "kind": "handles" ++ }, ++ { ++ "id": "route:POST /admin/api/orchestrator/jobs/{id}/retry-\u003efunction:neuroforge/internal/httpapi:Server.adminOrchestratorRetry:handles", ++ "from": "route:POST /admin/api/orchestrator/jobs/{id}/retry", ++ "to": "function:neuroforge/internal/httpapi:Server.adminOrchestratorRetry", ++ "kind": "handles" ++ }, + { + "id": "route:POST /admin/api/provider-health-\u003efunction:neuroforge/internal/httpapi:Server.adminProviderHealth:handles", + "from": "route:POST /admin/api/provider-health", +@@ -60880,6 +66118,18 @@ + "to": "function:neuroforge/internal/httpapi:Server.workerComplete", + "kind": "handles" + }, ++ { ++ "id": "route:POST /api/v1/worker/heartbeat-\u003efunction:neuroforge/internal/httpapi:Server.workerHeartbeat:handles", ++ "from": "route:POST /api/v1/worker/heartbeat", ++ "to": "function:neuroforge/internal/httpapi:Server.workerHeartbeat", ++ "kind": "handles" ++ }, ++ { ++ "id": "route:POST /api/v1/worker/register-\u003efunction:neuroforge/internal/httpapi:Server.workerRegister:handles", ++ "from": "route:POST /api/v1/worker/register", ++ "to": "function:neuroforge/internal/httpapi:Server.workerRegister", ++ "kind": "handles" ++ }, + { + "id": "route:POST /internal/v1/cluster/abort-\u003efunction:neuroforge/internal/httpapi:Server.clusterAbort:handles", + "from": "route:POST /internal/v1/cluster/abort", +@@ -61019,17 +66269,29 @@ + "kind": "depends_on" + }, + { +- "id": "service:neuroforge-worker-\u003eservice:neuroforge:depends_on", +- "from": "service:neuroforge-worker", ++ "id": "service:neuroforge-worker-cpu-\u003eservice:neuroforge:depends_on", ++ "from": "service:neuroforge-worker-cpu", + "to": "service:neuroforge", + "kind": "depends_on" ++ }, ++ { ++ "id": "service:neuroforge-worker-gpu-\u003eservice:neuroforge:depends_on", ++ "from": "service:neuroforge-worker-gpu", ++ "to": "service:neuroforge", ++ "kind": "depends_on" ++ }, ++ { ++ "id": "service:neuroforge-worker-gpu-\u003eservice:ollama:depends_on", ++ "from": "service:neuroforge-worker-gpu", ++ "to": "service:ollama", ++ "kind": "depends_on" + } + ], + "meta": { +- "edges": 6633, ++ "edges": 7210, + "format_version": 1, + "generator": "go-ast+compose", + "modules": 4, +- "nodes": 1689 ++ "nodes": 1830 + } + } +diff --git a/services/control/index.html b/services/control/index.html +index bb3b7ee..2b6ab47 100644 +--- a/services/control/index.html ++++ b/services/control/index.html +@@ -9,6 +9,8 @@ + +

Service Status

10-Sekunden-Refresh · optionale Engineering-Komponenten beeinflussen Readiness nicht
+ ++

Orchestrator & Knowledge Graph

Master/Subagent-Leases · Queue-Zustand · n:m-Graphgesundheit
++ +
+ + +@@ -29,7 +31,7 @@ const palette={ticket:'#7eb6ff',run:'#9a8cff',knowledge:'#70d5ae',validated_outc + function nodeColor(n){if(n.status==='problem'||n.status==='error'||n.status==='fail'||n.status==='failed')return '#ff7f92';if(n.status==='superseded'||n.status==='archived')return '#65758a';if(n.status==='corrected'||n.status==='corroborated')return '#ffd16d';return palette[n.kind]||palette[n.group]||'#9ab0c8'} + function resize(){let r=canvas.getBoundingClientRect(),d=devicePixelRatio||1;canvas.width=Math.round(r.width*d);canvas.height=Math.round(r.height*d);ctx.setTransform(d,0,0,d,0,0);draw()}addEventListener('resize',resize); + async function jget(url){let r=await fetch(url,{cache:'no-store'});if(!r.ok)throw new Error(await r.text()||r.statusText);return r.json()} +-async function loadStatus(){try{const [c,r]=await Promise.all([jget('/api/config'),jget('/api/status')]);config=c;mode.textContent=c.vector_backend;controlled.textContent=c.controlled_learning==='true'?'ON':'OFF';outcomeRetrieval.textContent=(c.outcome_retrieval==='true'?'ON':'OFF')+' · K='+c.outcome_retrieval_search_k+' · min '+c.outcome_retrieval_min_similarity;research.textContent=(c.research_enabled==='true'?'ON':'OFF')+' / '+(c.searxng_enabled==='true'?'SearXNG':'kein SearXNG')+' / '+(c.kb_staging_bridge==='true'?'Staging':'kein Staging');autonomy.textContent=c.autonomy_enabled==='true'?'ON':'OFF';services.innerHTML=(r.services||[]).map(s=>`

${esc(s.name)}${s.optional?' (optional)':''}

${s.ok?'ONLINE':s.optional?'OPTIONAL/OFFLINE':'PROBLEM'} · HTTP ${s.status||'–'} · ${s.latency_ms} ms
${s.public_url?`

Oberfläche öffnen

`:''}
${esc(JSON.stringify(s.detail||s.error||{},null,2))}
`).join('')}catch(e){services.innerHTML=`
Status nicht verfügbar: ${esc(e.message)}
`}} ++async function loadStatus(){try{const [c,r,o,g]=await Promise.all([jget('/api/config'),jget('/api/status'),jget('/api/neuroforge/orchestrator'),jget('/api/neuroforge/graph-status')]);config=c;mode.textContent=c.vector_backend;controlled.textContent=c.controlled_learning==='true'?'ON':'OFF';outcomeRetrieval.textContent=(c.outcome_retrieval==='true'?'ON':'OFF')+' · K='+c.outcome_retrieval_search_k+' · min '+c.outcome_retrieval_min_similarity;research.textContent=(c.research_enabled==='true'?'ON':'OFF')+' / '+(c.searxng_enabled==='true'?'SearXNG':'kein SearXNG')+' / '+(c.kb_staging_bridge==='true'?'Staging':'kein Staging');autonomy.textContent=c.autonomy_enabled==='true'?'ON':'OFF';services.innerHTML=(r.services||[]).map(s=>`

${esc(s.name)}${s.optional?' (optional)':''}

${s.ok?'ONLINE':s.optional?'OPTIONAL/OFFLINE':'PROBLEM'} · HTTP ${s.status||'–'} · ${s.latency_ms} ms
${s.public_url?`

Oberfläche öffnen

`:''}
${esc(JSON.stringify(s.detail||s.error||{},null,2))}
`).join('');let os=o.status||{},gs=g.stats||{},workers=os.workers||[],online=workers.filter(x=>x.status==='online').length,stale=workers.filter(x=>x.status==='stale').length;orchestration.innerHTML=`

Master / Scheduler

${o.leader?'LEADER':'FOLLOWER'}
${esc(JSON.stringify({queued:os.queued||0,claimed:os.claimed||0,retrying:os.retrying||0,applying:os.applying||0,blocked:os.blocked||0,failed:os.failed||0},null,2))}

Subagents

${online} ONLINE · ${stale} stale
${esc(JSON.stringify(workers,null,2))}

Knowledge Graph

${gs.synapses||0} Kanten · Grad Ø ${Number(gs.average_degree||0).toFixed(2)}
${esc(JSON.stringify(gs,null,2))}

Graph Connectivity

${gs.multi_linked_memories||0} n:m-Knoten
${esc(JSON.stringify({linked:gs.linked_memories||0,isolated:gs.isolated_memories||0,max_degree:gs.max_degree||0,components:gs.connected_components||0,largest_component:gs.largest_component||0},null,2))}
`}catch(e){services.innerHTML=`
Status nicht verfügbar: ${esc(e.message)}
`;orchestration.innerHTML=`
Orchestrator/Graph-Status nicht verfügbar: ${esc(e.message)}
`}} + async function loadRuns(){try{let xs=await jget('/api/graph/runs?limit=80');runSelect.innerHTML=''+xs.map(x=>``).join('')}catch(e){runSelect.innerHTML=''}} + function endpoint(){let s=scope.value,b=budget.value,q=search.value.trim();if(s==='runtime')return '/api/graph/runtime';if(s==='ticket')return runSelect.value?'/api/graph/ticket?run_id='+encodeURIComponent(runSelect.value):'';if(s==='learning')return '/api/graph/learning?limit='+Math.min(500,Number(b)||180);if(s==='research')return '/api/graph/research?runs=8&max_events='+Math.min(800,Number(b)||320);if(s==='brain')return '/api/graph/brain?max_nodes='+Math.min(700,Number(b)||320);if(s==='engineering')return '/api/graph/engineering?max_nodes='+encodeURIComponent(b)+(q?'&q='+encodeURIComponent(q):'');if(s==='impact')return q?'/api/graph/impact?max_nodes='+encodeURIComponent(b)+'&depth=2&q='+encodeURIComponent(q):'';return ''} + async function loadGraph(){let u=endpoint();if(!u){graph={nodes:[],edges:[],title:'Run auswählen'};applyGraph();return}graphMeta.textContent='lädt…';try{graph=await jget(u);selected='';neighborhood=null;applyGraph()}catch(e){graph={nodes:[],edges:[],title:'Fehler',meta:{error:e.message}};applyGraph();graphMeta.textContent='Fehler: '+e.message}} +diff --git a/services/control/main.go b/services/control/main.go +index 4b1dc00..d11d024 100644 +--- a/services/control/main.go ++++ b/services/control/main.go +@@ -105,6 +105,8 @@ func main() { + mux.HandleFunc("GET /api/graph/learning", s.handleLearningGraph) + mux.HandleFunc("GET /api/graph/research", s.handleResearchGraph) + mux.HandleFunc("GET /api/graph/brain", s.handleBrainGraph) ++ mux.HandleFunc("GET /api/neuroforge/orchestrator", s.handleNeuroForgeOrchestrator) ++ mux.HandleFunc("GET /api/neuroforge/graph-status", s.handleNeuroForgeGraphStatus) + mux.HandleFunc("GET /api/graph/engineering", s.handleEngineeringGraph) + mux.HandleFunc("GET /api/graph/impact", s.handleEngineeringImpact) + mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) { +@@ -144,6 +146,14 @@ func main() { + } + } + ++func (s *server) handleNeuroForgeOrchestrator(w http.ResponseWriter, r *http.Request) { ++ s.proxyJSON(w, r, s.neuroforgeURL+"/api/v1/integrations/orchestrator/status", bearerHeader(s.neuroforgeKey)) ++} ++ ++func (s *server) handleNeuroForgeGraphStatus(w http.ResponseWriter, r *http.Request) { ++ s.proxyJSON(w, r, s.neuroforgeURL+"/api/v1/integrations/graph/status", bearerHeader(s.neuroforgeKey)) ++} ++ + func validateControlSecrets() error { + check := func(name string, min int) error { + v := strings.TrimSpace(os.Getenv(name)) diff --git a/platform/neuroforge/cmd/server/main.go b/platform/neuroforge/cmd/server/main.go index 0f438cd..9886bcd 100644 --- a/platform/neuroforge/cmd/server/main.go +++ b/platform/neuroforge/cmd/server/main.go @@ -254,6 +254,108 @@ func run() (retErr error) { } } + // Master/subagent orchestrator and graph bootstrap. Every option is explicit + // and environment-owned only when set, preserving admin-managed config otherwise. + workerEnv := []string{ + "NEUROFORGE_WORKER_LEASE_SECONDS", "NEUROFORGE_WORKER_HEARTBEAT_SECONDS", "NEUROFORGE_WORKER_STALE_AFTER_SECONDS", + "NEUROFORGE_WORKER_DEFAULT_MAX_ATTEMPTS", "NEUROFORGE_WORKER_RETRY_BACKOFF_SECONDS", "NEUROFORGE_WORKER_MAX_QUEUED_JOBS", + "NEUROFORGE_WORKER_JOB_RETENTION_HOURS", "NEUROFORGE_WORKER_MAX_TERMINAL_JOBS", + "NEUROFORGE_WORKER_MASTER_APPLY_MAX_ATTEMPTS", "NEUROFORGE_WORKER_MASTER_APPLY_BACKOFF_SECONDS", + "NEUROFORGE_GRAPH_BACKFILL_ENABLED", "NEUROFORGE_GRAPH_BACKFILL_INTERVAL_SECONDS", "NEUROFORGE_GRAPH_BACKFILL_BATCH_SIZE", + "NEUROFORGE_GRAPH_BACKFILL_MAX_QUEUED", "NEUROFORGE_GRAPH_BACKFILL_MIN_DEGREE", "NEUROFORGE_GRAPH_CANDIDATE_MULTIPLIER", + "NEUROFORGE_GRAPH_RETRY_AFTER_MINUTES", "NEUROFORGE_GRAPH_REQUIRE_WORKER", "NEUROFORGE_GRAPH_MAX_HOPS", + "NEUROFORGE_GRAPH_HOP_DECAY", "NEUROFORGE_GRAPH_MAX_EXPANSION", "NEUROFORGE_GRAPH_MIN_EDGE_WEIGHT", + "NEUROFORGE_OFFLOAD_CHAT", "NEUROFORGE_OFFLOAD_EMBEDDINGS", "NEUROFORGE_DISTRIBUTED_INFERENCE_WAIT_SECONDS", + } + hasWorkerEnv := false + for _, name := range workerEnv { + if _, ok := os.LookupEnv(name); ok { + hasWorkerEnv = true + break + } + } + if hasWorkerEnv { + cfg := s.Config() + if v, ok := envInt("NEUROFORGE_WORKER_LEASE_SECONDS"); ok { + cfg.Worker.LeaseSeconds = v + } + if v, ok := envInt("NEUROFORGE_WORKER_HEARTBEAT_SECONDS"); ok { + cfg.Worker.HeartbeatSeconds = v + } + if v, ok := envInt("NEUROFORGE_WORKER_STALE_AFTER_SECONDS"); ok { + cfg.Worker.StaleAfterSeconds = v + } + if v, ok := envInt("NEUROFORGE_WORKER_DEFAULT_MAX_ATTEMPTS"); ok { + cfg.Worker.DefaultMaxAttempts = v + } + if v, ok := envInt("NEUROFORGE_WORKER_RETRY_BACKOFF_SECONDS"); ok { + cfg.Worker.RetryBackoffSeconds = v + } + if v, ok := envInt("NEUROFORGE_WORKER_MAX_QUEUED_JOBS"); ok { + cfg.Worker.MaxQueuedJobs = v + } + if v, ok := envInt("NEUROFORGE_WORKER_JOB_RETENTION_HOURS"); ok { + cfg.Worker.JobRetentionHours = v + } + if v, ok := envInt("NEUROFORGE_WORKER_MAX_TERMINAL_JOBS"); ok { + cfg.Worker.MaxTerminalJobs = v + } + if v, ok := envInt("NEUROFORGE_WORKER_MASTER_APPLY_MAX_ATTEMPTS"); ok { + cfg.Worker.MasterApplyMaxAttempts = v + } + if v, ok := envInt("NEUROFORGE_WORKER_MASTER_APPLY_BACKOFF_SECONDS"); ok { + cfg.Worker.MasterApplyBackoffSeconds = v + } + if v, ok := envBool("NEUROFORGE_GRAPH_BACKFILL_ENABLED"); ok { + cfg.Worker.GraphBackfillEnabled = v + } + if v, ok := envInt("NEUROFORGE_GRAPH_BACKFILL_INTERVAL_SECONDS"); ok { + cfg.Worker.GraphBackfillIntervalS = v + } + if v, ok := envInt("NEUROFORGE_GRAPH_BACKFILL_BATCH_SIZE"); ok { + cfg.Worker.GraphBackfillBatchSize = v + } + if v, ok := envInt("NEUROFORGE_GRAPH_BACKFILL_MAX_QUEUED"); ok { + cfg.Worker.GraphBackfillMaxQueued = v + } + if v, ok := envInt("NEUROFORGE_GRAPH_BACKFILL_MIN_DEGREE"); ok { + cfg.Worker.GraphBackfillMinDegree = v + } + if v, ok := envInt("NEUROFORGE_GRAPH_CANDIDATE_MULTIPLIER"); ok { + cfg.Worker.GraphCandidateMultiplier = v + } + if v, ok := envInt("NEUROFORGE_GRAPH_RETRY_AFTER_MINUTES"); ok { + cfg.Worker.GraphRetryAfterMinutes = v + } + if v, ok := envBool("NEUROFORGE_GRAPH_REQUIRE_WORKER"); ok { + cfg.Worker.RequireWorkerForGraph = v + } + if v, ok := envBool("NEUROFORGE_OFFLOAD_CHAT"); ok { + cfg.Worker.OffloadChat = v + } + if v, ok := envBool("NEUROFORGE_OFFLOAD_EMBEDDINGS"); ok { + cfg.Worker.OffloadEmbeddings = v + } + if v, ok := envInt("NEUROFORGE_DISTRIBUTED_INFERENCE_WAIT_SECONDS"); ok { + cfg.Worker.DistributedInferenceWaitS = v + } + if v, ok := envInt("NEUROFORGE_GRAPH_MAX_HOPS"); ok { + cfg.Brain.GraphMaxHops = v + } + if v, ok := envFloat("NEUROFORGE_GRAPH_HOP_DECAY"); ok { + cfg.Brain.GraphHopDecay = v + } + if v, ok := envInt("NEUROFORGE_GRAPH_MAX_EXPANSION"); ok { + cfg.Brain.GraphMaxExpansion = v + } + if v, ok := envFloat("NEUROFORGE_GRAPH_MIN_EDGE_WEIGHT"); ok { + cfg.Brain.GraphMinEdgeWeight = v + } + if err := s.UpdateConfig(cfg); err != nil { + return fmt.Errorf("apply orchestrator/graph environment bootstrap: %w", err) + } + } + r := provider.NewRouter(s) c := cost.New(s) b := brain.New(s, r, c) @@ -336,6 +438,7 @@ func run() (retErr error) { maintenanceCtx, stopMaintenance := context.WithCancel(rootCtx) defer stopMaintenance() go b.RunV6Maintenance(maintenanceCtx) + go b.RunOrchestrator(maintenanceCtx) api := httpapi.New(s, b, r, c) if v, ok := envBool("NEUROFORGE_READINESS_OLLAMA_LIVE"); ok { api.SetReadinessOllamaLive(v) diff --git a/platform/neuroforge/cmd/worker/main.go b/platform/neuroforge/cmd/worker/main.go index a6ffd1d..d189683 100644 --- a/platform/neuroforge/cmd/worker/main.go +++ b/platform/neuroforge/cmd/worker/main.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "context" "encoding/json" "flag" "fmt" @@ -9,20 +10,30 @@ import ( "log" "net/http" "os" + "os/signal" "sort" + "strconv" "strings" + "sync" + "syscall" "time" "neuroforge/internal/core" "neuroforge/internal/vector" ) +const workerVersion = "1.6.0" + type relinkPayload struct { - TargetID string `json:"target_id"` - Target []float32 `json:"target"` - Candidates []struct { - ID string `json:"id"` - Vector []float32 `json:"vector"` + TargetID string `json:"target_id"` + TargetVersion int64 `json:"target_version"` + TargetFingerprint string `json:"target_fingerprint"` + Target []float32 `json:"target"` + Candidates []struct { + ID string `json:"id"` + Version int64 `json:"version"` + Fingerprint string `json:"fingerprint"` + Vector []float32 `json:"vector"` } `json:"candidates"` K int `json:"k"` MinSimilarity float64 `json:"min_similarity"` @@ -36,58 +47,226 @@ type relinkResult struct { Neighbors []neighbor `json:"neighbors"` } +type modelEmbedPayload struct { + Text string `json:"text"` + Model string `json:"model,omitempty"` +} + +type modelChatPayload struct { + Instructions string `json:"instructions,omitempty"` + Input string `json:"input"` + Model string `json:"model,omitempty"` + MaxOutput int `json:"max_output,omitempty"` + JSONMode bool `json:"json_mode,omitempty"` +} + +type modelUsage struct { + InputTokens int64 `json:"input_tokens"` + CachedTokens int64 `json:"cached_tokens,omitempty"` + OutputTokens int64 `json:"output_tokens"` +} + +type modelResult struct { + Text string `json:"text,omitempty"` + Vector []float32 `json:"vector,omitempty"` + Usage modelUsage `json:"usage"` + Provider string `json:"provider"` + Model string `json:"model"` + NodeID string `json:"node_id"` +} + +type workerConfig struct { + ID string + Server string + Token string + Interval time.Duration + Heartbeat time.Duration + ResourceClass string + Capabilities []string + MaxConcurrency int + Hostname string + OllamaURL string + OllamaChatModel string + OllamaEmbedModel string + OllamaNumCtx int + OllamaKeepAlive string +} + +type leaseSet struct { + mu sync.RWMutex + m map[string]string +} + +func (l *leaseSet) add(id, token string) { + l.mu.Lock() + l.m[id] = token + l.mu.Unlock() +} +func (l *leaseSet) del(id string) { + l.mu.Lock() + delete(l.m, id) + l.mu.Unlock() +} +func (l *leaseSet) snapshot() map[string]string { + l.mu.RLock() + defer l.mu.RUnlock() + out := make(map[string]string, len(l.m)) + for k, v := range l.m { + out[k] = v + } + return out +} + func main() { - server := flag.String("server", "http://localhost:8080", "NeuroForge server") - id := flag.String("id", hostname(), "worker id") + server := flag.String("server", "http://localhost:8080", "NeuroForge master/orchestrator server") + id := flag.String("id", hostname(), "subagent worker id") interval := flag.Duration("interval", 2*time.Second, "poll interval") flag.Parse() token := strings.TrimSpace(os.Getenv("NEUROFORGE_WORKER_TOKEN")) if token == "" { log.Fatal("NEUROFORGE_WORKER_TOKEN is required") } - client := &http.Client{Timeout: 180 * time.Second} - log.Printf("worker %s polling %s", *id, *server) - for { - job, err := claim(client, *server, token, *id) + resource := lowerDefault(os.Getenv("NEUROFORGE_WORKER_RESOURCE_CLASS"), "cpu") + caps := csv(os.Getenv("NEUROFORGE_WORKER_CAPABILITIES")) + if len(caps) == 0 { + if resource == "gpu" { + caps = []string{"gpu", "model.chat", "model.embed"} + } else { + caps = []string{"cpu", "vector.relink"} + } + } + maxConcurrency := envInt("NEUROFORGE_WORKER_MAX_CONCURRENCY", 1) + if maxConcurrency < 1 { + maxConcurrency = 1 + } + heartbeat := envDuration("NEUROFORGE_WORKER_HEARTBEAT_INTERVAL", 15*time.Second) + cfg := workerConfig{ + ID: *id, Server: strings.TrimRight(*server, "/"), Token: token, Interval: *interval, + Heartbeat: heartbeat, ResourceClass: resource, Capabilities: caps, + MaxConcurrency: maxConcurrency, Hostname: hostname(), + OllamaURL: strings.TrimRight(firstNonEmpty(os.Getenv("NEUROFORGE_WORKER_OLLAMA_URL"), os.Getenv("OLLAMA_BASE_URL"), os.Getenv("OLLAMA_URL")), "/"), + OllamaChatModel: firstNonEmpty(os.Getenv("NEUROFORGE_WORKER_OLLAMA_CHAT_MODEL"), os.Getenv("OLLAMA_MODEL")), + OllamaEmbedModel: firstNonEmpty(os.Getenv("NEUROFORGE_WORKER_OLLAMA_EMBEDDING_MODEL"), os.Getenv("OLLAMA_EMBEDDING_MODEL")), + OllamaNumCtx: envInt("NEUROFORGE_WORKER_OLLAMA_NUM_CTX", 8192), + OllamaKeepAlive: firstNonEmpty(os.Getenv("NEUROFORGE_WORKER_OLLAMA_KEEP_ALIVE"), "10m"), + } + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer stop() + // Control-plane requests must never hang forever on a dead master. Inference + // uses its own client and is bounded by the durable job timeout/context. + controlClient := &http.Client{Timeout: 30 * time.Second} + inferenceClient := &http.Client{Timeout: 0} + leases := &leaseSet{m: map[string]string{}} + if err := register(controlClient, cfg); err != nil { + log.Printf("initial register failed: %v", err) + } + go heartbeatLoop(ctx, controlClient, cfg, leases) + log.Printf("subagent %s polling %s resource=%s caps=%s concurrency=%d", cfg.ID, cfg.Server, cfg.ResourceClass, strings.Join(cfg.Capabilities, ","), cfg.MaxConcurrency) + var wg sync.WaitGroup + for i := 0; i < cfg.MaxConcurrency; i++ { + wg.Add(1) + go func(slot int) { + defer wg.Done() + pollLoop(ctx, controlClient, inferenceClient, cfg, leases, slot) + }(i) + } + <-ctx.Done() + log.Printf("subagent shutdown requested; waiting for active slots") + wg.Wait() +} + +func waitOrDone(ctx context.Context, d time.Duration) bool { + t := time.NewTimer(d) + defer t.Stop() + select { + case <-ctx.Done(): + return false + case <-t.C: + return true + } +} + +func pollLoop(root context.Context, controlClient, inferenceClient *http.Client, cfg workerConfig, leases *leaseSet, slot int) { + for root.Err() == nil { + job, err := claim(controlClient, cfg) if err != nil { - log.Printf("claim: %v", err) - time.Sleep(*interval) + log.Printf("slot=%d claim: %v", slot, err) + if !waitOrDone(root, cfg.Interval) { + return + } continue } if job == nil { - time.Sleep(*interval) + if !waitOrDone(root, cfg.Interval) { + return + } continue } - res, jobErr := run(job) - if err := complete(client, *server, token, *id, job.ID, res, jobErr); err != nil { - log.Printf("complete %s: %v", job.ID, err) + leases.add(job.ID, job.LeaseToken) + jobCtx := root + cancel := func() {} + if job.TimeoutSeconds > 0 { + jobCtx, cancel = context.WithTimeout(root, time.Duration(job.TimeoutSeconds)*time.Second) + } + res, jobErr := run(jobCtx, inferenceClient, cfg, job) + cancel() + if root.Err() != nil && jobErr == "" { + jobErr = root.Err().Error() + } + if err := complete(controlClient, cfg, job.ID, job.LeaseToken, res, jobErr); err != nil { + log.Printf("slot=%d complete %s: %v", slot, job.ID, err) + } else if jobErr != "" { + log.Printf("slot=%d job %s %s returned error: %s", slot, job.ID, job.Type, jobErr) } else { - log.Printf("job %s %s done", job.ID, job.Type) + log.Printf("slot=%d job %s %s complete", slot, job.ID, job.Type) + } + leases.del(job.ID) + } +} + +func workerBody(cfg workerConfig, leases map[string]string) map[string]any { + return map[string]any{ + "worker_id": cfg.ID, "resource_class": cfg.ResourceClass, "capabilities": cfg.Capabilities, + "max_concurrency": cfg.MaxConcurrency, "version": workerVersion, "hostname": cfg.Hostname, + "active_leases": leases, + } +} + +func register(c *http.Client, cfg workerConfig) error { + return postJSON(c, cfg.Server+"/api/v1/worker/register", cfg.Token, workerBody(cfg, nil), nil, http.StatusOK) +} + +func heartbeatLoop(ctx context.Context, c *http.Client, cfg workerConfig, leases *leaseSet) { + t := time.NewTicker(cfg.Heartbeat) + defer t.Stop() + for { + select { + case <-ctx.Done(): + return + case <-t.C: + var state core.WorkerState + if err := postJSON(c, cfg.Server+"/api/v1/worker/heartbeat", cfg.Token, workerBody(cfg, leases.snapshot()), &state, http.StatusOK); err != nil { + log.Printf("heartbeat: %v", err) + } } } } -func hostname() string { - h, _ := os.Hostname() - if h == "" { - h = "worker" - } - return h -} -func claim(c *http.Client, server, token, id string) (*core.Job, error) { - body, _ := json.Marshal(map[string]string{"worker_id": id}) - req, _ := http.NewRequest("POST", strings.TrimRight(server, "/")+"/api/v1/worker/claim", bytes.NewReader(body)) - req.Header.Set("Authorization", "Bearer "+token) + +func claim(c *http.Client, cfg workerConfig) (*core.Job, error) { + body, _ := json.Marshal(workerBody(cfg, nil)) + req, _ := http.NewRequest("POST", cfg.Server+"/api/v1/worker/claim", bytes.NewReader(body)) + req.Header.Set("Authorization", "Bearer "+cfg.Token) req.Header.Set("Content-Type", "application/json") resp, err := c.Do(req) if err != nil { return nil, err } defer resp.Body.Close() - if resp.StatusCode == 204 { + if resp.StatusCode == http.StatusNoContent { return nil, nil } - raw, _ := io.ReadAll(io.LimitReader(resp.Body, 8<<20)) - if resp.StatusCode != 200 { + raw, _ := io.ReadAll(io.LimitReader(resp.Body, 32<<20)) + if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, raw) } var j core.Job @@ -96,7 +275,8 @@ func claim(c *http.Client, server, token, id string) (*core.Job, error) { } return &j, nil } -func run(j *core.Job) (json.RawMessage, string) { + +func run(ctx context.Context, c *http.Client, cfg workerConfig, j *core.Job) (json.RawMessage, string) { switch j.Type { case "vector.relink": var p relinkPayload @@ -104,10 +284,10 @@ func run(j *core.Job) (json.RawMessage, string) { return nil, err.Error() } out := relinkResult{TargetID: p.TargetID} - for _, c := range p.Candidates { - sim := vector.Cosine(p.Target, c.Vector) + for _, candidate := range p.Candidates { + sim := vector.Cosine(p.Target, candidate.Vector) if sim >= p.MinSimilarity { - out.Neighbors = append(out.Neighbors, neighbor{ID: c.ID, Similarity: sim}) + out.Neighbors = append(out.Neighbors, neighbor{ID: candidate.ID, Similarity: sim}) } } sort.Slice(out.Neighbors, func(i, k int) bool { return out.Neighbors[i].Similarity > out.Neighbors[k].Similarity }) @@ -116,13 +296,134 @@ func run(j *core.Job) (json.RawMessage, string) { } b, _ := json.Marshal(out) return b, "" + case "model.embed": + if !hasCap(cfg.Capabilities, "model.embed") { + return nil, "worker lacks model.embed capability" + } + var p modelEmbedPayload + if err := json.Unmarshal(j.Payload, &p); err != nil { + return nil, err.Error() + } + out, err := ollamaEmbed(ctx, c, cfg, p) + if err != nil { + return nil, err.Error() + } + b, _ := json.Marshal(out) + return b, "" + case "model.chat": + if !hasCap(cfg.Capabilities, "model.chat") { + return nil, "worker lacks model.chat capability" + } + var p modelChatPayload + if err := json.Unmarshal(j.Payload, &p); err != nil { + return nil, err.Error() + } + out, err := ollamaChat(ctx, c, cfg, p) + if err != nil { + return nil, err.Error() + } + b, _ := json.Marshal(out) + return b, "" default: return nil, "unsupported job type: " + j.Type } } -func complete(c *http.Client, server, token, id, jobID string, result json.RawMessage, jobErr string) error { - body, _ := json.Marshal(map[string]any{"worker_id": id, "job_id": jobID, "result": result, "error": jobErr}) - req, _ := http.NewRequest("POST", strings.TrimRight(server, "/")+"/api/v1/worker/complete", bytes.NewReader(body)) + +func ollamaEmbed(ctx context.Context, c *http.Client, cfg workerConfig, p modelEmbedPayload) (modelResult, error) { + if cfg.OllamaURL == "" { + return modelResult{}, fmt.Errorf("NEUROFORGE_WORKER_OLLAMA_URL is required for model.embed") + } + model := firstNonEmpty(p.Model, cfg.OllamaEmbedModel) + if model == "" || strings.TrimSpace(p.Text) == "" { + return modelResult{}, fmt.Errorf("embedding model and text are required") + } + body := map[string]any{"model": model, "input": p.Text, "keep_alive": cfg.OllamaKeepAlive} + var resp struct { + Embeddings [][]float32 `json:"embeddings"` + PromptEvalCount int64 `json:"prompt_eval_count"` + } + if err := postOllama(ctx, c, cfg.OllamaURL+"/api/embed", body, &resp); err != nil { + return modelResult{}, err + } + if len(resp.Embeddings) == 0 || len(resp.Embeddings[0]) == 0 { + return modelResult{}, fmt.Errorf("ollama embedding response is empty") + } + return modelResult{Vector: resp.Embeddings[0], Usage: modelUsage{InputTokens: resp.PromptEvalCount}, Provider: "ollama", Model: model, NodeID: cfg.ID}, nil +} + +func ollamaChat(ctx context.Context, c *http.Client, cfg workerConfig, p modelChatPayload) (modelResult, error) { + if cfg.OllamaURL == "" { + return modelResult{}, fmt.Errorf("NEUROFORGE_WORKER_OLLAMA_URL is required for model.chat") + } + model := firstNonEmpty(p.Model, cfg.OllamaChatModel) + if model == "" || strings.TrimSpace(p.Input) == "" { + return modelResult{}, fmt.Errorf("chat model and input are required") + } + messages := []map[string]string{} + if strings.TrimSpace(p.Instructions) != "" { + messages = append(messages, map[string]string{"role": "system", "content": p.Instructions}) + } + messages = append(messages, map[string]string{"role": "user", "content": p.Input}) + body := map[string]any{"model": model, "messages": messages, "stream": false, "keep_alive": cfg.OllamaKeepAlive} + if p.JSONMode { + body["format"] = "json" + } + options := map[string]any{} + if cfg.OllamaNumCtx > 0 { + options["num_ctx"] = cfg.OllamaNumCtx + } + if p.MaxOutput > 0 { + options["num_predict"] = p.MaxOutput + } + if len(options) > 0 { + body["options"] = options + } + var resp struct { + Message struct { + Content string `json:"content"` + } `json:"message"` + PromptEvalCount int64 `json:"prompt_eval_count"` + EvalCount int64 `json:"eval_count"` + } + if err := postOllama(ctx, c, cfg.OllamaURL+"/api/chat", body, &resp); err != nil { + return modelResult{}, err + } + return modelResult{Text: strings.TrimSpace(resp.Message.Content), Usage: modelUsage{InputTokens: resp.PromptEvalCount, OutputTokens: resp.EvalCount}, Provider: "ollama", Model: model, NodeID: cfg.ID}, nil +} + +func postOllama(ctx context.Context, c *http.Client, url string, body any, out any) error { + raw, _ := json.Marshal(body) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(raw)) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/json") + resp, err := c.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + b, _ := io.ReadAll(io.LimitReader(resp.Body, 32<<20)) + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return fmt.Errorf("ollama HTTP %d: %s", resp.StatusCode, strings.TrimSpace(string(b))) + } + if err := json.Unmarshal(b, out); err != nil { + return fmt.Errorf("decode ollama response: %w", err) + } + return nil +} + +func complete(c *http.Client, cfg workerConfig, jobID, leaseToken string, result json.RawMessage, jobErr string) error { + body := map[string]any{"worker_id": cfg.ID, "job_id": jobID, "lease_token": leaseToken, "result": result, "error": jobErr} + return postJSON(c, cfg.Server+"/api/v1/worker/complete", cfg.Token, body, nil, http.StatusOK) +} + +func postJSON(c *http.Client, url, token string, body any, out any, want int) error { + raw, _ := json.Marshal(body) + req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(raw)) + if err != nil { + return err + } req.Header.Set("Authorization", "Bearer "+token) req.Header.Set("Content-Type", "application/json") resp, err := c.Do(req) @@ -130,9 +431,87 @@ func complete(c *http.Client, server, token, id, jobID string, result json.RawMe return err } defer resp.Body.Close() - raw, _ := io.ReadAll(io.LimitReader(resp.Body, 2<<20)) - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - return fmt.Errorf("HTTP %d: %s", resp.StatusCode, raw) + b, _ := io.ReadAll(io.LimitReader(resp.Body, 8<<20)) + if resp.StatusCode != want { + return fmt.Errorf("HTTP %d: %s", resp.StatusCode, strings.TrimSpace(string(b))) + } + if out != nil && len(bytes.TrimSpace(b)) > 0 { + if err := json.Unmarshal(b, out); err != nil { + return err + } } return nil } + +func hostname() string { + h, _ := os.Hostname() + if h == "" { + h = "worker" + } + return h +} + +func csv(v string) []string { + seen := map[string]bool{} + out := []string{} + for _, x := range strings.Split(v, ",") { + x = strings.ToLower(strings.TrimSpace(x)) + if x != "" && !seen[x] { + seen[x] = true + out = append(out, x) + } + } + sort.Strings(out) + return out +} + +func hasCap(caps []string, want string) bool { + want = strings.ToLower(strings.TrimSpace(want)) + for _, x := range caps { + if strings.ToLower(strings.TrimSpace(x)) == want { + return true + } + } + return false +} + +func lowerDefault(v, def string) string { + v = strings.ToLower(strings.TrimSpace(v)) + if v == "" { + return def + } + return v +} + +func firstNonEmpty(xs ...string) string { + for _, x := range xs { + if strings.TrimSpace(x) != "" { + return strings.TrimSpace(x) + } + } + return "" +} + +func envInt(name string, def int) int { + v := strings.TrimSpace(os.Getenv(name)) + if v == "" { + return def + } + n, err := strconv.Atoi(v) + if err != nil { + return def + } + return n +} + +func envDuration(name string, def time.Duration) time.Duration { + v := strings.TrimSpace(os.Getenv(name)) + if v == "" { + return def + } + d, err := time.ParseDuration(v) + if err != nil || d <= 0 { + return def + } + return d +} diff --git a/platform/neuroforge/docker-compose.yml b/platform/neuroforge/docker-compose.yml index 154b4aa..618d6aa 100644 --- a/platform/neuroforge/docker-compose.yml +++ b/platform/neuroforge/docker-compose.yml @@ -29,28 +29,46 @@ services: retries: 4 start_period: 10s - worker: + worker-cpu: build: context: . target: worker - command: - - -server - - http://neuroforge:8080 - - -id - - worker-compose-1 + command: ["-server", "http://neuroforge:8080", "-id", "cpu-compose-1"] environment: NEUROFORGE_WORKER_TOKEN: ${NEUROFORGE_WORKER_TOKEN:?Set a unique NeuroForge worker token} + NEUROFORGE_WORKER_RESOURCE_CLASS: cpu + NEUROFORGE_WORKER_CAPABILITIES: cpu,vector.relink + NEUROFORGE_WORKER_MAX_CONCURRENCY: ${NEUROFORGE_CPU_WORKER_CONCURRENCY:-2} depends_on: neuroforge: condition: service_healthy restart: unless-stopped read_only: true - tmpfs: - - /tmp:size=32m,mode=1777 - security_opt: - - no-new-privileges:true - cap_drop: - - ALL + tmpfs: ["/tmp:size=32m,mode=1777"] + security_opt: ["no-new-privileges:true"] + cap_drop: ["ALL"] + + worker-gpu: + build: + context: . + target: worker + command: ["-server", "http://neuroforge:8080", "-id", "gpu-compose-1"] + environment: + NEUROFORGE_WORKER_TOKEN: ${NEUROFORGE_WORKER_TOKEN:?Set a unique NeuroForge worker token} + NEUROFORGE_WORKER_RESOURCE_CLASS: gpu + NEUROFORGE_WORKER_CAPABILITIES: gpu,model.chat,model.embed + NEUROFORGE_WORKER_MAX_CONCURRENCY: ${NEUROFORGE_GPU_WORKER_CONCURRENCY:-1} + NEUROFORGE_WORKER_OLLAMA_URL: ${OLLAMA_BASE_URL:-http://ollama:11434} + NEUROFORGE_WORKER_OLLAMA_CHAT_MODEL: ${OLLAMA_MODEL:-gemma3} + NEUROFORGE_WORKER_OLLAMA_EMBEDDING_MODEL: ${OLLAMA_EMBEDDING_MODEL:-embeddinggemma} + depends_on: + neuroforge: + condition: service_healthy + restart: unless-stopped + read_only: true + tmpfs: ["/tmp:size=32m,mode=1777"] + security_opt: ["no-new-privileges:true"] + cap_drop: ["ALL"] volumes: neuroforge-data: diff --git a/platform/neuroforge/internal/brain/brain.go b/platform/neuroforge/internal/brain/brain.go index 835bdde..290caef 100644 --- a/platform/neuroforge/internal/brain/brain.go +++ b/platform/neuroforge/internal/brain/brain.go @@ -3,10 +3,14 @@ package brain import ( "bytes" "context" + "crypto/sha256" + "encoding/binary" + "encoding/hex" "encoding/json" "errors" "fmt" "io" + "math" "net/http" "regexp" "sort" @@ -87,6 +91,12 @@ func (e *Engine) embed(ctx context.Context, text string) (provider.EmbedResult, if route == "" { route = "auto" } + if (route == "auto" || route == "ollama") && nodeID == "" { + if res, used, offloadErr := e.distributedEmbed(ctx, model, text); used && offloadErr == nil { + costUSD, recErr := e.cost.Record(res.Provider, res.Model, "embedding", res.Usage) + return res, costUSD, recErr + } + } if route == "auto" { res, err := e.router.EmbedOn(ctx, "ollama", model, nodeID, text) if err == nil { @@ -160,6 +170,12 @@ func (e *Engine) chatModelLimitOnMode(ctx context.Context, providerName, model, if route == "" { route = "auto" } + if (route == "auto" || route == "ollama") && nodeID == "" { + if res, used, offloadErr := e.distributedChat(ctx, model, instructions, input, maxOutput, jsonMode); used && offloadErr == nil { + costUSD, recErr := e.cost.Record(res.Provider, res.Model, "chat", res.Usage) + return res, costUSD, recErr + } + } if route == "auto" { var res provider.ChatResult var err error @@ -986,15 +1002,19 @@ func minFloat(a, b float64) float64 { } type relinkPayload struct { - TargetID string `json:"target_id"` - Target []float32 `json:"target"` - Candidates []relinkCandidate `json:"candidates"` - K int `json:"k"` - MinSimilarity float64 `json:"min_similarity"` + TargetID string `json:"target_id"` + TargetVersion int64 `json:"target_version"` + TargetFingerprint string `json:"target_fingerprint"` + Target []float32 `json:"target"` + Candidates []relinkCandidate `json:"candidates"` + K int `json:"k"` + MinSimilarity float64 `json:"min_similarity"` } type relinkCandidate struct { - ID string `json:"id"` - Vector []float32 `json:"vector"` + ID string `json:"id"` + Version int64 `json:"version"` + Fingerprint string `json:"fingerprint"` + Vector []float32 `json:"vector"` } type RelinkResult struct { TargetID string `json:"target_id"` @@ -1004,16 +1024,49 @@ type RelinkResult struct { } `json:"neighbors"` } +func vectorFingerprint(v []float32) string { + h := sha256.New() + var buf [4]byte + for _, x := range v { + binary.LittleEndian.PutUint32(buf[:], math.Float32bits(x)) + _, _ = h.Write(buf[:]) + } + sum := h.Sum(nil) + return hex.EncodeToString(sum[:12]) +} + +var errObsoleteRelink = errors.New("obsolete vector.relink result") + func (e *Engine) enqueueRelink(m *core.Memory) (*core.Job, error) { cfg := e.store.Config() - snap := e.store.MemoriesSnapshot() - p := relinkPayload{TargetID: m.ID, Target: m.Vector, K: cfg.Brain.RecallK, MinSimilarity: cfg.Brain.MinSimilarity} - for _, x := range snap { - if x.ID != m.ID && len(x.Vector) == len(m.Vector) { - p.Candidates = append(p.Candidates, relinkCandidate{ID: x.ID, Vector: x.Vector}) - } + if m == nil || m.ID == "" || len(m.Vector) == 0 { + return nil, errors.New("relink target requires id and vector") } - return e.store.EnqueueJob("vector.relink", p) + mult := cfg.Worker.GraphCandidateMultiplier + if mult < 2 { + mult = 4 + } + candidateK := cfg.Brain.RecallK * mult + if candidateK < cfg.Brain.RecallK+4 { + candidateK = cfg.Brain.RecallK + 4 + } + if candidateK > 256 { + candidateK = 256 + } + hits := e.store.SearchVector(m.Vector, candidateK+1, cfg.Brain.MinSimilarity, 0) + p := relinkPayload{TargetID: m.ID, TargetVersion: m.Version, TargetFingerprint: vectorFingerprint(m.Vector), Target: append([]float32(nil), m.Vector...), K: cfg.Brain.RecallK, MinSimilarity: cfg.Brain.MinSimilarity} + for _, h := range hits { + if h.Memory.ID == m.ID || len(h.Memory.Vector) != len(m.Vector) { + continue + } + p.Candidates = append(p.Candidates, relinkCandidate{ID: h.Memory.ID, Version: h.Memory.Version, Fingerprint: vectorFingerprint(h.Memory.Vector), Vector: append([]float32(nil), h.Memory.Vector...)}) + } + return e.store.EnqueueJobSpec(store.JobSpec{ + Type: "vector.relink", Payload: p, Priority: 20, ResourceClass: "cpu", + RequiredCapabilities: []string{"cpu", "vector.relink"}, + RequiresMasterApply: true, + IdempotencyKey: "vector.relink:" + m.ID + ":v" + strconv.FormatInt(m.Version, 10), + }) } func (e *Engine) localRelink(m *core.Memory) error { cfg := e.store.Config() @@ -1026,20 +1079,53 @@ func (e *Engine) localRelink(m *core.Memory) error { return nil } func (e *Engine) ApplyJobResult(j *core.Job) error { - if j.Type != "vector.relink" || j.Status != "done" { + if j == nil || j.Type != "vector.relink" || (j.Status != "done" && j.Status != "apply_wait") { return nil } + var p relinkPayload + if err := json.Unmarshal(j.Payload, &p); err != nil { + return fmt.Errorf("decode relink payload during master apply: %w", err) + } + current, ok := e.store.GetMemory(p.TargetID) + if !ok || current.Version != p.TargetVersion || vectorFingerprint(current.Vector) != p.TargetFingerprint { + // The worker computed against an older delete/recreate or memory version. + // Treat the result as obsolete rather than poisoning the current graph; the + // backfill planner will schedule the current version again. + return errObsoleteRelink + } var r RelinkResult if err := json.Unmarshal(j.Result, &r); err != nil { return err } + if r.TargetID != p.TargetID { + return fmt.Errorf("relink result target %q does not match payload target %q", r.TargetID, p.TargetID) + } + candidateMeta := make(map[string]relinkCandidate, len(p.Candidates)) + for _, c := range p.Candidates { + candidateMeta[c.ID] = c + } sort.Slice(r.Neighbors, func(i, j int) bool { return r.Neighbors[i].Similarity > r.Neighbors[j].Similarity }) for _, n := range r.Neighbors { - if err := e.reinforcePair(r.TargetID, n.ID, n.Similarity, n.Similarity); err != nil { + meta, expected := candidateMeta[n.ID] + if !expected { + // A worker may only return neighbors from the bounded master-selected + // candidate set. Rejecting extras closes a trust-boundary gap. + continue + } + neighbor, ok := e.store.GetMemory(n.ID) + if !ok || neighbor.Version != meta.Version || vectorFingerprint(neighbor.Vector) != meta.Fingerprint { + continue + } + cfg := e.store.Config() + delta := cfg.Brain.LearningRate * n.Similarity + if delta <= 0 { + delta = n.Similarity + } + if err := e.store.UpsertRelationEvidence(r.TargetID, n.ID, "semantic_similarity", n.Similarity, delta, cfg.Brain.MaxSynapseWeight); err != nil { return err } } - return nil + return e.store.MarkGraphLinked(r.TargetID) } // SearchByProvenanceSources embeds text once and searches only the requested diff --git a/platform/neuroforge/internal/brain/distributed.go b/platform/neuroforge/internal/brain/distributed.go new file mode 100644 index 0000000..265989f --- /dev/null +++ b/platform/neuroforge/internal/brain/distributed.go @@ -0,0 +1,107 @@ +package brain + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "time" + + "neuroforge/internal/provider" + "neuroforge/internal/store" +) + +type distributedModelResult struct { + Text string `json:"text,omitempty"` + Vector []float32 `json:"vector,omitempty"` + Usage struct { + InputTokens int64 `json:"input_tokens"` + CachedTokens int64 `json:"cached_tokens,omitempty"` + OutputTokens int64 `json:"output_tokens"` + } `json:"usage"` + Provider string `json:"provider"` + Model string `json:"model"` + NodeID string `json:"node_id"` +} + +func (e *Engine) waitDistributedJob(ctx context.Context, jID string) (*distributedModelResult, error) { + t := time.NewTicker(50 * time.Millisecond) + defer t.Stop() + for { + select { + case <-ctx.Done(): + _ = e.store.CancelJob(jID, "caller context ended: "+ctx.Err().Error()) + return nil, ctx.Err() + case <-t.C: + j, ok := e.store.Job(jID) + if !ok { + return nil, errors.New("distributed job disappeared") + } + switch j.Status { + case "done": + var out distributedModelResult + if err := json.Unmarshal(j.Result, &out); err != nil { + return nil, fmt.Errorf("decode distributed model result: %w", err) + } + return &out, nil + case "failed", "canceled": + return nil, fmt.Errorf("distributed job %s: %s", j.Status, j.Error) + } + } + } +} + +func (e *Engine) distributedEmbed(ctx context.Context, model, text string) (provider.EmbedResult, bool, error) { + cfg := e.store.Config() + if !cfg.Worker.OffloadEmbeddings || !e.store.HasLiveWorker("gpu", "gpu", "model.embed") { + return provider.EmbedResult{}, false, nil + } + wait := cfg.Worker.DistributedInferenceWaitS + if wait <= 0 { + wait = 180 + } + j, err := e.store.EnqueueJobSpec(store.JobSpec{ + Type: "model.embed", Payload: map[string]any{"text": text, "model": model}, + Priority: 100, ResourceClass: "gpu", RequiredCapabilities: []string{"gpu", "model.embed"}, + MaxAttempts: 2, TimeoutSeconds: wait, + }) + if err != nil { + return provider.EmbedResult{}, true, err + } + jobCtx, cancel := context.WithTimeout(ctx, time.Duration(wait)*time.Second) + defer cancel() + out, err := e.waitDistributedJob(jobCtx, j.ID) + if err != nil { + return provider.EmbedResult{}, true, err + } + if len(out.Vector) == 0 { + return provider.EmbedResult{}, true, errors.New("distributed embedding returned empty vector") + } + return provider.EmbedResult{Vector: out.Vector, Provider: out.Provider, Model: out.Model, NodeID: out.NodeID, Usage: provider.Usage{InputTokens: out.Usage.InputTokens, CachedTokens: out.Usage.CachedTokens, OutputTokens: out.Usage.OutputTokens}}, true, nil +} + +func (e *Engine) distributedChat(ctx context.Context, model, instructions, input string, maxOutput int, jsonMode bool) (provider.ChatResult, bool, error) { + cfg := e.store.Config() + if !cfg.Worker.OffloadChat || !e.store.HasLiveWorker("gpu", "gpu", "model.chat") { + return provider.ChatResult{}, false, nil + } + wait := cfg.Worker.DistributedInferenceWaitS + if wait <= 0 { + wait = 180 + } + j, err := e.store.EnqueueJobSpec(store.JobSpec{ + Type: "model.chat", Payload: map[string]any{"instructions": instructions, "input": input, "model": model, "max_output": maxOutput, "json_mode": jsonMode}, + Priority: 100, ResourceClass: "gpu", RequiredCapabilities: []string{"gpu", "model.chat"}, + MaxAttempts: 2, TimeoutSeconds: wait, + }) + if err != nil { + return provider.ChatResult{}, true, err + } + jobCtx, cancel := context.WithTimeout(ctx, time.Duration(wait)*time.Second) + defer cancel() + out, err := e.waitDistributedJob(jobCtx, j.ID) + if err != nil { + return provider.ChatResult{}, true, err + } + return provider.ChatResult{Text: out.Text, Provider: out.Provider, Model: out.Model, NodeID: out.NodeID, Usage: provider.Usage{InputTokens: out.Usage.InputTokens, CachedTokens: out.Usage.CachedTokens, OutputTokens: out.Usage.OutputTokens}}, true, nil +} diff --git a/platform/neuroforge/internal/brain/orchestrator.go b/platform/neuroforge/internal/brain/orchestrator.go new file mode 100644 index 0000000..992fe75 --- /dev/null +++ b/platform/neuroforge/internal/brain/orchestrator.go @@ -0,0 +1,117 @@ +package brain + +import ( + "context" + "errors" + "time" + + "neuroforge/internal/core" +) + +// PlanGraphBackfill keeps the associative graph converging toward a durable, +// multi-neighbor state. It deliberately uses a bounded queue so a large KB +// import cannot create an unbounded O(N^2) burst of relink payloads. +func (e *Engine) PlanGraphBackfill() (int, error) { + cfg := e.store.Config() + if !e.store.IsOrchestratorLeader() { + return 0, nil + } + wc := cfg.Worker + if !wc.GraphBackfillEnabled { + return 0, nil + } + if wc.RequireWorkerForGraph && !e.store.HasLiveWorker("cpu", "cpu", "vector.relink") { + return 0, nil + } + maxQueued := wc.GraphBackfillMaxQueued + if maxQueued <= 0 { + maxQueued = 512 + } + pending := e.store.PendingJobCount("vector.relink") + if pending >= maxQueued { + return 0, nil + } + batch := wc.GraphBackfillBatchSize + if batch <= 0 { + batch = 64 + } + if room := maxQueued - pending; batch > room { + batch = room + } + retryAfter := time.Duration(wc.GraphRetryAfterMinutes) * time.Minute + if retryAfter <= 0 { + retryAfter = 6 * time.Hour + } + items := e.store.GraphBackfillCandidates(batch, wc.GraphBackfillMinDegree, retryAfter) + planned := 0 + for i := range items { + if _, err := e.enqueueRelink(&items[i]); err != nil { + return planned, err + } + planned++ + } + return planned, nil +} + +// ApplyAndFinalizeJob is the durable second phase for jobs whose worker result +// mutates authoritative master state. The worker result is already in the WAL; +// apply can therefore be retried after a master crash without recomputing it. +func (e *Engine) ApplyAndFinalizeJob(j *core.Job) error { + if j == nil || j.Status != "apply_wait" { + return nil + } + err := e.ApplyJobResult(j) + if errors.Is(err, errObsoleteRelink) { + // Obsolete computation is not a failed mutation. Close the old job and + // leave the current memory unlinked so normal backfill schedules it again. + err = nil + } + _, finishErr := e.store.FinishMasterApply(j.ID, err) + if finishErr != nil { + return finishErr + } + return err +} + +func (e *Engine) ApplyPendingJobResults(limit int) (applied, failed int) { + jobs := e.store.PendingMasterApplyJobs(limit) + for i := range jobs { + if err := e.ApplyAndFinalizeJob(&jobs[i]); err != nil { + failed++ + } else { + applied++ + } + } + return applied, failed +} + +func (e *Engine) RunOrchestrator(ctx context.Context) { + var lastPrune time.Time + // Run quickly on boot so a previously imported knowledge corpus starts + // linking as soon as a capable worker has registered. + timer := time.NewTimer(2 * time.Second) + defer timer.Stop() + for { + select { + case <-ctx.Done(): + return + case <-timer.C: + cfg := e.store.Config() + if !e.store.IsOrchestratorLeader() { + timer.Reset(5 * time.Second) + continue + } + _, _ = e.ApplyPendingJobResults(64) + if lastPrune.IsZero() || time.Since(lastPrune) >= 10*time.Minute { + _, _ = e.store.PruneTerminalJobs(time.Duration(cfg.Worker.JobRetentionHours)*time.Hour, cfg.Worker.MaxTerminalJobs) + lastPrune = time.Now().UTC() + } + interval := time.Duration(cfg.Worker.GraphBackfillIntervalS) * time.Second + if interval < 2*time.Second { + interval = 10 * time.Second + } + _, _ = e.PlanGraphBackfill() + timer.Reset(interval) + } + } +} diff --git a/platform/neuroforge/internal/brain/orchestrator_test.go b/platform/neuroforge/internal/brain/orchestrator_test.go new file mode 100644 index 0000000..d05cae1 --- /dev/null +++ b/platform/neuroforge/internal/brain/orchestrator_test.go @@ -0,0 +1,200 @@ +package brain + +import ( + "encoding/json" + "errors" + "testing" + "time" + + "neuroforge/internal/core" + "neuroforge/internal/cost" + "neuroforge/internal/provider" + "neuroforge/internal/store" +) + +func TestGraphBackfillPlansBoundedJobsAndDurablyAppliesResult(t *testing.T) { + s, err := store.New(t.TempDir()) + if err != nil { + t.Fatal(err) + } + defer s.Close() + cfg := s.Config() + cfg.Worker.GraphBackfillEnabled = true + cfg.Worker.RequireWorkerForGraph = true + cfg.Worker.GraphBackfillBatchSize = 4 + cfg.Worker.GraphBackfillMaxQueued = 8 + cfg.Worker.GraphBackfillMinDegree = 2 + cfg.Worker.GraphCandidateMultiplier = 4 + cfg.Brain.RecallK = 3 + cfg.Brain.MinSimilarity = .1 + if err := s.UpdateConfig(cfg); err != nil { + t.Fatal(err) + } + for i := 0; i < 20; i++ { + m := core.Memory{ID: store.NewID("kb"), Kind: "knowledge.chunk", MemoryType: core.MemorySemantic, Text: "knowledge", Vector: []float32{1, float32(i+1) / 100}} + if err := s.AddMemory(&m); err != nil { + t.Fatal(err) + } + } + if _, err := s.RegisterWorker(store.WorkerHeartbeat{ID: "cpu-1", ResourceClass: "cpu", Capabilities: []string{"cpu", "vector.relink"}, MaxConcurrency: 2}); err != nil { + t.Fatal(err) + } + e := New(s, provider.NewRouter(s), cost.New(s)) + planned, err := e.PlanGraphBackfill() + if err != nil { + t.Fatal(err) + } + if planned != 4 || s.PendingJobCount("vector.relink") != 4 { + t.Fatalf("planned=%d pending=%d", planned, s.PendingJobCount("vector.relink")) + } + jobs := s.JobsSnapshot(10, "", "vector.relink") + if len(jobs) != 4 { + t.Fatalf("jobs=%d", len(jobs)) + } + full, ok := s.Job(jobs[0].ID) + if !ok || !full.RequiresMasterApply { + t.Fatalf("job=%+v", full) + } + var payload relinkPayload + if err := json.Unmarshal(full.Payload, &payload); err != nil { + t.Fatal(err) + } + if len(payload.Candidates) == 0 || len(payload.Candidates) > 256 { + t.Fatalf("candidate payload is not bounded: %d", len(payload.Candidates)) + } + + worker := store.WorkerHeartbeat{ID: "cpu-1", ResourceClass: "cpu", Capabilities: []string{"cpu", "vector.relink"}, MaxConcurrency: 2} + claimed, err := s.ClaimJobForWorker(worker, time.Minute) + if err != nil || claimed == nil { + t.Fatalf("claim=%+v err=%v", claimed, err) + } + var p relinkPayload + if err := json.Unmarshal(claimed.Payload, &p); err != nil { + t.Fatal(err) + } + if len(p.Candidates) < 2 { + t.Fatalf("need at least two candidates: %+v", p) + } + result, _ := json.Marshal(map[string]any{"target_id": p.TargetID, "neighbors": []map[string]any{{"id": p.Candidates[0].ID, "similarity": .95}, {"id": p.Candidates[1].ID, "similarity": .90}}}) + completed, err := s.CompleteJobLease(claimed.ID, worker.ID, claimed.LeaseToken, result, "") + if err != nil { + t.Fatal(err) + } + if completed.Status != "apply_wait" { + t.Fatalf("worker result must be durable before master apply: %+v", completed) + } + if err := e.ApplyAndFinalizeJob(completed); err != nil { + t.Fatal(err) + } + final, _ := s.Job(completed.ID) + if final.Status != "done" || final.ApplyAttempts != 1 { + t.Fatalf("final=%+v", final) + } + if degree := s.GraphDegree(p.TargetID); degree < 2 { + t.Fatalf("target degree=%d; expected real multi-neighbor graph", degree) + } + m, ok := s.GetMemory(p.TargetID) + if !ok || m.GraphLinkedAt.IsZero() || m.GraphVersion != m.Version { + t.Fatalf("graph linkage checkpoint missing: %+v", m) + } +} + +func TestMasterApplyReplayDoesNotInflateSemanticEdge(t *testing.T) { + s, err := store.New(t.TempDir()) + if err != nil { + t.Fatal(err) + } + defer s.Close() + for _, id := range []string{"A", "B"} { + m := core.Memory{ID: id, Kind: "knowledge.chunk", MemoryType: core.MemorySemantic, Text: id, Vector: []float32{1, .1}} + if err := s.AddMemory(&m); err != nil { + t.Fatal(err) + } + } + e := New(s, provider.NewRouter(s), cost.New(s)) + a, _ := s.GetMemory("A") + b, _ := s.GetMemory("B") + payload, _ := json.Marshal(relinkPayload{ + TargetID: "A", TargetVersion: a.Version, TargetFingerprint: vectorFingerprint(a.Vector), Target: append([]float32(nil), a.Vector...), + Candidates: []relinkCandidate{{ID: "B", Version: b.Version, Fingerprint: vectorFingerprint(b.Vector), Vector: append([]float32(nil), b.Vector...)}}, + K: 1, MinSimilarity: .1, + }) + result, _ := json.Marshal(map[string]any{"target_id": "A", "neighbors": []map[string]any{{"id": "B", "similarity": .9}}}) + j := &core.Job{Type: "vector.relink", Status: "apply_wait", Payload: payload, Result: result} + if err := e.ApplyJobResult(j); err != nil { + t.Fatal(err) + } + if err := e.ApplyJobResult(j); err != nil { + t.Fatal(err) + } + g := s.KnowledgeGraph("A", 1, 10) + if len(g.Edges) != 1 { + t.Fatalf("edges=%+v", g.Edges) + } + if g.Edges[0].Activations != 1 { + t.Fatalf("retry replay inflated activations: %+v", g.Edges[0]) + } +} + +func TestRelinkResultFromDeletedRecreatedMemoryIsObsolete(t *testing.T) { + s, err := store.New(t.TempDir()) + if err != nil { + t.Fatal(err) + } + defer s.Close() + for _, m := range []core.Memory{ + {ID: "A", Kind: "knowledge.chunk", MemoryType: core.MemorySemantic, Text: "old A", Vector: []float32{1, .1}}, + {ID: "B", Kind: "knowledge.chunk", MemoryType: core.MemorySemantic, Text: "B", Vector: []float32{1, .2}}, + } { + mm := m + if err := s.AddMemory(&mm); err != nil { + t.Fatal(err) + } + } + e := New(s, provider.NewRouter(s), cost.New(s)) + job, err := e.enqueueRelink(mustMemory(t, s, "A")) + if err != nil { + t.Fatal(err) + } + var p relinkPayload + if err := json.Unmarshal(job.Payload, &p); err != nil { + t.Fatal(err) + } + if len(p.Candidates) == 0 { + t.Fatal("expected relink candidate") + } + + // Simulate a knowledge sync replacing the stable ID while an old worker is + // still computing. Delete removes old edges; the re-created memory may reuse + // version 1, so the vector fingerprint is the required fencing signal. + if err := s.DeleteMemory("A"); err != nil { + t.Fatal(err) + } + recreated := core.Memory{ID: "A", Kind: "knowledge.chunk", MemoryType: core.MemorySemantic, Text: "new A", Vector: []float32{.1, 1}} + if err := s.AddMemory(&recreated); err != nil { + t.Fatal(err) + } + + result, _ := json.Marshal(map[string]any{"target_id": "A", "neighbors": []map[string]any{{"id": p.Candidates[0].ID, "similarity": .99}}}) + job.Status = "apply_wait" + job.Result = result + if err := e.ApplyJobResult(job); !errors.Is(err, errObsoleteRelink) { + t.Fatalf("expected obsolete relink result, got %v", err) + } + if got := s.GraphDegree("A"); got != 0 { + t.Fatalf("obsolete worker result mutated recreated memory graph: degree=%d", got) + } + m := mustMemory(t, s, "A") + if !m.GraphLinkedAt.IsZero() || m.GraphVersion != 0 { + t.Fatalf("recreated memory was incorrectly marked linked: %+v", m) + } +} + +func mustMemory(t *testing.T, s *store.Store, id string) *core.Memory { + t.Helper() + m, ok := s.GetMemory(id) + if !ok { + t.Fatalf("memory %s missing", id) + } + return m +} diff --git a/platform/neuroforge/internal/core/types.go b/platform/neuroforge/internal/core/types.go index 5cd39f3..78b379b 100644 --- a/platform/neuroforge/internal/core/types.go +++ b/platform/neuroforge/internal/core/types.go @@ -173,6 +173,10 @@ type Config struct { DecayPerDay float64 `json:"decay_per_day"` MaxSynapseWeight float64 `json:"max_synapse_weight"` GraphBonus float64 `json:"graph_bonus"` + GraphMaxHops int `json:"graph_max_hops"` + GraphHopDecay float64 `json:"graph_hop_decay"` + GraphMaxExpansion int `json:"graph_max_expansion"` + GraphMinEdgeWeight float64 `json:"graph_min_edge_weight"` MaxContextMemories int `json:"max_context_memories"` AutoLearn bool `json:"auto_learn"` ExternalRelinkWorker bool `json:"external_relink_worker"` @@ -388,7 +392,27 @@ type Config struct { } `json:"api"` Worker struct { - LeaseSeconds int `json:"lease_seconds"` + LeaseSeconds int `json:"lease_seconds"` + HeartbeatSeconds int `json:"heartbeat_seconds"` + StaleAfterSeconds int `json:"stale_after_seconds"` + DefaultMaxAttempts int `json:"default_max_attempts"` + RetryBackoffSeconds int `json:"retry_backoff_seconds"` + MaxQueuedJobs int `json:"max_queued_jobs"` + MasterApplyMaxAttempts int `json:"master_apply_max_attempts"` + MasterApplyBackoffSeconds int `json:"master_apply_backoff_seconds"` + JobRetentionHours int `json:"job_retention_hours"` + MaxTerminalJobs int `json:"max_terminal_jobs"` + GraphBackfillEnabled bool `json:"graph_backfill_enabled"` + GraphBackfillIntervalS int `json:"graph_backfill_interval_seconds"` + GraphBackfillBatchSize int `json:"graph_backfill_batch_size"` + GraphBackfillMaxQueued int `json:"graph_backfill_max_queued"` + GraphBackfillMinDegree int `json:"graph_backfill_min_degree"` + GraphCandidateMultiplier int `json:"graph_candidate_multiplier"` + GraphRetryAfterMinutes int `json:"graph_retry_after_minutes"` + RequireWorkerForGraph bool `json:"require_worker_for_graph"` + OffloadChat bool `json:"offload_chat"` + OffloadEmbeddings bool `json:"offload_embeddings"` + DistributedInferenceWaitS int `json:"distributed_inference_wait_seconds"` } `json:"worker"` } @@ -469,6 +493,9 @@ type Memory struct { ConsolidationCount int `json:"consolidation_count,omitempty"` EvidenceSourceIDs []string `json:"evidence_source_ids,omitempty"` EvidenceCount int `json:"evidence_count,omitempty"` + GraphLinkedAt time.Time `json:"graph_linked_at,omitempty"` + GraphDegree int `json:"graph_degree,omitempty"` + GraphVersion int64 `json:"graph_version,omitempty"` Provenance MemoryProvenance `json:"provenance,omitempty"` } @@ -477,6 +504,7 @@ type Synapse struct { B string `json:"b"` Weight float64 `json:"weight"` Similarity float64 `json:"similarity"` + Relations []string `json:"relations,omitempty"` Activations int64 `json:"activations"` LastUpdated time.Time `json:"last_updated"` } @@ -494,16 +522,50 @@ type UsageEvent struct { } type Job struct { - ID string `json:"id"` - Type string `json:"type"` - Payload json.RawMessage `json:"payload"` - Result json.RawMessage `json:"result,omitempty"` - Status string `json:"status"` - ClaimedBy string `json:"claimed_by,omitempty"` - LeaseUntil time.Time `json:"lease_until,omitempty"` - Error string `json:"error,omitempty"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ID string `json:"id"` + Type string `json:"type"` + Payload json.RawMessage `json:"payload"` + Result json.RawMessage `json:"result,omitempty"` + Status string `json:"status"` + Priority int `json:"priority,omitempty"` + ResourceClass string `json:"resource_class,omitempty"` + RequiredCapabilities []string `json:"required_capabilities,omitempty"` + IdempotencyKey string `json:"idempotency_key,omitempty"` + ParentJobID string `json:"parent_job_id,omitempty"` + DependsOn []string `json:"depends_on,omitempty"` + Attempts int `json:"attempts,omitempty"` + MaxAttempts int `json:"max_attempts,omitempty"` + BackoffSeconds int `json:"backoff_seconds,omitempty"` + TimeoutSeconds int `json:"timeout_seconds,omitempty"` + RequiresMasterApply bool `json:"requires_master_apply,omitempty"` + ApplyAttempts int `json:"apply_attempts,omitempty"` + MaxApplyAttempts int `json:"max_apply_attempts,omitempty"` + ApplyBackoffSeconds int `json:"apply_backoff_seconds,omitempty"` + ApplyNextAttemptAt time.Time `json:"apply_next_attempt_at,omitempty"` + ApplyError string `json:"apply_error,omitempty"` + ClaimedBy string `json:"claimed_by,omitempty"` + LeaseToken string `json:"lease_token,omitempty"` + LeaseUntil time.Time `json:"lease_until,omitempty"` + NextAttemptAt time.Time `json:"next_attempt_at,omitempty"` + StartedAt time.Time `json:"started_at,omitempty"` + FinishedAt time.Time `json:"finished_at,omitempty"` + Error string `json:"error,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type WorkerState struct { + ID string `json:"id"` + ResourceClass string `json:"resource_class"` + Capabilities []string `json:"capabilities"` + Labels map[string]string `json:"labels,omitempty"` + MaxConcurrency int `json:"max_concurrency"` + Inflight int `json:"inflight"` + Version string `json:"version,omitempty"` + Hostname string `json:"hostname,omitempty"` + LastHeartbeat time.Time `json:"last_heartbeat"` + RegisteredAt time.Time `json:"registered_at"` + Status string `json:"status"` } type MaintenanceStatus struct { @@ -688,6 +750,10 @@ func DefaultConfig() Config { c.Brain.DecayPerDay = 0.01 c.Brain.MaxSynapseWeight = 4.0 c.Brain.GraphBonus = 0.15 + c.Brain.GraphMaxHops = 3 + c.Brain.GraphHopDecay = 0.60 + c.Brain.GraphMaxExpansion = 64 + c.Brain.GraphMinEdgeWeight = 0.05 c.Brain.MaxContextMemories = 8 c.Brain.AutoLearn = true c.Brain.ExternalRelinkWorker = true @@ -850,5 +916,25 @@ func DefaultConfig() Config { c.Cluster.LogSegmentBytes = 64 << 20 c.API.RequireKey = true c.Worker.LeaseSeconds = 120 + c.Worker.HeartbeatSeconds = 15 + c.Worker.StaleAfterSeconds = 60 + c.Worker.DefaultMaxAttempts = 3 + c.Worker.RetryBackoffSeconds = 15 + c.Worker.MaxQueuedJobs = 5000 + c.Worker.MasterApplyMaxAttempts = 5 + c.Worker.MasterApplyBackoffSeconds = 5 + c.Worker.JobRetentionHours = 168 + c.Worker.MaxTerminalJobs = 20000 + c.Worker.GraphBackfillEnabled = true + c.Worker.GraphBackfillIntervalS = 10 + c.Worker.GraphBackfillBatchSize = 64 + c.Worker.GraphBackfillMaxQueued = 512 + c.Worker.GraphBackfillMinDegree = 3 + c.Worker.GraphCandidateMultiplier = 6 + c.Worker.GraphRetryAfterMinutes = 360 + c.Worker.RequireWorkerForGraph = true + c.Worker.OffloadChat = false + c.Worker.OffloadEmbeddings = false + c.Worker.DistributedInferenceWaitS = 180 return c } diff --git a/platform/neuroforge/internal/httpapi/httpapi.go b/platform/neuroforge/internal/httpapi/httpapi.go index 5bfa388..109e8ea 100644 --- a/platform/neuroforge/internal/httpapi/httpapi.go +++ b/platform/neuroforge/internal/httpapi/httpapi.go @@ -92,6 +92,8 @@ func (s *Server) routes() { s.mux.Handle("POST /api/v1/integrations/outcomes/search", s.integrationAuth(http.HandlerFunc(s.integrationValidatedOutcomeSearch))) s.mux.Handle("GET /api/v1/integrations/graph/research", s.controlReadAuth(http.HandlerFunc(s.integrationResearchGraph))) s.mux.Handle("GET /api/v1/integrations/graph/brain", s.controlReadAuth(http.HandlerFunc(s.integrationBrainGraph))) + s.mux.Handle("GET /api/v1/integrations/graph/status", s.controlReadAuth(http.HandlerFunc(s.integrationGraphStatus))) + s.mux.Handle("GET /api/v1/integrations/orchestrator/status", s.controlReadAuth(http.HandlerFunc(s.integrationOrchestratorStatus))) s.mux.Handle("POST /internal/v1/cluster/request-vote", s.clusterAuth(http.HandlerFunc(s.clusterRequestVote))) s.mux.Handle("POST /internal/v1/cluster/heartbeat", s.clusterAuth(http.HandlerFunc(s.clusterHeartbeat))) @@ -102,6 +104,8 @@ func (s *Server) routes() { s.mux.Handle("GET /internal/v1/cluster/decision/{id}", s.clusterAuth(http.HandlerFunc(s.clusterDecision))) s.mux.Handle("GET /internal/v1/cluster/status", s.clusterAuth(http.HandlerFunc(s.clusterStatus))) + s.mux.Handle("POST /api/v1/worker/register", s.workerAuth(http.HandlerFunc(s.workerRegister))) + s.mux.Handle("POST /api/v1/worker/heartbeat", s.workerAuth(http.HandlerFunc(s.workerHeartbeat))) s.mux.Handle("POST /api/v1/worker/claim", s.workerAuth(http.HandlerFunc(s.workerClaim))) s.mux.Handle("POST /api/v1/worker/complete", s.workerAuth(http.HandlerFunc(s.workerComplete))) @@ -138,6 +142,12 @@ func (s *Server) routes() { s.mux.Handle("GET /admin/api/knowledge/memories", s.adminAuth(http.HandlerFunc(s.adminKnowledgeMemories))) s.mux.Handle("GET /admin/api/knowledge/memory/{id}", s.adminAuth(http.HandlerFunc(s.adminKnowledgeMemory))) s.mux.Handle("GET /admin/api/knowledge/graph", s.adminAuth(http.HandlerFunc(s.adminKnowledgeGraph))) + s.mux.Handle("GET /admin/api/graph/status", s.adminAuth(http.HandlerFunc(s.adminGraphStatus))) + s.mux.Handle("POST /admin/api/graph/backfill", s.adminAuth(http.HandlerFunc(s.adminGraphBackfill))) + s.mux.Handle("GET /admin/api/orchestrator/status", s.adminAuth(http.HandlerFunc(s.adminOrchestratorStatus))) + s.mux.Handle("GET /admin/api/orchestrator/jobs", s.adminAuth(http.HandlerFunc(s.adminOrchestratorJobs))) + s.mux.Handle("POST /admin/api/orchestrator/jobs/{id}/retry", s.adminAuth(http.HandlerFunc(s.adminOrchestratorRetry))) + s.mux.Handle("POST /admin/api/orchestrator/jobs/{id}/cancel", s.adminAuth(http.HandlerFunc(s.adminOrchestratorCancel))) s.mux.Handle("GET /admin/api/knowledge/events", s.adminAuth(http.HandlerFunc(s.adminKnowledgeEvents))) s.mux.Handle("POST /admin/api/knowledge/search", s.adminAuth(http.HandlerFunc(s.adminKnowledgeSearch))) s.mux.Handle("GET /admin/api/learning-policy", s.adminAuth(http.HandlerFunc(s.adminGetLearningPolicy))) @@ -418,10 +428,84 @@ func (s *Server) stats(w http.ResponseWriter, r *http.Request) { s.json(w, 200, map[string]any{"stats": s.store.Stats(), "cost": s.cost.Totals()}) } -func (s *Server) workerClaim(w http.ResponseWriter, r *http.Request) { - var q struct { - WorkerID string `json:"worker_id"` +func (s *Server) integrationGraphStatus(w http.ResponseWriter, r *http.Request) { + cfg := s.store.Config() + s.json(w, http.StatusOK, map[string]any{ + "stats": s.store.GraphStats(), + "config": map[string]any{ + "max_hops": cfg.Brain.GraphMaxHops, "hop_decay": cfg.Brain.GraphHopDecay, + "max_expansion": cfg.Brain.GraphMaxExpansion, "min_edge_weight": cfg.Brain.GraphMinEdgeWeight, + "backfill_enabled": cfg.Worker.GraphBackfillEnabled, "backfill_min_degree": cfg.Worker.GraphBackfillMinDegree, + }, + }) +} + +func (s *Server) integrationOrchestratorStatus(w http.ResponseWriter, r *http.Request) { + cfg := s.store.Config() + s.json(w, http.StatusOK, map[string]any{ + "leader": s.store.IsOrchestratorLeader(), + "status": s.store.OrchestratorStatus(), + "config": map[string]any{ + "lease_seconds": cfg.Worker.LeaseSeconds, "heartbeat_seconds": cfg.Worker.HeartbeatSeconds, + "stale_after_seconds": cfg.Worker.StaleAfterSeconds, "max_queued_jobs": cfg.Worker.MaxQueuedJobs, + "offload_chat": cfg.Worker.OffloadChat, "offload_embeddings": cfg.Worker.OffloadEmbeddings, + }, + }) +} + +type workerRequest struct { + WorkerID string `json:"worker_id"` + ResourceClass string `json:"resource_class,omitempty"` + Capabilities []string `json:"capabilities,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + MaxConcurrency int `json:"max_concurrency,omitempty"` + Version string `json:"version,omitempty"` + Hostname string `json:"hostname,omitempty"` + ActiveLeases map[string]string `json:"active_leases,omitempty"` +} + +func workerHeartbeatFromRequest(q workerRequest) store.WorkerHeartbeat { + return store.WorkerHeartbeat{ + ID: q.WorkerID, ResourceClass: q.ResourceClass, Capabilities: q.Capabilities, + Labels: q.Labels, MaxConcurrency: q.MaxConcurrency, Version: q.Version, + Hostname: q.Hostname, ActiveLeases: q.ActiveLeases, } +} + +func (s *Server) workerRegister(w http.ResponseWriter, r *http.Request) { + var q workerRequest + if err := decode(r, &q); err != nil { + s.err(w, http.StatusBadRequest, err) + return + } + state, err := s.store.RegisterWorker(workerHeartbeatFromRequest(q)) + if err != nil { + s.err(w, http.StatusBadRequest, err) + return + } + s.json(w, http.StatusOK, state) +} + +func (s *Server) workerHeartbeat(w http.ResponseWriter, r *http.Request) { + var q workerRequest + if err := decode(r, &q); err != nil { + s.err(w, http.StatusBadRequest, err) + return + } + lease := s.store.Config().Worker.LeaseSeconds + if lease < 10 { + lease = 120 + } + state, err := s.store.HeartbeatWorker(workerHeartbeatFromRequest(q), time.Duration(lease)*time.Second) + if err != nil { + s.err(w, http.StatusBadRequest, err) + return + } + s.json(w, http.StatusOK, state) +} + +func (s *Server) workerClaim(w http.ResponseWriter, r *http.Request) { + var q workerRequest if err := decode(r, &q); err != nil { s.err(w, 400, err) return @@ -434,7 +518,7 @@ func (s *Server) workerClaim(w http.ResponseWriter, r *http.Request) { if lease < 10 { lease = 120 } - j, err := s.store.ClaimJob(q.WorkerID, time.Duration(lease)*time.Second) + j, err := s.store.ClaimJobForWorker(workerHeartbeatFromRequest(q), time.Duration(lease)*time.Second) if err != nil { s.err(w, 500, err) return @@ -445,27 +529,33 @@ func (s *Server) workerClaim(w http.ResponseWriter, r *http.Request) { } s.json(w, 200, j) } + func (s *Server) workerComplete(w http.ResponseWriter, r *http.Request) { var q struct { - WorkerID string `json:"worker_id"` - JobID string `json:"job_id"` - Result json.RawMessage `json:"result"` - Error string `json:"error"` + WorkerID string `json:"worker_id"` + JobID string `json:"job_id"` + LeaseToken string `json:"lease_token,omitempty"` + Result json.RawMessage `json:"result"` + Error string `json:"error"` } if err := decode(r, &q); err != nil { s.err(w, 400, err) return } - j, err := s.store.CompleteJob(q.JobID, q.WorkerID, q.Result, q.Error) + j, err := s.store.CompleteJobLease(q.JobID, q.WorkerID, q.LeaseToken, q.Result, q.Error) if err != nil { - s.err(w, 400, err) + s.err(w, 409, err) return } - if err := s.brain.ApplyJobResult(j); err != nil { - s.err(w, 500, err) - return + if j.Status == "apply_wait" && s.store.IsOrchestratorLeader() { + // Best-effort eager second phase. Any failure remains durable as apply_wait + // and is retried by the orchestrator; the worker must not recompute it. + _ = s.brain.ApplyAndFinalizeJob(j) + if cur, ok := s.store.Job(j.ID); ok { + j = cur + } } - s.json(w, 200, map[string]bool{"ok": true}) + s.json(w, 200, map[string]any{"ok": true, "status": j.Status, "attempts": j.Attempts, "apply_attempts": j.ApplyAttempts, "apply_error": j.ApplyError, "next_attempt_at": j.NextAttemptAt, "apply_next_attempt_at": j.ApplyNextAttemptAt}) } func (s *Server) adminStatus(w http.ResponseWriter, r *http.Request) { diff --git a/platform/neuroforge/internal/httpapi/knowledge.go b/platform/neuroforge/internal/httpapi/knowledge.go index b7073ac..beac310 100644 --- a/platform/neuroforge/internal/httpapi/knowledge.go +++ b/platform/neuroforge/internal/httpapi/knowledge.go @@ -115,3 +115,69 @@ func (s *Server) adminPutLearningPolicy(w http.ResponseWriter, r *http.Request) }) s.json(w, http.StatusOK, q) } + +func (s *Server) adminGraphStatus(w http.ResponseWriter, r *http.Request) { + s.json(w, http.StatusOK, map[string]any{ + "graph": s.store.GraphStats(), + "orchestrator": s.store.OrchestratorStatus(), + "config": map[string]any{ + "max_hops": s.store.Config().Brain.GraphMaxHops, + "hop_decay": s.store.Config().Brain.GraphHopDecay, + "max_expansion": s.store.Config().Brain.GraphMaxExpansion, + "min_edge_weight": s.store.Config().Brain.GraphMinEdgeWeight, + "backfill_enabled": s.store.Config().Worker.GraphBackfillEnabled, + "backfill_min_degree": s.store.Config().Worker.GraphBackfillMinDegree, + "backfill_max_queued": s.store.Config().Worker.GraphBackfillMaxQueued, + }, + }) +} + +func (s *Server) adminGraphBackfill(w http.ResponseWriter, r *http.Request) { + planned, err := s.brain.PlanGraphBackfill() + if err != nil { + s.err(w, http.StatusInternalServerError, err) + return + } + s.json(w, http.StatusAccepted, map[string]any{"ok": true, "planned": planned, "graph": s.store.GraphStats()}) +} + +func (s *Server) adminOrchestratorStatus(w http.ResponseWriter, r *http.Request) { + s.json(w, http.StatusOK, s.store.OrchestratorStatus()) +} + +func (s *Server) adminOrchestratorJobs(w http.ResponseWriter, r *http.Request) { + limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) + s.json(w, http.StatusOK, s.store.JobsSnapshot(limit, r.URL.Query().Get("status"), r.URL.Query().Get("type"))) +} + +func (s *Server) adminOrchestratorRetry(w http.ResponseWriter, r *http.Request) { + job, err := s.store.RetryJob(r.PathValue("id")) + if err != nil { + s.err(w, http.StatusConflict, err) + return + } + s.json(w, http.StatusOK, job) +} + +func (s *Server) adminOrchestratorCancel(w http.ResponseWriter, r *http.Request) { + var q struct { + Reason string `json:"reason"` + } + // Empty bodies are valid for an operator cancellation; malformed non-empty + // JSON is not. + if r.ContentLength != 0 { + if err := decode(r, &q); err != nil { + s.err(w, http.StatusBadRequest, err) + return + } + } + if strings.TrimSpace(q.Reason) == "" { + q.Reason = "canceled by administrator" + } + if err := s.store.CancelJob(r.PathValue("id"), q.Reason); err != nil { + s.err(w, http.StatusNotFound, err) + return + } + job, _ := s.store.Job(r.PathValue("id")) + s.json(w, http.StatusOK, job) +} diff --git a/platform/neuroforge/internal/httpapi/metrics.go b/platform/neuroforge/internal/httpapi/metrics.go index e13ba8f..d49bf82 100644 --- a/platform/neuroforge/internal/httpapi/metrics.go +++ b/platform/neuroforge/internal/httpapi/metrics.go @@ -242,6 +242,8 @@ func (s *Server) metricsEndpoint(w http.ResponseWriter, r *http.Request) { } st := s.store.ObservabilitySnapshot() + orch := s.store.OrchestratorStatus() + graph := s.store.GraphStats() costs := s.cost.Totals() cfg := s.store.Config() rt := currentRuntimeSnapshot() @@ -269,11 +271,43 @@ func (s *Server) metricsEndpoint(w http.ResponseWriter, r *http.Request) { promHeader(&b, "neuroforge_knowledge_events", "Current number of retained explainability/knowledge events.", "gauge") promSample(&b, "neuroforge_knowledge_events", st.KnowledgeEvents) - promHeader(&b, "neuroforge_jobs", "Current worker jobs by bounded status class.", "gauge") - promSample(&b, "neuroforge_jobs", st.JobsQueued, "status", "queued") - promSample(&b, "neuroforge_jobs", st.JobsClaimed, "status", "claimed") - promSample(&b, "neuroforge_jobs", st.JobsDone, "status", "done") - promSample(&b, "neuroforge_jobs", st.JobsFailed, "status", "failed") + promHeader(&b, "neuroforge_jobs", "Current durable orchestrator jobs by status.", "gauge") + statuses := []string{"queued", "claimed", "retry_wait", "blocked", "apply_wait", "done", "failed", "canceled"} + for _, status := range statuses { + promSample(&b, "neuroforge_jobs", orch.JobsByStatus[status], "status", status) + } + promHeader(&b, "neuroforge_jobs_by_resource", "Current durable orchestrator jobs by resource class.", "gauge") + for resource, n := range orch.JobsByResource { + promSample(&b, "neuroforge_jobs_by_resource", n, "resource", resource) + } + promHeader(&b, "neuroforge_workers", "Registered subagents by resource class and liveness state.", "gauge") + workerCounts := map[string]int{} + for _, worker := range orch.Workers { + workerCounts[worker.ResourceClass+"\x00"+worker.Status]++ + } + for key, n := range workerCounts { + parts := strings.SplitN(key, "\x00", 2) + promSample(&b, "neuroforge_workers", n, "resource", parts[0], "status", parts[1]) + } + promHeader(&b, "neuroforge_worker_inflight", "Current claimed jobs per registered subagent.", "gauge") + promHeader(&b, "neuroforge_worker_capacity", "Configured concurrency capacity per registered subagent.", "gauge") + for _, worker := range orch.Workers { + promSample(&b, "neuroforge_worker_inflight", worker.Inflight, "worker", worker.ID, "resource", worker.ResourceClass) + promSample(&b, "neuroforge_worker_capacity", worker.MaxConcurrency, "worker", worker.ID, "resource", worker.ResourceClass) + } + + promHeader(&b, "neuroforge_graph_memories", "Knowledge memories by graph linkage class.", "gauge") + promSample(&b, "neuroforge_graph_memories", graph.LinkedMemories, "state", "linked") + promSample(&b, "neuroforge_graph_memories", graph.IsolatedMemories, "state", "isolated") + promSample(&b, "neuroforge_graph_memories", graph.MultiLinked, "state", "multi_linked") + promHeader(&b, "neuroforge_graph_max_degree", "Maximum associative graph degree.", "gauge") + promSample(&b, "neuroforge_graph_max_degree", graph.MaxDegree) + promHeader(&b, "neuroforge_graph_average_degree", "Average associative graph degree.", "gauge") + promSample(&b, "neuroforge_graph_average_degree", strconv.FormatFloat(graph.AverageDegree, 'f', 6, 64)) + promHeader(&b, "neuroforge_graph_connected_components", "Number of connected components in the associative graph.", "gauge") + promSample(&b, "neuroforge_graph_connected_components", graph.ConnectedComponents) + promHeader(&b, "neuroforge_graph_largest_component", "Memories in the largest connected component.", "gauge") + promSample(&b, "neuroforge_graph_largest_component", graph.LargestComponent) promHeader(&b, "neuroforge_hnsw_nodes", "Current number of vectors in hot HNSW indexes.", "gauge") promSample(&b, "neuroforge_hnsw_nodes", st.HNSWNodes) diff --git a/platform/neuroforge/internal/store/batch.go b/platform/neuroforge/internal/store/batch.go index 5b2ad86..d5492b2 100644 --- a/platform/neuroforge/internal/store/batch.go +++ b/platform/neuroforge/internal/store/batch.go @@ -129,6 +129,7 @@ func (s *Store) DeleteMemoriesBatch(ids []string) error { } for key, syn := range s.state.Synapses { if syn.A == id || syn.B == id { + s.unindexSynapseLocked(syn) delete(s.state.Synapses, key) } } diff --git a/platform/neuroforge/internal/store/graph_index.go b/platform/neuroforge/internal/store/graph_index.go new file mode 100644 index 0000000..56dffa5 --- /dev/null +++ b/platform/neuroforge/internal/store/graph_index.go @@ -0,0 +1,405 @@ +package store + +import ( + "sort" + "strings" + "time" + + "neuroforge/internal/core" + "neuroforge/internal/vector" +) + +// GraphStats exposes structural graph health, not just raw edge counts. A real +// associative graph is expected to have nodes with degree >1 and multi-hop +// reachability; these metrics make that observable. +type GraphStats struct { + Memories int `json:"memories"` + Synapses int `json:"synapses"` + LinkedMemories int `json:"linked_memories"` + IsolatedMemories int `json:"isolated_memories"` + MultiLinked int `json:"multi_linked_memories"` + MaxDegree int `json:"max_degree"` + AverageDegree float64 `json:"average_degree"` + ConnectedComponents int `json:"connected_components"` + LargestComponent int `json:"largest_component"` +} + +func (s *Store) rebuildSynapseAdjLocked() { + s.synapseAdj = make(map[string]map[string]*core.Synapse) + for _, syn := range s.state.Synapses { + if syn == nil || syn.A == "" || syn.B == "" || syn.A == syn.B { + continue + } + s.indexSynapseLocked(syn) + } +} + +func (s *Store) indexSynapseLocked(syn *core.Synapse) { + if syn == nil || syn.A == "" || syn.B == "" || syn.A == syn.B { + return + } + if s.synapseAdj == nil { + s.synapseAdj = make(map[string]map[string]*core.Synapse) + } + if s.synapseAdj[syn.A] == nil { + s.synapseAdj[syn.A] = make(map[string]*core.Synapse) + } + if s.synapseAdj[syn.B] == nil { + s.synapseAdj[syn.B] = make(map[string]*core.Synapse) + } + s.synapseAdj[syn.A][syn.B] = syn + s.synapseAdj[syn.B][syn.A] = syn +} + +func (s *Store) unindexSynapseLocked(syn *core.Synapse) { + if syn == nil || s.synapseAdj == nil { + return + } + if m := s.synapseAdj[syn.A]; m != nil { + delete(m, syn.B) + if len(m) == 0 { + delete(s.synapseAdj, syn.A) + } + } + if m := s.synapseAdj[syn.B]; m != nil { + delete(m, syn.A) + if len(m) == 0 { + delete(s.synapseAdj, syn.B) + } + } +} + +func appendUniqueRelation(xs []string, value string) []string { + for _, x := range xs { + if x == value { + return xs + } + } + return append(xs, value) +} + +// ReinforceRelation adds or reinforces an associative edge while preserving the +// reason(s) the edge exists. Multiple relations can coexist on the same pair. +func (s *Store) ReinforceRelation(a, b, relation string, similarity, delta, decayPerDay, maxWeight float64) error { + if a == "" || b == "" || a == b { + return nil + } + s.mu.Lock() + defer s.mu.Unlock() + if s.state.Memories[a] == nil || s.state.Memories[b] == nil { + return nil + } + key := edgeKey(a, b) + now := time.Now().UTC() + syn, ok := s.state.Synapses[key] + if !ok { + syn = &core.Synapse{A: a, B: b, Similarity: similarity, LastUpdated: now} + s.state.Synapses[key] = syn + s.indexSynapseLocked(syn) + } + if relation == "" { + relation = "association" + } + syn.Relations = appendUniqueRelation(syn.Relations, relation) + days := now.Sub(syn.LastUpdated).Hours() / 24 + if days > 0 && decayPerDay > 0 { + syn.Weight *= pow(1-decayPerDay, days) + } + syn.Weight = vector.Clamp(syn.Weight+delta, -maxWeight, maxWeight) + if similarity > syn.Similarity { + syn.Similarity = similarity + } + syn.Activations++ + syn.LastUpdated = now + return s.commitLocked("synapse.upsert", *syn) +} + +func (s *Store) GraphDegree(id string) int { + s.mu.RLock() + defer s.mu.RUnlock() + return len(s.synapseAdj[id]) +} + +func (s *Store) GraphStats() GraphStats { + s.mu.RLock() + defer s.mu.RUnlock() + out := GraphStats{Memories: len(s.state.Memories), Synapses: len(s.state.Synapses)} + if out.Memories == 0 { + return out + } + for id := range s.state.Memories { + degree := len(s.synapseAdj[id]) + if degree == 0 { + out.IsolatedMemories++ + } else { + out.LinkedMemories++ + } + if degree > 1 { + out.MultiLinked++ + } + if degree > out.MaxDegree { + out.MaxDegree = degree + } + } + out.AverageDegree = float64(out.Synapses*2) / float64(out.Memories) + visited := make(map[string]bool, out.Memories) + for id := range s.state.Memories { + if visited[id] { + continue + } + out.ConnectedComponents++ + queue := []string{id} + visited[id] = true + size := 0 + for len(queue) > 0 { + cur := queue[0] + queue = queue[1:] + size++ + for next := range s.synapseAdj[cur] { + if !visited[next] && s.state.Memories[next] != nil { + visited[next] = true + queue = append(queue, next) + } + } + } + if size > out.LargestComponent { + out.LargestComponent = size + } + } + return out +} + +// graphExpandLocked applies bounded multi-hop associative propagation to a +// vector result set. The graph remains pairwise at the edge level (as graphs +// naturally are) but retrieval can now follow chains across several memories. +func (s *Store) graphExpandLocked(q []float32, hits []SearchHit, k int, min, graphBonus float64) []SearchHit { + cfg := s.state.Config.Brain + if graphBonus <= 0 || len(hits) == 0 || len(s.state.Synapses) == 0 { + return hits + } + maxHops := cfg.GraphMaxHops + if maxHops <= 0 { + maxHops = 1 + } + if maxHops > 6 { + maxHops = 6 + } + decay := cfg.GraphHopDecay + if decay <= 0 || decay > 1 { + decay = 0.60 + } + maxExpansion := cfg.GraphMaxExpansion + if maxExpansion <= 0 { + maxExpansion = 64 + } + minEdge := cfg.GraphMinEdgeWeight + + type frontierItem struct { + id string + boost float64 + hop int + } + bestBoost := map[string]float64{} + queue := make([]frontierItem, 0, len(hits)*2) + for _, h := range hits { + bestBoost[h.Memory.ID] = 0 + queue = append(queue, frontierItem{id: h.Memory.ID, boost: graphBonus, hop: 0}) + } + expanded := 0 + for len(queue) > 0 && expanded < maxExpansion { + cur := queue[0] + queue = queue[1:] + if cur.hop >= maxHops { + continue + } + neighbors := s.synapseAdj[cur.id] + if len(neighbors) == 0 { + continue + } + type edgeItem struct { + id string + syn *core.Synapse + } + edges := make([]edgeItem, 0, len(neighbors)) + for id, syn := range neighbors { + if syn != nil && syn.Weight >= minEdge { + edges = append(edges, edgeItem{id: id, syn: syn}) + } + } + sort.Slice(edges, func(i, j int) bool { return edges[i].syn.Weight > edges[j].syn.Weight }) + for _, edge := range edges { + if expanded >= maxExpansion { + break + } + meta := s.state.Memories[edge.id] + if meta == nil || !memorySearchable(meta) { + continue + } + boost := cur.boost * edge.syn.Weight + if cur.hop > 0 { + boost *= decay + } + if old, ok := bestBoost[edge.id]; ok && old >= boost { + continue + } + bestBoost[edge.id] = boost + queue = append(queue, frontierItem{id: edge.id, boost: boost, hop: cur.hop + 1}) + expanded++ + } + } + + byID := make(map[string]int, len(hits)) + for i := range hits { + byID[hits[i].Memory.ID] = i + } + for id, boost := range bestBoost { + if boost <= 0 { + continue + } + if i, ok := byID[id]; ok { + hits[i].GraphBoost += boost + hits[i].Score += boost + continue + } + m, ok := s.fullMemoryForReadLocked(id) + if !ok || len(m.Vector) != len(q) { + continue + } + sim := vector.Cosine(q, m.Vector) + if sim < min { + continue + } + baseScore := sim * 0.5 + hits = append(hits, SearchHit{Memory: cloneMemory(m), Similarity: sim, BaseScore: baseScore, GraphBoost: boost, Score: baseScore + boost, TypeWeight: 1, SalienceFactor: 1, ConfidenceFactor: 1, CandidateSource: "synapse-multihop"}) + } + sort.Slice(hits, func(i, j int) bool { return hits[i].Score > hits[j].Score }) + if len(hits) > k { + hits = hits[:k] + } + return hits +} + +// GraphBackfillCandidates returns a bounded set of memories whose associative +// links are missing, stale for the current memory version, or remain below the +// configured minimum degree after the retry cooldown. +func (s *Store) GraphBackfillCandidates(limit, minDegree int, retryAfter time.Duration) []core.Memory { + if limit <= 0 { + return nil + } + if minDegree < 1 { + minDegree = 1 + } + now := time.Now().UTC() + s.mu.RLock() + defer s.mu.RUnlock() + out := make([]core.Memory, 0, limit) + // Do not repeatedly return a target that already has a durable relink job + // in flight. Without this guard, the lexicographically first memories can + // occupy every planning batch until their first job finishes and starve the + // rest of a large imported corpus. + pendingTargets := make(map[string]bool) + for _, j := range s.state.Jobs { + if j == nil || j.Type != "vector.relink" { + continue + } + switch j.Status { + case "queued", "claimed", "retry_wait", "blocked", "apply_wait": + default: + continue + } + const prefix = "vector.relink:" + if !strings.HasPrefix(j.IdempotencyKey, prefix) { + continue + } + rest := strings.TrimPrefix(j.IdempotencyKey, prefix) + if cut := strings.LastIndex(rest, ":v"); cut > 0 { + pendingTargets[rest[:cut]] = true + } + } + ids := make([]string, 0, len(s.state.Memories)) + for id := range s.state.Memories { + ids = append(ids, id) + } + sort.Strings(ids) + for _, id := range ids { + meta := s.state.Memories[id] + if pendingTargets[id] || meta == nil || !memorySearchable(meta) { + continue + } + m, ok := s.fullMemoryForReadLocked(id) + if !ok || len(m.Vector) == 0 { + continue + } + degree := len(s.synapseAdj[id]) + needs := m.GraphVersion < m.Version || m.GraphLinkedAt.IsZero() + if !needs && degree < minDegree && (retryAfter <= 0 || now.Sub(m.GraphLinkedAt) >= retryAfter) { + needs = true + } + if !needs { + continue + } + m.GraphDegree = degree + out = append(out, cloneMemory(m)) + if len(out) >= limit { + break + } + } + return out +} + +func (s *Store) MarkGraphLinked(id string) error { + s.mu.Lock() + defer s.mu.Unlock() + m, ok := s.materializeMemoryLocked(id) + if !ok || m == nil { + return nil + } + m.GraphLinkedAt = time.Now().UTC() + m.GraphDegree = len(s.synapseAdj[id]) + m.GraphVersion = m.Version + return s.commitLocked("memory.upsert", []core.Memory{cloneMemory(*m)}) +} + +// UpsertRelationEvidence records deterministic relation evidence without +// additive reinforcement. It is used for retryable master-apply phases where +// the same worker result may be replayed after a crash; replaying it must not +// inflate graph weights or activation counters. +func (s *Store) UpsertRelationEvidence(a, b, relation string, similarity, weight, maxWeight float64) error { + if a == "" || b == "" || a == b { + return nil + } + s.mu.Lock() + defer s.mu.Unlock() + if s.state.Memories[a] == nil || s.state.Memories[b] == nil { + return nil + } + if relation == "" { + relation = "association" + } + key := edgeKey(a, b) + now := time.Now().UTC() + syn, ok := s.state.Synapses[key] + if !ok { + syn = &core.Synapse{A: a, B: b, LastUpdated: now} + s.state.Synapses[key] = syn + s.indexSynapseLocked(syn) + } + beforeRelation := len(syn.Relations) + syn.Relations = appendUniqueRelation(syn.Relations, relation) + if similarity > syn.Similarity { + syn.Similarity = similarity + } + if maxWeight <= 0 { + maxWeight = 1 + } + weight = vector.Clamp(weight, -maxWeight, maxWeight) + if weight > syn.Weight { + syn.Weight = weight + } + // Count creation of relation evidence, not crash/retry replays. + if len(syn.Relations) > beforeRelation || syn.Activations == 0 { + syn.Activations++ + } + syn.LastUpdated = now + return s.commitLocked("synapse.upsert", *syn) +} diff --git a/platform/neuroforge/internal/store/graph_multihop_test.go b/platform/neuroforge/internal/store/graph_multihop_test.go new file mode 100644 index 0000000..a8f0d72 --- /dev/null +++ b/platform/neuroforge/internal/store/graph_multihop_test.go @@ -0,0 +1,103 @@ +package store + +import ( + "testing" + "time" + + "neuroforge/internal/core" +) + +func TestGraphIsManyToManyAndTraversesMultipleHops(t *testing.T) { + s, err := New(t.TempDir()) + if err != nil { + t.Fatal(err) + } + defer s.Close() + cfg := s.Config() + cfg.Brain.GraphMaxHops = 3 + cfg.Brain.GraphHopDecay = .8 + cfg.Brain.GraphMaxExpansion = 32 + cfg.Brain.GraphMinEdgeWeight = .01 + if err := s.UpdateConfig(cfg); err != nil { + t.Fatal(err) + } + + items := []core.Memory{ + {ID: "A", Kind: "knowledge", MemoryType: core.MemorySemantic, Text: "A", Vector: []float32{1, .01}}, + {ID: "B", Kind: "knowledge", MemoryType: core.MemorySemantic, Text: "B", Vector: []float32{.99, .1}}, + {ID: "C", Kind: "knowledge", MemoryType: core.MemorySemantic, Text: "C", Vector: []float32{.9, .3}}, + {ID: "D", Kind: "knowledge", MemoryType: core.MemorySemantic, Text: "D", Vector: []float32{.8, .4}}, + } + if err := s.AddMemoriesBatch(items); err != nil { + t.Fatal(err) + } + for _, e := range [][2]string{{"A", "B"}, {"B", "C"}, {"C", "D"}, {"B", "D"}} { + if err := s.ReinforceRelation(e[0], e[1], "semantic_similarity", .9, .8, 0, 1); err != nil { + t.Fatal(err) + } + } + // A second relation on the same pair proves relation metadata is not limited + // to a single 1:1 reason. + if err := s.ReinforceRelation("B", "C", "coactivation", .9, .05, 0, 1); err != nil { + t.Fatal(err) + } + + st := s.GraphStats() + if st.Synapses != 4 || st.MultiLinked < 2 || st.MaxDegree < 3 || st.ConnectedComponents != 1 || st.LargestComponent != 4 { + t.Fatalf("graph stats do not prove an n:m connected graph: %+v", st) + } + + s.mu.RLock() + seed, ok := s.fullMemoryForReadLocked("A") + if !ok { + s.mu.RUnlock() + t.Fatal("missing A") + } + hits := s.graphExpandLocked([]float32{1, .01}, []SearchHit{{Memory: cloneMemory(seed), Similarity: 1, Score: 1, BaseScore: 1}}, 4, .1, .25) + s.mu.RUnlock() + foundD := false + for _, h := range hits { + if h.Memory.ID == "D" && h.CandidateSource == "synapse-multihop" { + foundD = true + } + } + if !foundD { + t.Fatalf("multi-hop traversal did not surface D: %+v", hits) + } + + g := s.KnowledgeGraph("B", 2, 32) + relationFound := false + for _, e := range g.Edges { + if (e.A == "B" && e.B == "C") || (e.A == "C" && e.B == "B") { + if len(e.Relations) >= 2 { + relationFound = true + } + } + } + if !relationFound { + t.Fatalf("typed/multi-relation edge not exposed: %+v", g.Edges) + } +} + +func TestGraphBackfillSkipsTargetsAlreadyQueued(t *testing.T) { + s, err := New(t.TempDir()) + if err != nil { + t.Fatal(err) + } + defer s.Close() + items := []core.Memory{ + {ID: "A", Kind: "knowledge", MemoryType: core.MemorySemantic, Text: "A", Vector: []float32{1, 0}}, + {ID: "B", Kind: "knowledge", MemoryType: core.MemorySemantic, Text: "B", Vector: []float32{.9, .1}}, + {ID: "C", Kind: "knowledge", MemoryType: core.MemorySemantic, Text: "C", Vector: []float32{.8, .2}}, + } + if err := s.AddMemoriesBatch(items); err != nil { + t.Fatal(err) + } + if _, err := s.EnqueueJobSpec(JobSpec{Type: "vector.relink", Payload: map[string]any{"target_id": "A"}, IdempotencyKey: "vector.relink:A:v1", ResourceClass: "cpu"}); err != nil { + t.Fatal(err) + } + got := s.GraphBackfillCandidates(2, 3, time.Hour) + if len(got) != 2 || got[0].ID == "A" || got[1].ID == "A" { + t.Fatalf("queued target was not skipped: %+v", got) + } +} diff --git a/platform/neuroforge/internal/store/knowledge.go b/platform/neuroforge/internal/store/knowledge.go index c6e4f34..89c481b 100644 --- a/platform/neuroforge/internal/store/knowledge.go +++ b/platform/neuroforge/internal/store/knowledge.go @@ -61,6 +61,7 @@ type KnowledgeList struct { type KnowledgeEdge struct { A string `json:"a"` B string `json:"b"` + Relations []string `json:"relations,omitempty"` Weight float64 `json:"weight"` Similarity float64 `json:"similarity"` Activations int64 `json:"activations"` @@ -368,7 +369,7 @@ func (s *Store) KnowledgeGraph(center string, depth, maxNodes int) KnowledgeGrap } for _, syn := range s.state.Synapses { if selected[syn.A] && selected[syn.B] { - out.Edges = append(out.Edges, KnowledgeEdge{A: syn.A, B: syn.B, Weight: syn.Weight, Similarity: syn.Similarity, Activations: syn.Activations, LastUpdated: syn.LastUpdated}) + out.Edges = append(out.Edges, KnowledgeEdge{A: syn.A, B: syn.B, Relations: append([]string(nil), syn.Relations...), Weight: syn.Weight, Similarity: syn.Similarity, Activations: syn.Activations, LastUpdated: syn.LastUpdated}) } } sort.Slice(out.Edges, func(i, j int) bool { return out.Edges[i].Weight > out.Edges[j].Weight }) @@ -397,7 +398,7 @@ func (s *Store) KnowledgeMemoryDetail(id string) (KnowledgeMemoryDetail, bool) { } else { continue } - out.Edges = append(out.Edges, KnowledgeEdge{A: syn.A, B: syn.B, Weight: syn.Weight, Similarity: syn.Similarity, Activations: syn.Activations, LastUpdated: syn.LastUpdated}) + out.Edges = append(out.Edges, KnowledgeEdge{A: syn.A, B: syn.B, Relations: append([]string(nil), syn.Relations...), Weight: syn.Weight, Similarity: syn.Similarity, Activations: syn.Activations, LastUpdated: syn.LastUpdated}) if !seenNeighbor[other] { if om, ok := s.fullMemoryForReadLocked(other); ok { out.Neighbors = append(out.Neighbors, memoryPreview(om)) diff --git a/platform/neuroforge/internal/store/orchestrator.go b/platform/neuroforge/internal/store/orchestrator.go new file mode 100644 index 0000000..f3b6368 --- /dev/null +++ b/platform/neuroforge/internal/store/orchestrator.go @@ -0,0 +1,841 @@ +package store + +import ( + "encoding/json" + "errors" + "fmt" + "sort" + "strings" + "time" + + "neuroforge/internal/core" +) + +// JobSpec describes a durable unit of work. Jobs are persisted through the +// normal NeuroForge WAL/checkpoint path so a master restart does not lose +// queued, leased, retrying, or dependency-blocked work. +type JobSpec struct { + Type string + Payload any + Priority int + ResourceClass string + RequiredCapabilities []string + IdempotencyKey string + ParentJobID string + DependsOn []string + MaxAttempts int + BackoffSeconds int + TimeoutSeconds int + RequiresMasterApply bool + MaxApplyAttempts int + ApplyBackoffSeconds int +} + +type WorkerHeartbeat struct { + ID string + ResourceClass string + Capabilities []string + Labels map[string]string + MaxConcurrency int + Version string + Hostname string + ActiveLeases map[string]string // job_id -> lease_token +} + +type OrchestratorStatus struct { + Workers []core.WorkerState `json:"workers"` + JobsByStatus map[string]int `json:"jobs_by_status"` + JobsByType map[string]int `json:"jobs_by_type"` + JobsByResource map[string]int `json:"jobs_by_resource"` + OldestQueued time.Time `json:"oldest_queued,omitempty"` + Queued int `json:"queued"` + Claimed int `json:"claimed"` + Retrying int `json:"retrying"` + Applying int `json:"applying"` + Blocked int `json:"blocked"` + Failed int `json:"failed"` + Done int `json:"done"` +} + +func normalizeCapabilities(in []string) []string { + seen := map[string]bool{} + out := make([]string, 0, len(in)) + for _, x := range in { + x = strings.ToLower(strings.TrimSpace(x)) + if x == "" || seen[x] { + continue + } + seen[x] = true + out = append(out, x) + } + sort.Strings(out) + return out +} + +func capabilitiesContain(have []string, need []string) bool { + if len(need) == 0 { + return true + } + m := make(map[string]bool, len(have)) + for _, x := range have { + m[strings.ToLower(strings.TrimSpace(x))] = true + } + for _, x := range need { + if !m[strings.ToLower(strings.TrimSpace(x))] { + return false + } + } + return true +} + +func (s *Store) EnqueueJob(kind string, payload any) (*core.Job, error) { + return s.EnqueueJobSpec(JobSpec{Type: kind, Payload: payload}) +} + +func (s *Store) EnqueueJobSpec(spec JobSpec) (*core.Job, error) { + spec.Type = strings.TrimSpace(spec.Type) + if spec.Type == "" { + return nil, errors.New("job type is required") + } + b, err := json.Marshal(spec.Payload) + if err != nil { + return nil, err + } + s.mu.Lock() + defer s.mu.Unlock() + cfg := s.state.Config.Worker + if cfg.MaxQueuedJobs > 0 { + pending := 0 + for _, j := range s.state.Jobs { + if j != nil && (j.Status == "queued" || j.Status == "claimed" || j.Status == "retry_wait" || j.Status == "blocked" || j.Status == "apply_wait") { + pending++ + } + } + if pending >= cfg.MaxQueuedJobs { + return nil, fmt.Errorf("orchestrator queue full: %d >= %d", pending, cfg.MaxQueuedJobs) + } + } + if key := strings.TrimSpace(spec.IdempotencyKey); key != "" { + for _, j := range s.state.Jobs { + if j != nil && j.IdempotencyKey == key && (j.Status == "queued" || j.Status == "claimed" || j.Status == "retry_wait" || j.Status == "blocked" || j.Status == "apply_wait") { + cp := *j + return &cp, nil + } + } + } + // Dependencies must already exist. Accepting dangling IDs would create jobs + // that can remain blocked forever and makes convergence impossible to reason + // about after operator typos or partial planning failures. + seenDeps := make(map[string]struct{}, len(spec.DependsOn)) + cleanDeps := make([]string, 0, len(spec.DependsOn)) + for _, depID := range spec.DependsOn { + depID = strings.TrimSpace(depID) + if depID == "" { + return nil, errors.New("dependency job id must not be empty") + } + if _, duplicate := seenDeps[depID]; duplicate { + continue + } + if s.state.Jobs[depID] == nil { + return nil, fmt.Errorf("dependency job %s not found", depID) + } + seenDeps[depID] = struct{}{} + cleanDeps = append(cleanDeps, depID) + } + spec.DependsOn = cleanDeps + if parentID := strings.TrimSpace(spec.ParentJobID); parentID != "" && s.state.Jobs[parentID] == nil { + return nil, fmt.Errorf("parent job %s not found", parentID) + } + now := time.Now().UTC() + maxAttempts := spec.MaxAttempts + if maxAttempts <= 0 { + maxAttempts = cfg.DefaultMaxAttempts + } + if maxAttempts <= 0 { + maxAttempts = 3 + } + backoff := spec.BackoffSeconds + if backoff <= 0 { + backoff = cfg.RetryBackoffSeconds + } + if backoff <= 0 { + backoff = 15 + } + maxApplyAttempts := spec.MaxApplyAttempts + if maxApplyAttempts <= 0 { + maxApplyAttempts = cfg.MasterApplyMaxAttempts + } + if maxApplyAttempts <= 0 { + maxApplyAttempts = 5 + } + applyBackoff := spec.ApplyBackoffSeconds + if applyBackoff <= 0 { + applyBackoff = cfg.MasterApplyBackoffSeconds + } + if applyBackoff <= 0 { + applyBackoff = 5 + } + priority := spec.Priority + if priority < -1000 { + priority = -1000 + } + if priority > 1000 { + priority = 1000 + } + status := "queued" + if len(spec.DependsOn) > 0 { + status = "blocked" + } + j := &core.Job{ + ID: NewID("job"), + Type: spec.Type, + Payload: b, + Status: status, + Priority: priority, + ResourceClass: strings.ToLower(strings.TrimSpace(spec.ResourceClass)), + RequiredCapabilities: normalizeCapabilities(spec.RequiredCapabilities), + IdempotencyKey: strings.TrimSpace(spec.IdempotencyKey), + ParentJobID: strings.TrimSpace(spec.ParentJobID), + DependsOn: append([]string(nil), spec.DependsOn...), + MaxAttempts: maxAttempts, + BackoffSeconds: backoff, + TimeoutSeconds: spec.TimeoutSeconds, + RequiresMasterApply: spec.RequiresMasterApply, + MaxApplyAttempts: maxApplyAttempts, + ApplyBackoffSeconds: applyBackoff, + CreatedAt: now, + UpdatedAt: now, + } + s.state.Jobs[j.ID] = j + cp := *j + return &cp, s.commitLocked("job.upsert", cp) +} + +func (s *Store) dependencyStateLocked(j *core.Job) (ready bool, terminalFailure string) { + if len(j.DependsOn) == 0 { + return true, "" + } + for _, id := range j.DependsOn { + dep := s.state.Jobs[id] + if dep == nil { + return false, "" + } + switch dep.Status { + case "done": + continue + case "failed", "canceled": + return false, "dependency " + id + " ended as " + dep.Status + default: + return false, "" + } + } + return true, "" +} + +func workerMatchesJob(w WorkerHeartbeat, j *core.Job) bool { + resource := strings.ToLower(strings.TrimSpace(j.ResourceClass)) + workerResource := strings.ToLower(strings.TrimSpace(w.ResourceClass)) + if resource != "" && resource != "any" && resource != workerResource { + return false + } + return capabilitiesContain(w.Capabilities, j.RequiredCapabilities) +} + +func (s *Store) registerWorkerLocked(h WorkerHeartbeat, now time.Time) core.WorkerState { + if s.workers == nil { + s.workers = map[string]core.WorkerState{} + } + old := s.workers[h.ID] + registered := old.RegisteredAt + if registered.IsZero() { + registered = now + } + maxc := h.MaxConcurrency + if maxc <= 0 { + maxc = 1 + } + state := core.WorkerState{ + ID: h.ID, + ResourceClass: strings.ToLower(strings.TrimSpace(h.ResourceClass)), + Capabilities: normalizeCapabilities(h.Capabilities), + Labels: h.Labels, + MaxConcurrency: maxc, + Version: strings.TrimSpace(h.Version), + Hostname: strings.TrimSpace(h.Hostname), + LastHeartbeat: now, + RegisteredAt: registered, + Status: "online", + } + if state.ResourceClass == "" { + state.ResourceClass = "cpu" + } + if len(state.Capabilities) == 0 { + state.Capabilities = []string{"cpu", "vector.relink"} + } + for _, j := range s.state.Jobs { + if j != nil && j.Status == "claimed" && j.ClaimedBy == h.ID && now.Before(j.LeaseUntil) { + state.Inflight++ + } + } + s.workers[h.ID] = state + return state +} + +func (s *Store) RegisterWorker(h WorkerHeartbeat) (core.WorkerState, error) { + h.ID = strings.TrimSpace(h.ID) + if h.ID == "" { + return core.WorkerState{}, errors.New("worker id is required") + } + s.mu.Lock() + defer s.mu.Unlock() + return s.registerWorkerLocked(h, time.Now().UTC()), nil +} + +func (s *Store) HeartbeatWorker(h WorkerHeartbeat, lease time.Duration) (core.WorkerState, error) { + h.ID = strings.TrimSpace(h.ID) + if h.ID == "" { + return core.WorkerState{}, errors.New("worker id is required") + } + if lease <= 0 { + lease = 120 * time.Second + } + s.mu.Lock() + defer s.mu.Unlock() + now := time.Now().UTC() + state := s.registerWorkerLocked(h, now) + for jobID, token := range h.ActiveLeases { + j := s.state.Jobs[jobID] + if j == nil || j.Status != "claimed" || j.ClaimedBy != h.ID || j.LeaseToken == "" || j.LeaseToken != token { + continue + } + // Persist a renewal only when the remaining lease is below half the + // configured lease. This gives restart-safe leases without heartbeat WAL spam. + if time.Until(j.LeaseUntil) <= lease/2 { + j.LeaseUntil = now.Add(lease) + j.UpdatedAt = now + cp := *j + if err := s.commitLocked("job.upsert", cp); err != nil { + return core.WorkerState{}, err + } + } + } + return state, nil +} + +func (s *Store) normalizeJobForClaimLocked(j *core.Job, now time.Time) error { + if j == nil { + return nil + } + changed := false + if j.MaxAttempts <= 0 { + j.MaxAttempts = s.state.Config.Worker.DefaultMaxAttempts + if j.MaxAttempts <= 0 { + j.MaxAttempts = 3 + } + changed = true + } + if j.Status == "claimed" && !j.LeaseUntil.IsZero() && now.After(j.LeaseUntil) { + j.ClaimedBy = "" + j.LeaseToken = "" + j.LeaseUntil = time.Time{} + if j.Attempts >= j.MaxAttempts { + j.Status = "failed" + j.Error = "lease expired after maximum attempts" + j.FinishedAt = now + } else { + j.Status = "retry_wait" + j.Error = "worker lease expired" + j.NextAttemptAt = now.Add(time.Duration(maxInt(1, j.BackoffSeconds)) * time.Second) + } + j.UpdatedAt = now + changed = true + } + if j.Status == "retry_wait" && (j.NextAttemptAt.IsZero() || !now.Before(j.NextAttemptAt)) { + j.Status = "queued" + j.NextAttemptAt = time.Time{} + j.UpdatedAt = now + changed = true + } + if j.Status == "blocked" { + ready, depFailure := s.dependencyStateLocked(j) + if depFailure != "" { + j.Status = "failed" + j.Error = depFailure + j.FinishedAt = now + j.UpdatedAt = now + changed = true + } else if ready { + j.Status = "queued" + j.UpdatedAt = now + changed = true + } + } + if changed { + cp := *j + return s.commitLocked("job.upsert", cp) + } + return nil +} + +func maxInt(a, b int) int { + if a > b { + return a + } + return b +} + +// ClaimJob preserves the v0.8 worker contract for compatibility. New workers +// should use ClaimJobForWorker so resource/capability routing is enforced. +func (s *Store) ClaimJob(worker string, lease time.Duration) (*core.Job, error) { + return s.ClaimJobForWorker(WorkerHeartbeat{ID: worker, ResourceClass: "cpu", Capabilities: []string{"cpu", "vector.relink"}, MaxConcurrency: 1}, lease) +} + +func (s *Store) ClaimJobForWorker(h WorkerHeartbeat, lease time.Duration) (*core.Job, error) { + h.ID = strings.TrimSpace(h.ID) + if h.ID == "" { + return nil, errors.New("worker id is required") + } + if lease <= 0 { + lease = 120 * time.Second + } + s.mu.Lock() + defer s.mu.Unlock() + now := time.Now().UTC() + state := s.registerWorkerLocked(h, now) + if state.Inflight >= state.MaxConcurrency { + return nil, nil + } + var chosen *core.Job + for _, j := range s.state.Jobs { + if err := s.normalizeJobForClaimLocked(j, now); err != nil { + return nil, err + } + if j == nil || j.Status != "queued" || !workerMatchesJob(h, j) { + continue + } + if chosen == nil || j.Priority > chosen.Priority || (j.Priority == chosen.Priority && j.CreatedAt.Before(chosen.CreatedAt)) { + chosen = j + } + } + if chosen == nil { + return nil, nil + } + chosen.Status = "claimed" + chosen.ClaimedBy = h.ID + chosen.LeaseToken = NewID("lease") + chosen.LeaseUntil = now.Add(lease) + chosen.Attempts++ + chosen.UpdatedAt = now + if chosen.StartedAt.IsZero() { + chosen.StartedAt = now + } + cp := *chosen + return &cp, s.commitLocked("job.upsert", cp) +} + +func (s *Store) CompleteJob(id, worker string, result json.RawMessage, jobErr string) (*core.Job, error) { + return s.CompleteJobLease(id, worker, "", result, jobErr) +} + +func (s *Store) CompleteJobLease(id, worker, leaseToken string, result json.RawMessage, jobErr string) (*core.Job, error) { + s.mu.Lock() + defer s.mu.Unlock() + j, ok := s.state.Jobs[id] + if !ok || j == nil { + return nil, errors.New("job not found") + } + if j.Status != "claimed" { + return nil, fmt.Errorf("job is not claimed: %s", j.Status) + } + if j.ClaimedBy != worker { + return nil, errors.New("job claimed by another worker") + } + if j.LeaseToken != "" && leaseToken != j.LeaseToken { + return nil, errors.New("stale or invalid job lease token") + } + now := time.Now().UTC() + if !j.LeaseUntil.IsZero() && !now.Before(j.LeaseUntil) { + // A lease token fences ownership, but its validity also has a deadline. + // Reject late results even if no other worker has claimed the job yet; + // otherwise a paused partitioned worker could mutate state after expiry. + return nil, errors.New("stale or expired job lease") + } + j.Result = result + j.Error = strings.TrimSpace(jobErr) + j.UpdatedAt = now + j.ClaimedBy = "" + j.LeaseToken = "" + j.LeaseUntil = time.Time{} + if j.Error == "" { + j.NextAttemptAt = time.Time{} + if j.RequiresMasterApply { + j.Status = "apply_wait" + j.ApplyError = "" + j.ApplyNextAttemptAt = now + } else { + j.Status = "done" + j.FinishedAt = now + } + } else if j.Attempts < maxInt(1, j.MaxAttempts) { + j.Status = "retry_wait" + backoff := maxInt(1, j.BackoffSeconds) + shift := j.Attempts - 1 + if shift > 6 { + shift = 6 + } + delay := time.Duration(backoff*(1< time.Hour { + delay = time.Hour + } + j.NextAttemptAt = now.Add(delay) + } else { + j.Status = "failed" + j.FinishedAt = now + } + cp := *j + return &cp, s.commitLocked("job.upsert", cp) +} + +func (s *Store) Job(id string) (*core.Job, bool) { + s.mu.RLock() + defer s.mu.RUnlock() + j := s.state.Jobs[id] + if j == nil { + return nil, false + } + cp := *j + cp.Payload = append(json.RawMessage(nil), j.Payload...) + cp.Result = append(json.RawMessage(nil), j.Result...) + return &cp, true +} + +func (s *Store) JobsSnapshot(limit int, status, kind string) []core.Job { + if limit <= 0 || limit > 5000 { + limit = 500 + } + status = strings.TrimSpace(status) + kind = strings.TrimSpace(kind) + s.mu.RLock() + defer s.mu.RUnlock() + out := make([]core.Job, 0) + for _, j := range s.state.Jobs { + if j == nil || (status != "" && j.Status != status) || (kind != "" && j.Type != kind) { + continue + } + cp := *j + // Admin status does not need potentially large/sensitive payloads. + cp.Payload = nil + cp.Result = nil + out = append(out, cp) + } + sort.Slice(out, func(i, j int) bool { return out[i].CreatedAt.After(out[j].CreatedAt) }) + if len(out) > limit { + out = out[:limit] + } + return out +} + +func (s *Store) RetryJob(id string) (*core.Job, error) { + s.mu.Lock() + defer s.mu.Unlock() + j := s.state.Jobs[id] + if j == nil { + return nil, errors.New("job not found") + } + if j.Status != "failed" && j.Status != "canceled" { + return nil, fmt.Errorf("job %s cannot be retried from status %s", id, j.Status) + } + j.Status = "queued" + j.Error = "" + j.Result = nil + j.Attempts = 0 + j.NextAttemptAt = time.Time{} + j.FinishedAt = time.Time{} + j.UpdatedAt = time.Now().UTC() + cp := *j + return &cp, s.commitLocked("job.upsert", cp) +} + +func (s *Store) WorkersSnapshot() []core.WorkerState { + s.mu.RLock() + defer s.mu.RUnlock() + now := time.Now().UTC() + staleAfter := time.Duration(s.state.Config.Worker.StaleAfterSeconds) * time.Second + if staleAfter <= 0 { + staleAfter = 60 * time.Second + } + out := make([]core.WorkerState, 0, len(s.workers)) + for _, w := range s.workers { + cp := w + cp.Inflight = 0 + for _, j := range s.state.Jobs { + if j != nil && j.Status == "claimed" && j.ClaimedBy == w.ID && now.Before(j.LeaseUntil) { + cp.Inflight++ + } + } + if now.Sub(cp.LastHeartbeat) > staleAfter { + cp.Status = "stale" + } + out = append(out, cp) + } + sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID }) + return out +} + +func (s *Store) HasLiveWorker(resource string, capabilities ...string) bool { + resource = strings.ToLower(strings.TrimSpace(resource)) + for _, w := range s.WorkersSnapshot() { + if w.Status != "online" || (resource != "" && resource != "any" && w.ResourceClass != resource) { + continue + } + if capabilitiesContain(w.Capabilities, capabilities) && w.Inflight < w.MaxConcurrency { + return true + } + } + return false +} + +func (s *Store) OrchestratorStatus() OrchestratorStatus { + out := OrchestratorStatus{Workers: s.WorkersSnapshot(), JobsByStatus: map[string]int{}, JobsByType: map[string]int{}, JobsByResource: map[string]int{}} + s.mu.RLock() + defer s.mu.RUnlock() + for _, j := range s.state.Jobs { + if j == nil { + continue + } + out.JobsByStatus[j.Status]++ + out.JobsByType[j.Type]++ + resource := j.ResourceClass + if resource == "" { + resource = "any" + } + out.JobsByResource[resource]++ + switch j.Status { + case "queued": + out.Queued++ + if out.OldestQueued.IsZero() || j.CreatedAt.Before(out.OldestQueued) { + out.OldestQueued = j.CreatedAt + } + case "claimed": + out.Claimed++ + case "retry_wait": + out.Retrying++ + case "apply_wait": + out.Applying++ + case "blocked": + out.Blocked++ + case "failed": + out.Failed++ + case "done": + out.Done++ + } + } + return out +} + +func (s *Store) PendingJobCount(kind string) int { + s.mu.RLock() + defer s.mu.RUnlock() + n := 0 + for _, j := range s.state.Jobs { + if j == nil || (kind != "" && j.Type != kind) { + continue + } + if j.Status == "queued" || j.Status == "claimed" || j.Status == "retry_wait" || j.Status == "blocked" || j.Status == "apply_wait" { + n++ + } + } + return n +} + +// PendingMasterApplyJobs returns durable worker results whose mutation still +// needs to be committed by the master. The result payload remains persisted, +// so a master restart can resume without recomputing the worker job. +func (s *Store) PendingMasterApplyJobs(limit int) []core.Job { + if limit <= 0 || limit > 1024 { + limit = 64 + } + now := time.Now().UTC() + s.mu.RLock() + defer s.mu.RUnlock() + out := make([]core.Job, 0, limit) + for _, j := range s.state.Jobs { + if j == nil || j.Status != "apply_wait" || (!j.ApplyNextAttemptAt.IsZero() && now.Before(j.ApplyNextAttemptAt)) { + continue + } + cp := *j + cp.Payload = append(json.RawMessage(nil), j.Payload...) + cp.Result = append(json.RawMessage(nil), j.Result...) + out = append(out, cp) + } + sort.Slice(out, func(i, j int) bool { + if out[i].Priority != out[j].Priority { + return out[i].Priority > out[j].Priority + } + return out[i].UpdatedAt.Before(out[j].UpdatedAt) + }) + if len(out) > limit { + out = out[:limit] + } + return out +} + +// FinishMasterApply closes the second phase of a worker job. Failed apply +// attempts are retried from the already persisted Result; successful worker +// computation is never lost merely because the master restarted mid-commit. +func (s *Store) FinishMasterApply(id string, applyErr error) (*core.Job, error) { + s.mu.Lock() + defer s.mu.Unlock() + j := s.state.Jobs[id] + if j == nil { + return nil, errors.New("job not found") + } + if j.Status != "apply_wait" { + return nil, fmt.Errorf("job is not awaiting master apply: %s", j.Status) + } + now := time.Now().UTC() + j.ApplyAttempts++ + j.UpdatedAt = now + if applyErr == nil { + j.Status = "done" + j.ApplyError = "" + j.ApplyNextAttemptAt = time.Time{} + j.FinishedAt = now + } else { + j.ApplyError = strings.TrimSpace(applyErr.Error()) + maxAttempts := j.MaxApplyAttempts + if maxAttempts <= 0 { + maxAttempts = maxInt(1, s.state.Config.Worker.MasterApplyMaxAttempts) + } + if j.ApplyAttempts >= maxAttempts { + j.Status = "failed" + j.Error = "master apply failed: " + j.ApplyError + j.FinishedAt = now + j.ApplyNextAttemptAt = time.Time{} + } else { + backoff := j.ApplyBackoffSeconds + if backoff <= 0 { + backoff = maxInt(1, s.state.Config.Worker.MasterApplyBackoffSeconds) + } + shift := j.ApplyAttempts - 1 + if shift > 6 { + shift = 6 + } + delay := time.Duration(backoff*(1< time.Hour { + delay = time.Hour + } + j.ApplyNextAttemptAt = now.Add(delay) + } + } + cp := *j + return &cp, s.commitLocked("job.upsert", cp) +} + +func (s *Store) CancelJob(id, reason string) error { + s.mu.Lock() + defer s.mu.Unlock() + j := s.state.Jobs[id] + if j == nil { + return errors.New("job not found") + } + if j.Status == "done" || j.Status == "failed" || j.Status == "canceled" { + return nil + } + now := time.Now().UTC() + j.Status = "canceled" + j.Error = strings.TrimSpace(reason) + j.ClaimedBy = "" + j.LeaseToken = "" + j.LeaseUntil = time.Time{} + j.FinishedAt = now + j.UpdatedAt = now + cp := *j + return s.commitLocked("job.upsert", cp) +} + +// PruneTerminalJobs bounds durable scheduler history while preserving jobs that +// are still referenced as dependencies by non-terminal work. +func (s *Store) PruneTerminalJobs(retention time.Duration, maxTerminal int) (int, error) { + if retention <= 0 { + retention = 7 * 24 * time.Hour + } + if maxTerminal < 100 { + maxTerminal = 20000 + } + s.mu.Lock() + defer s.mu.Unlock() + now := time.Now().UTC() + cutoff := now.Add(-retention) + referenced := map[string]bool{} + for _, j := range s.state.Jobs { + if j == nil || j.Status == "done" || j.Status == "failed" || j.Status == "canceled" { + continue + } + for _, dep := range j.DependsOn { + referenced[dep] = true + } + } + type terminal struct { + id string + t time.Time + } + items := make([]terminal, 0) + for id, j := range s.state.Jobs { + if j == nil || referenced[id] || (j.Status != "done" && j.Status != "failed" && j.Status != "canceled") { + continue + } + t := j.FinishedAt + if t.IsZero() { + t = j.UpdatedAt + } + items = append(items, terminal{id: id, t: t}) + } + sort.Slice(items, func(i, j int) bool { return items[i].t.Before(items[j].t) }) + remove := map[string]bool{} + for _, x := range items { + if !x.t.IsZero() && x.t.Before(cutoff) { + remove[x.id] = true + } + } + remaining := len(items) - len(remove) + if remaining > maxTerminal { + need := remaining - maxTerminal + for _, x := range items { + if need == 0 { + break + } + if remove[x.id] { + continue + } + remove[x.id] = true + need-- + } + } + if len(remove) == 0 { + return 0, nil + } + ids := make([]string, 0, len(remove)) + for id := range remove { + delete(s.state.Jobs, id) + ids = append(ids, id) + } + sort.Strings(ids) + return len(ids), s.commitLocked("job.delete", ids) +} + +// IsOrchestratorLeader prevents two clustered masters from planning/applying +// the same durable work. Standalone installations are always authoritative. +func (s *Store) IsOrchestratorLeader() bool { + s.mu.RLock() + defer s.mu.RUnlock() + cfg := s.state.Config.Cluster + if !cfg.Enabled { + return true + } + leader := s.state.Cluster.LeaderID + if !cfg.AutoElection && leader == "" { + leader = cfg.LeaderID + } + return leader != "" && leader == cfg.NodeID && s.state.Cluster.Role == ClusterLeader +} diff --git a/platform/neuroforge/internal/store/orchestrator_test.go b/platform/neuroforge/internal/store/orchestrator_test.go new file mode 100644 index 0000000..16ca3a8 --- /dev/null +++ b/platform/neuroforge/internal/store/orchestrator_test.go @@ -0,0 +1,294 @@ +package store + +import ( + "encoding/json" + "strings" + "testing" + "time" +) + +func TestOrchestratorCapabilityRoutingAndLeaseFencing(t *testing.T) { + s, err := New(t.TempDir()) + if err != nil { + t.Fatal(err) + } + defer s.Close() + + j, err := s.EnqueueJobSpec(JobSpec{ + Type: "model.chat", Payload: map[string]any{"input": "x"}, Priority: 100, + ResourceClass: "gpu", RequiredCapabilities: []string{"gpu", "model.chat"}, + }) + if err != nil { + t.Fatal(err) + } + + cpu := WorkerHeartbeat{ID: "cpu-1", ResourceClass: "cpu", Capabilities: []string{"cpu", "vector.relink"}, MaxConcurrency: 1} + got, err := s.ClaimJobForWorker(cpu, time.Minute) + if err != nil { + t.Fatal(err) + } + if got != nil { + t.Fatalf("CPU worker must not claim GPU job: %+v", got) + } + + gpu := WorkerHeartbeat{ID: "gpu-1", ResourceClass: "gpu", Capabilities: []string{"gpu", "model.chat"}, MaxConcurrency: 1} + got, err = s.ClaimJobForWorker(gpu, time.Minute) + if err != nil { + t.Fatal(err) + } + if got == nil || got.ID != j.ID || got.LeaseToken == "" { + t.Fatalf("GPU claim=%+v", got) + } + + if _, err := s.CompleteJobLease(got.ID, gpu.ID, "stale-token", json.RawMessage(`{"ok":true}`), ""); err == nil || !strings.Contains(err.Error(), "lease") { + t.Fatalf("stale lease must be fenced, err=%v", err) + } + done, err := s.CompleteJobLease(got.ID, gpu.ID, got.LeaseToken, json.RawMessage(`{"ok":true}`), "") + if err != nil { + t.Fatal(err) + } + if done.Status != "done" { + t.Fatalf("status=%s", done.Status) + } +} + +func TestOrchestratorRetryDependencyAndWorkerStaleness(t *testing.T) { + s, err := New(t.TempDir()) + if err != nil { + t.Fatal(err) + } + defer s.Close() + cfg := s.Config() + cfg.Worker.RetryBackoffSeconds = 1 + cfg.Worker.HeartbeatSeconds = 2 + cfg.Worker.StaleAfterSeconds = 2 + if err := s.UpdateConfig(cfg); err != nil { + t.Fatal(err) + } + + parent, err := s.EnqueueJobSpec(JobSpec{Type: "parent", Payload: map[string]any{}, ResourceClass: "cpu", MaxAttempts: 2, BackoffSeconds: 1}) + if err != nil { + t.Fatal(err) + } + child, err := s.EnqueueJobSpec(JobSpec{Type: "child", Payload: map[string]any{}, ResourceClass: "cpu", DependsOn: []string{parent.ID}}) + if err != nil { + t.Fatal(err) + } + if child.Status != "blocked" { + t.Fatalf("child status=%s", child.Status) + } + + w := WorkerHeartbeat{ID: "cpu-1", ResourceClass: "cpu", Capabilities: []string{"cpu"}, MaxConcurrency: 1} + p1, err := s.ClaimJobForWorker(w, time.Minute) + if err != nil { + t.Fatal(err) + } + if p1 == nil || p1.ID != parent.ID { + t.Fatalf("claim=%+v", p1) + } + retry, err := s.CompleteJobLease(p1.ID, w.ID, p1.LeaseToken, nil, "temporary") + if err != nil { + t.Fatal(err) + } + if retry.Status != "retry_wait" || retry.NextAttemptAt.IsZero() { + t.Fatalf("retry=%+v", retry) + } + + // Avoid sleeping: advance this job's retry clock under the package lock. + s.mu.Lock() + s.state.Jobs[parent.ID].NextAttemptAt = time.Now().Add(-time.Second) + s.mu.Unlock() + p2, err := s.ClaimJobForWorker(w, time.Minute) + if err != nil { + t.Fatal(err) + } + if p2 == nil || p2.ID != parent.ID || p2.Attempts != 2 { + t.Fatalf("second claim=%+v", p2) + } + if _, err := s.CompleteJobLease(p2.ID, w.ID, p2.LeaseToken, json.RawMessage(`{"ok":true}`), ""); err != nil { + t.Fatal(err) + } + + c, err := s.ClaimJobForWorker(w, time.Minute) + if err != nil { + t.Fatal(err) + } + if c == nil || c.ID != child.ID { + t.Fatalf("child claim after dependency=%+v", c) + } + + s.mu.Lock() + ws := s.workers[w.ID] + ws.LastHeartbeat = time.Now().Add(-2 * time.Second) + s.workers[w.ID] = ws + s.mu.Unlock() + workers := s.WorkersSnapshot() + if len(workers) != 1 || workers[0].Status != "stale" { + t.Fatalf("workers=%+v", workers) + } +} + +func TestDependencyFailureFailsBlockedChild(t *testing.T) { + s, err := New(t.TempDir()) + if err != nil { + t.Fatal(err) + } + defer s.Close() + p, err := s.EnqueueJobSpec(JobSpec{Type: "parent", Payload: map[string]any{}, ResourceClass: "cpu", MaxAttempts: 1}) + if err != nil { + t.Fatal(err) + } + c, err := s.EnqueueJobSpec(JobSpec{Type: "child", Payload: map[string]any{}, ResourceClass: "cpu", DependsOn: []string{p.ID}}) + if err != nil { + t.Fatal(err) + } + w := WorkerHeartbeat{ID: "cpu", ResourceClass: "cpu", Capabilities: []string{"cpu"}, MaxConcurrency: 1} + pj, _ := s.ClaimJobForWorker(w, time.Minute) + if _, err := s.CompleteJobLease(pj.ID, w.ID, pj.LeaseToken, nil, "permanent"); err != nil { + t.Fatal(err) + } + got, err := s.ClaimJobForWorker(w, time.Minute) + if err != nil { + t.Fatal(err) + } + if got != nil { + t.Fatalf("failed dependency child must not be claimable: %+v", got) + } + cj, ok := s.Job(c.ID) + if !ok || cj.Status != "failed" || !strings.Contains(cj.Error, "dependency") { + t.Fatalf("child=%+v", cj) + } +} + +func TestPruneTerminalJobsPreservesReferencedDependencies(t *testing.T) { + s, err := New(t.TempDir()) + if err != nil { + t.Fatal(err) + } + defer s.Close() + p, err := s.EnqueueJobSpec(JobSpec{Type: "parent", Payload: map[string]any{}, ResourceClass: "cpu"}) + if err != nil { + t.Fatal(err) + } + w := WorkerHeartbeat{ID: "cpu", ResourceClass: "cpu", Capabilities: []string{"cpu"}, MaxConcurrency: 1} + pj, _ := s.ClaimJobForWorker(w, time.Minute) + if _, err := s.CompleteJobLease(pj.ID, w.ID, pj.LeaseToken, nil, ""); err != nil { + t.Fatal(err) + } + _, err = s.EnqueueJobSpec(JobSpec{Type: "child", Payload: map[string]any{}, ResourceClass: "gpu", RequiredCapabilities: []string{"gpu"}, DependsOn: []string{p.ID}}) + if err != nil { + t.Fatal(err) + } + + s.mu.Lock() + s.state.Jobs[p.ID].FinishedAt = time.Now().Add(-48 * time.Hour) + s.state.Jobs[p.ID].UpdatedAt = s.state.Jobs[p.ID].FinishedAt + s.mu.Unlock() + n, err := s.PruneTerminalJobs(time.Hour, 100) + if err != nil { + t.Fatal(err) + } + if n != 0 { + t.Fatalf("pruned referenced dependency: %d", n) + } + if _, ok := s.Job(p.ID); !ok { + t.Fatal("referenced parent was removed") + } +} + +func TestExpiredLeaseCannotCompleteBeforeReclaim(t *testing.T) { + s, err := New(t.TempDir()) + if err != nil { + t.Fatal(err) + } + defer s.Close() + _, err = s.EnqueueJobSpec(JobSpec{Type: "cpu.task", Payload: map[string]any{"x": 1}, ResourceClass: "cpu", RequiredCapabilities: []string{"cpu"}}) + if err != nil { + t.Fatal(err) + } + w := WorkerHeartbeat{ID: "cpu-1", ResourceClass: "cpu", Capabilities: []string{"cpu"}, MaxConcurrency: 1} + j, err := s.ClaimJobForWorker(w, time.Minute) + if err != nil || j == nil { + t.Fatalf("claim=%+v err=%v", j, err) + } + s.mu.Lock() + s.state.Jobs[j.ID].LeaseUntil = time.Now().Add(-time.Second) + s.mu.Unlock() + if _, err := s.CompleteJobLease(j.ID, w.ID, j.LeaseToken, json.RawMessage(`{"ok":true}`), ""); err == nil || !strings.Contains(err.Error(), "expired") { + t.Fatalf("expired lease completion must be fenced, err=%v", err) + } + cur, ok := s.Job(j.ID) + if !ok || cur.Status != "claimed" { + t.Fatalf("late completion mutated job before scheduler recovery: %+v", cur) + } + // Any subsequent scheduler/claim pass normalizes the expired lease and may + // make the job retryable according to its durable attempt policy. + other := WorkerHeartbeat{ID: "cpu-2", ResourceClass: "cpu", Capabilities: []string{"cpu"}, MaxConcurrency: 1} + next, err := s.ClaimJobForWorker(other, time.Minute) + if err != nil { + t.Fatal(err) + } + if next != nil { + t.Fatalf("retry backoff should prevent immediate reclaim: %+v", next) + } + cur, _ = s.Job(j.ID) + if cur.Status != "retry_wait" || cur.Error != "worker lease expired" { + t.Fatalf("expired lease not normalized to retry_wait: %+v", cur) + } +} + +func TestOrchestratorRejectsDanglingDependencies(t *testing.T) { + s, err := New(t.TempDir()) + if err != nil { + t.Fatal(err) + } + defer s.Close() + if _, err := s.EnqueueJobSpec(JobSpec{Type: "child", Payload: map[string]any{}, DependsOn: []string{"job_missing"}}); err == nil || !strings.Contains(err.Error(), "not found") { + t.Fatalf("dangling dependency must fail closed, err=%v", err) + } + if s.PendingJobCount("") != 0 { + t.Fatalf("failed planning left durable work behind: %+v", s.OrchestratorStatus()) + } +} + +func TestApplyWaitJobSurvivesRestart(t *testing.T) { + dir := t.TempDir() + s, err := New(dir) + if err != nil { + t.Fatal(err) + } + j, err := s.EnqueueJobSpec(JobSpec{Type: "master.apply", Payload: map[string]any{"x": 1}, ResourceClass: "cpu", RequiredCapabilities: []string{"cpu"}, RequiresMasterApply: true}) + if err != nil { + t.Fatal(err) + } + w := WorkerHeartbeat{ID: "cpu-1", ResourceClass: "cpu", Capabilities: []string{"cpu"}, MaxConcurrency: 1} + claimed, err := s.ClaimJobForWorker(w, time.Minute) + if err != nil || claimed == nil || claimed.ID != j.ID { + t.Fatalf("claim=%+v err=%v", claimed, err) + } + result := json.RawMessage(`{"ok":true}`) + completed, err := s.CompleteJobLease(claimed.ID, w.ID, claimed.LeaseToken, result, "") + if err != nil || completed.Status != "apply_wait" { + t.Fatalf("complete=%+v err=%v", completed, err) + } + if err := s.Close(); err != nil { + t.Fatal(err) + } + + s2, err := New(dir) + if err != nil { + t.Fatal(err) + } + defer s2.Close() + recovered, ok := s2.Job(j.ID) + if !ok || recovered.Status != "apply_wait" { + t.Fatalf("durable apply_wait job not recovered: %+v", recovered) + } + if len(recovered.Payload) == 0 || string(recovered.Result) != string(result) { + t.Fatalf("recovered payload/result incomplete: %+v", recovered) + } + pending := s2.PendingMasterApplyJobs(10) + if len(pending) != 1 || pending[0].ID != j.ID { + t.Fatalf("recovered master apply queue=%+v", pending) + } +} diff --git a/platform/neuroforge/internal/store/store.go b/platform/neuroforge/internal/store/store.go index 4cf7567..8445572 100644 --- a/platform/neuroforge/internal/store/store.go +++ b/platform/neuroforge/internal/store/store.go @@ -45,6 +45,8 @@ type Store struct { clusterLogMu sync.Mutex clusterLog *ClusterLog provenanceSourceIDs map[string]map[string]struct{} + workers map[string]core.WorkerState + synapseAdj map[string]map[string]*core.Synapse } func New(dir string) (*Store, error) { @@ -54,7 +56,7 @@ func New(dir string) (*Store, error) { if err := os.MkdirAll(dir, 0700); err != nil { return nil, err } - s := &Store{dir: dir, indexes: map[int]*vector.HNSW{}, diskIndexes: map[int]*vector.PQIndex{}, provenanceSourceIDs: map[string]map[string]struct{}{}} + s := &Store{dir: dir, indexes: map[int]*vector.HNSW{}, diskIndexes: map[int]*vector.PQIndex{}, provenanceSourceIDs: map[string]map[string]struct{}{}, workers: map[string]core.WorkerState{}, synapseAdj: map[string]map[string]*core.Synapse{}} s.state = core.PersistedState{Config: core.DefaultConfig(), Memories: map[string]*core.Memory{}, Synapses: map[string]*core.Synapse{}, Jobs: map[string]*core.Job{}, Goals: map[string]*core.Goal{}, Sources: map[string]*core.KnowledgeSource{}, ResearchRuns: map[string]*core.ResearchRun{}} _ = s.loadJSON(filepath.Join(dir, "state.json"), &s.state) _ = s.loadJSON(filepath.Join(dir, "secrets.json"), &s.secrets) @@ -111,6 +113,7 @@ func New(dir string) (*Store, error) { if err := s.replayWAL(); err != nil { return nil, err } + s.rebuildSynapseAdjLocked() applyNewDefaults(&s.state.Config) if s.state.Cluster.Term < s.state.Config.Cluster.Term { s.state.Cluster.Term = s.state.Config.Cluster.Term @@ -235,6 +238,18 @@ func applyNewDefaults(c *core.Config) { if c.Brain.TypeWeights == nil { c.Brain.TypeWeights = d.Brain.TypeWeights } + if c.Brain.GraphMaxHops == 0 { + c.Brain.GraphMaxHops = d.Brain.GraphMaxHops + } + if c.Brain.GraphHopDecay == 0 { + c.Brain.GraphHopDecay = d.Brain.GraphHopDecay + } + if c.Brain.GraphMaxExpansion == 0 { + c.Brain.GraphMaxExpansion = d.Brain.GraphMaxExpansion + } + if c.Brain.GraphMinEdgeWeight == 0 { + c.Brain.GraphMinEdgeWeight = d.Brain.GraphMinEdgeWeight + } if c.Brain.LearningPolicy.MaxMemoryTextChars == 0 && c.Brain.LearningPolicy.DuplicateSimilarity == 0 && c.Brain.LearningPolicy.SemanticMinConfirmations == 0 { c.Brain.LearningPolicy = d.Brain.LearningPolicy } else { @@ -484,6 +499,65 @@ func applyNewDefaults(c *core.Config) { if c.Cluster.LogSegmentBytes == 0 { c.Cluster.LogSegmentBytes = d.Cluster.LogSegmentBytes } + if c.Worker.LeaseSeconds == 0 { + c.Worker.LeaseSeconds = d.Worker.LeaseSeconds + } + if c.Worker.HeartbeatSeconds == 0 && c.Worker.StaleAfterSeconds == 0 && c.Worker.DefaultMaxAttempts == 0 { + legacyLease := c.Worker.LeaseSeconds + c.Worker = d.Worker + if legacyLease > 0 { + c.Worker.LeaseSeconds = legacyLease + } + } else { + if c.Worker.HeartbeatSeconds == 0 { + c.Worker.HeartbeatSeconds = d.Worker.HeartbeatSeconds + } + if c.Worker.StaleAfterSeconds == 0 { + c.Worker.StaleAfterSeconds = d.Worker.StaleAfterSeconds + } + if c.Worker.DefaultMaxAttempts == 0 { + c.Worker.DefaultMaxAttempts = d.Worker.DefaultMaxAttempts + } + if c.Worker.RetryBackoffSeconds == 0 { + c.Worker.RetryBackoffSeconds = d.Worker.RetryBackoffSeconds + } + if c.Worker.MaxQueuedJobs == 0 { + c.Worker.MaxQueuedJobs = d.Worker.MaxQueuedJobs + } + if c.Worker.MasterApplyMaxAttempts == 0 { + c.Worker.MasterApplyMaxAttempts = d.Worker.MasterApplyMaxAttempts + } + if c.Worker.MasterApplyBackoffSeconds == 0 { + c.Worker.MasterApplyBackoffSeconds = d.Worker.MasterApplyBackoffSeconds + } + if c.Worker.JobRetentionHours == 0 { + c.Worker.JobRetentionHours = d.Worker.JobRetentionHours + } + if c.Worker.MaxTerminalJobs == 0 { + c.Worker.MaxTerminalJobs = d.Worker.MaxTerminalJobs + } + if c.Worker.GraphBackfillIntervalS == 0 { + c.Worker.GraphBackfillIntervalS = d.Worker.GraphBackfillIntervalS + } + if c.Worker.GraphBackfillBatchSize == 0 { + c.Worker.GraphBackfillBatchSize = d.Worker.GraphBackfillBatchSize + } + if c.Worker.GraphBackfillMaxQueued == 0 { + c.Worker.GraphBackfillMaxQueued = d.Worker.GraphBackfillMaxQueued + } + if c.Worker.GraphBackfillMinDegree == 0 { + c.Worker.GraphBackfillMinDegree = d.Worker.GraphBackfillMinDegree + } + if c.Worker.GraphCandidateMultiplier == 0 { + c.Worker.GraphCandidateMultiplier = d.Worker.GraphCandidateMultiplier + } + if c.Worker.GraphRetryAfterMinutes == 0 { + c.Worker.GraphRetryAfterMinutes = d.Worker.GraphRetryAfterMinutes + } + if c.Worker.DistributedInferenceWaitS == 0 { + c.Worker.DistributedInferenceWaitS = d.Worker.DistributedInferenceWaitS + } + } } func migrateMemories(memories map[string]*core.Memory, localShard string) { @@ -1010,51 +1084,7 @@ func (s *Store) searchVectorLocked(q []float32, k int, min float64, graphBonus f if len(hits) > k { hits = hits[:k] } - if graphBonus > 0 && len(hits) > 0 { - base := map[string]float64{} - for _, h := range hits { - base[h.Memory.ID] = h.Score - } - for _, syn := range s.state.Synapses { - var to string - if _, ok := base[syn.A]; ok { - to = syn.B - } else if _, ok := base[syn.B]; ok { - to = syn.A - } else { - continue - } - meta, ok := s.state.Memories[to] - if !ok || !memorySearchable(meta) { - continue - } - m, fullOK := s.fullMemoryForReadLocked(to) - if !fullOK || len(m.Vector) != len(q) { - continue - } - bonus := graphBonus * syn.Weight - found := false - for i := range hits { - if hits[i].Memory.ID == to { - hits[i].GraphBoost += bonus - hits[i].Score += bonus - found = true - break - } - } - if !found && len(hits) < k { - sim := vector.Cosine(q, m.Vector) - if sim >= min { - baseScore := sim * 0.5 - hits = append(hits, SearchHit{Memory: cloneMemory(m), Similarity: sim, BaseScore: baseScore, GraphBoost: bonus, Score: bonus + baseScore, TypeWeight: 1, SalienceFactor: 1, ConfidenceFactor: 1, CandidateSource: "synapse"}) - } - } - } - sort.Slice(hits, func(i, j int) bool { return hits[i].Score > hits[j].Score }) - if len(hits) > k { - hits = hits[:k] - } - } + hits = s.graphExpandLocked(q, hits, k, min, graphBonus) return hits } @@ -1066,32 +1096,7 @@ func edgeKey(a, b string) string { } func (s *Store) Reinforce(a, b string, similarity, delta, decayPerDay, maxWeight float64) error { - if a == "" || b == "" || a == b { - return nil - } - s.mu.Lock() - defer s.mu.Unlock() - if s.state.Memories[a] == nil || s.state.Memories[b] == nil { - return nil - } - key := edgeKey(a, b) - now := time.Now().UTC() - syn, ok := s.state.Synapses[key] - if !ok { - syn = &core.Synapse{A: a, B: b, Similarity: similarity, LastUpdated: now} - s.state.Synapses[key] = syn - } - days := now.Sub(syn.LastUpdated).Hours() / 24 - if days > 0 && decayPerDay > 0 { - syn.Weight *= pow(1-decayPerDay, days) - } - syn.Weight = vector.Clamp(syn.Weight+delta, -maxWeight, maxWeight) - if similarity > syn.Similarity { - syn.Similarity = similarity - } - syn.Activations++ - syn.LastUpdated = now - return s.commitLocked("synapse.upsert", *syn) + return s.ReinforceRelation(a, b, "association", similarity, delta, decayPerDay, maxWeight) } func pow(base, exp float64) float64 { @@ -1113,6 +1118,7 @@ func (s *Store) DecayAndPruneSynapses(decayPerDay, pruneBelow float64) (int, err syn.LastUpdated = now } if pruneBelow > 0 && math.Abs(syn.Weight) < pruneBelow { + s.unindexSynapseLocked(syn) delete(s.state.Synapses, key) pruned++ } @@ -1348,66 +1354,6 @@ func (s *Store) RecentUsage(limit int) []core.UsageEvent { return out } -func (s *Store) EnqueueJob(kind string, payload any) (*core.Job, error) { - b, err := json.Marshal(payload) - if err != nil { - return nil, err - } - s.mu.Lock() - defer s.mu.Unlock() - now := time.Now().UTC() - j := &core.Job{ID: NewID("job"), Type: kind, Payload: b, Status: "queued", CreatedAt: now, UpdatedAt: now} - s.state.Jobs[j.ID] = j - return j, s.commitLocked("job.upsert", *j) -} - -func (s *Store) ClaimJob(worker string, lease time.Duration) (*core.Job, error) { - s.mu.Lock() - defer s.mu.Unlock() - now := time.Now().UTC() - var chosen *core.Job - for _, j := range s.state.Jobs { - if j.Status == "claimed" && !j.LeaseUntil.IsZero() && now.After(j.LeaseUntil) { - j.Status = "queued" - j.ClaimedBy = "" - } - if j.Status == "queued" && (chosen == nil || j.CreatedAt.Before(chosen.CreatedAt)) { - chosen = j - } - } - if chosen == nil { - return nil, nil - } - chosen.Status = "claimed" - chosen.ClaimedBy = worker - chosen.LeaseUntil = now.Add(lease) - chosen.UpdatedAt = now - cp := *chosen - return &cp, s.commitLocked("job.upsert", cp) -} - -func (s *Store) CompleteJob(id, worker string, result json.RawMessage, jobErr string) (*core.Job, error) { - s.mu.Lock() - defer s.mu.Unlock() - j, ok := s.state.Jobs[id] - if !ok { - return nil, errors.New("job not found") - } - if j.ClaimedBy != worker { - return nil, errors.New("job claimed by another worker") - } - j.Result = result - j.Error = jobErr - j.UpdatedAt = time.Now().UTC() - if jobErr != "" { - j.Status = "failed" - } else { - j.Status = "done" - } - cp := *j - return &cp, s.commitLocked("job.upsert", cp) -} - func (s *Store) DeleteMemory(id string) error { s.mu.Lock() defer s.mu.Unlock() @@ -1421,6 +1367,7 @@ func (s *Store) DeleteMemory(id string) error { } for k, x := range s.state.Synapses { if x.A == id || x.B == id { + s.unindexSynapseLocked(x) delete(s.state.Synapses, k) } } @@ -1459,6 +1406,9 @@ func (s *Store) validateConfigLocked(c core.Config) error { if c.Brain.MinSimilarity < -1 || c.Brain.MinSimilarity > 1 { return errors.New("brain.min_similarity must be -1..1") } + if c.Brain.GraphMaxHops < 1 || c.Brain.GraphMaxHops > 6 || c.Brain.GraphHopDecay <= 0 || c.Brain.GraphHopDecay > 1 || c.Brain.GraphMaxExpansion < 1 || c.Brain.GraphMaxExpansion > 5000 || c.Brain.GraphMinEdgeWeight < 0 || c.Brain.GraphMinEdgeWeight > c.Brain.MaxSynapseWeight { + return errors.New("invalid brain graph traversal configuration") + } if c.Brain.Index.Enabled { mode := indexMode(c) if c.Brain.Index.Mode != "" && c.Brain.Index.Mode != "hnsw" && c.Brain.Index.Mode != "hybrid" && c.Brain.Index.Mode != "disk-pq" { @@ -1656,6 +1606,18 @@ func (s *Store) validateConfigLocked(c core.Config) error { return fmt.Errorf("cluster.term %d cannot be lower than persisted term %d", c.Cluster.Term, s.state.Cluster.Term) } } + if c.Worker.LeaseSeconds < 10 || c.Worker.LeaseSeconds > 3600 || c.Worker.HeartbeatSeconds < 2 || c.Worker.HeartbeatSeconds >= c.Worker.LeaseSeconds || c.Worker.StaleAfterSeconds < c.Worker.HeartbeatSeconds || c.Worker.StaleAfterSeconds > 7200 { + return errors.New("invalid worker lease/heartbeat/stale timing") + } + if c.Worker.DefaultMaxAttempts < 1 || c.Worker.DefaultMaxAttempts > 20 || c.Worker.RetryBackoffSeconds < 1 || c.Worker.RetryBackoffSeconds > 3600 || c.Worker.MaxQueuedJobs < 16 || c.Worker.MaxQueuedJobs > 1000000 || c.Worker.MasterApplyMaxAttempts < 1 || c.Worker.MasterApplyMaxAttempts > 20 || c.Worker.MasterApplyBackoffSeconds < 1 || c.Worker.MasterApplyBackoffSeconds > 3600 || c.Worker.JobRetentionHours < 1 || c.Worker.JobRetentionHours > 8760 || c.Worker.MaxTerminalJobs < 100 || c.Worker.MaxTerminalJobs > 1000000 { + return errors.New("invalid worker retry/queue/retention configuration") + } + if c.Worker.GraphBackfillIntervalS < 2 || c.Worker.GraphBackfillIntervalS > 3600 || c.Worker.GraphBackfillBatchSize < 1 || c.Worker.GraphBackfillBatchSize > 4096 || c.Worker.GraphBackfillMaxQueued < 1 || c.Worker.GraphBackfillMaxQueued > c.Worker.MaxQueuedJobs || c.Worker.GraphBackfillMinDegree < 1 || c.Worker.GraphBackfillMinDegree > 100 || c.Worker.GraphCandidateMultiplier < 2 || c.Worker.GraphCandidateMultiplier > 64 || c.Worker.GraphRetryAfterMinutes < 1 || c.Worker.GraphRetryAfterMinutes > 43200 { + return errors.New("invalid worker graph backfill configuration") + } + if c.Worker.DistributedInferenceWaitS < 5 || c.Worker.DistributedInferenceWaitS > 3600 { + return errors.New("worker.distributed_inference_wait_seconds must be 5..3600") + } if c.OpenAI.DailyBudgetUSD < 0 || c.OpenAI.MonthlyBudgetUSD < 0 { return errors.New("budgets must be >= 0") } diff --git a/platform/neuroforge/internal/store/wal.go b/platform/neuroforge/internal/store/wal.go index 5e67496..afab3bc 100644 --- a/platform/neuroforge/internal/store/wal.go +++ b/platform/neuroforge/internal/store/wal.go @@ -209,6 +209,7 @@ func (s *Store) applyWALEvent(ev walEvent) error { return err } s.state.Synapses[edgeKey(syn.A, syn.B)] = &syn + s.indexSynapseLocked(&syn) case "synapse.replace": var items []core.Synapse if err := json.Unmarshal(ev.Data, &items); err != nil { @@ -219,6 +220,7 @@ func (s *Store) applyWALEvent(ev walEvent) error { x := items[i] s.state.Synapses[edgeKey(x.A, x.B)] = &x } + s.rebuildSynapseAdjLocked() case "usage.add": var x core.UsageEvent if err := json.Unmarshal(ev.Data, &x); err != nil { @@ -231,6 +233,14 @@ func (s *Store) applyWALEvent(ev walEvent) error { return err } s.state.Jobs[x.ID] = &x + case "job.delete": + var ids []string + if err := json.Unmarshal(ev.Data, &ids); err != nil { + return err + } + for _, id := range ids { + delete(s.state.Jobs, id) + } case "maintenance.set": return json.Unmarshal(ev.Data, &s.state.Maintenance) case "goal.upsert": diff --git a/platform/neuroforge/openapi.yaml b/platform/neuroforge/openapi.yaml index 031fc51..afbe818 100644 --- a/platform/neuroforge/openapi.yaml +++ b/platform/neuroforge/openapi.yaml @@ -16,6 +16,10 @@ components: WorkerKey: type: http scheme: bearer + ControlReadKey: + type: http + scheme: bearer + description: Scoped read token used by the Control Center for graph/orchestrator status. AdminToken: type: apiKey in: header @@ -1324,13 +1328,46 @@ paths: responses: '200': description: Open truth-key conflicts + /api/v1/integrations/graph/status: + get: + security: + - ControlReadKey: [] + - AppKey: [] + - AdminToken: [] + description: Structural knowledge-graph health and graph/backfill configuration. + responses: + '200': + description: Graph statistics including linked/isolated/multi-linked nodes and connected components. + /api/v1/integrations/orchestrator/status: + get: + security: + - ControlReadKey: [] + - AppKey: [] + - AdminToken: [] + description: Read-only master/subagent scheduler status, workers, queues and offload settings. + responses: + '200': {description: Orchestrator status} + /api/v1/worker/register: + post: + security: + - WorkerKey: [] + description: Register or refresh a capability-bearing CPU/GPU subagent. + responses: + '200': {description: Worker state} + /api/v1/worker/heartbeat: + post: + security: + - WorkerKey: [] + description: Refresh worker liveness and renew matching active leases using lease-token fencing. + responses: + '200': {description: Worker state} /api/v1/worker/claim: post: security: - WorkerKey: [] responses: '200': - description: Claimed CPU job + description: Highest-priority compatible leased job for this worker resource/capability set. '204': description: No job available /api/v1/worker/complete: @@ -1339,7 +1376,9 @@ paths: - WorkerKey: [] responses: '200': - description: Job completion accepted + description: Lease-fenced result accepted. Master-state mutations may enter durable apply_wait before done. + '409': + description: Stale/expired lease, wrong worker, or job no longer claimable. /internal/v1/cluster/request-vote: post: security: @@ -1554,6 +1593,54 @@ paths: responses: '200': description: Secrets updated + /admin/api/graph/status: + get: + security: [{AdminToken: []}] + responses: + '200': {description: Graph and orchestrator status with effective graph configuration} + /admin/api/graph/backfill: + post: + security: [{AdminToken: []}] + responses: + '202': {description: Bounded graph-backfill planning cycle accepted} + /admin/api/orchestrator/status: + get: + security: [{AdminToken: []}] + responses: + '200': {description: Master/subagent scheduler status} + /admin/api/orchestrator/jobs: + get: + security: [{AdminToken: []}] + parameters: + - {name: limit, in: query, schema: {type: integer, minimum: 1, maximum: 5000}} + - {name: status, in: query, schema: {type: string}} + - {name: type, in: query, schema: {type: string}} + responses: + '200': {description: Bounded job list without large payload/result bodies} + /admin/api/orchestrator/jobs/{id}/retry: + post: + security: [{AdminToken: []}] + parameters: + - {name: id, in: path, required: true, schema: {type: string}} + responses: + '200': {description: Failed/canceled job reset to queued} + '409': {description: Job is not retryable from its current state} + /admin/api/orchestrator/jobs/{id}/cancel: + post: + security: [{AdminToken: []}] + parameters: + - {name: id, in: path, required: true, schema: {type: string}} + requestBody: + required: false + content: + application/json: + schema: + type: object + properties: + reason: {type: string} + responses: + '200': {description: Non-terminal job canceled and any active lease fenced} + '404': {description: Job not found} /admin/api/provider-health: post: security: diff --git a/scripts/backup-data.sh b/scripts/backup-data.sh index 8775933..9b6f5a5 100644 --- a/scripts/backup-data.sh +++ b/scripts/backup-data.sh @@ -10,11 +10,11 @@ dest="$DEST_ROOT/$stamp" mkdir -p "$dest" ./scripts/preflight.sh -restart() { docker compose --env-file "$ENV_FILE" up -d knowledge neuroforge neuroforge-worker agent >/dev/null 2>&1 || true; } +restart() { docker compose --env-file "$ENV_FILE" up -d knowledge neuroforge neuroforge-worker-cpu neuroforge-worker-gpu agent >/dev/null 2>&1 || true; } trap restart EXIT INT TERM echo "backup: entering short maintenance stop" -docker compose --env-file "$ENV_FILE" stop neuroforge-worker agent neuroforge knowledge +docker compose --env-file "$ENV_FILE" stop neuroforge-worker-cpu neuroforge-worker-gpu agent neuroforge knowledge docker compose --env-file "$ENV_FILE" create neuroforge agent >/dev/null mkdir -p "$dest/neuroforge-data" "$dest/agent-data" "$dest/knowledge" "$dest/staging" "$dest/knowledge-backups" docker compose --env-file "$ENV_FILE" cp neuroforge:/app/data/. "$dest/neuroforge-data/" diff --git a/scripts/preflight.sh b/scripts/preflight.sh index 54f0c93..4dae0ef 100644 --- a/scripts/preflight.sh +++ b/scripts/preflight.sh @@ -16,6 +16,12 @@ if grep -Eq '^[[:space:]]+env_file:' docker-compose.yml; then fail "production d for image in neuroforge neuroforge-worker agent agent-data-init knowledge control; do grep -Fq "git.send.nrw/sendnrw/glpi-neuroforge-mega-${image}:\${IMAGE_TAG:" docker-compose.yml || fail "registry image mapping missing for ${image}" done +for service in neuroforge-worker-cpu neuroforge-worker-gpu; do + grep -Eq "^[[:space:]]{2}${service}:" docker-compose.yml || fail "production compose is missing ${service}" +done +[ -f docker-compose.subagent.yml ] || fail "docker-compose.subagent.yml is missing" +if grep -Eq '^[[:space:]]+build:|^[[:space:]]+env_file:' docker-compose.subagent.yml; then fail "remote subagent compose contains build/env_file"; fi +grep -Fq 'glpi-neuroforge-mega-neuroforge-worker:${IMAGE_TAG:' docker-compose.subagent.yml || fail "remote subagent image is not pinned by IMAGE_TAG" if [ "$STATIC_ONLY" = true ]; then if grep -Eq '^IMAGE_TAG[[:space:]]*=[[:space:]]*latest([[:space:]]|$)' .env.example; then fail ".env.example sets IMAGE_TAG=latest"; fi diff --git a/scripts/release-gate.sh b/scripts/release-gate.sh index e2c2cf9..7fb9b1c 100644 --- a/scripts/release-gate.sh +++ b/scripts/release-gate.sh @@ -5,6 +5,7 @@ cd "$ROOT" ./scripts/preflight.sh --static ./scripts/check-compose-env.py ./scripts/secret-scan.sh +make engineering-graph-check for module in platform/neuroforge services/agent services/knowledge services/control; do echo "release-gate: $module: test" (cd "$module" && go test ./...) diff --git a/scripts/research-up.sh b/scripts/research-up.sh index 0403c8a..02843af 100644 --- a/scripts/research-up.sh +++ b/scripts/research-up.sh @@ -15,5 +15,5 @@ export SEARXNG_SECRET=$secret cd "$ROOT" NEUROFORGE_RESEARCH_ENABLED=true \ NEUROFORGE_SEARXNG_ENABLED=true \ -docker compose --profile research up -d ollama knowledge searxng neuroforge neuroforge-worker +docker compose --profile research up -d ollama knowledge searxng neuroforge neuroforge-worker-cpu neuroforge-worker-gpu printf '%s\n' 'SearXNG + NeuroForge research are running. Autonomy remains controlled by NEUROFORGE_AUTONOMY_ENABLED.' diff --git a/scripts/restore-data.sh b/scripts/restore-data.sh index 8038c6e..da1b3cb 100644 --- a/scripts/restore-data.sh +++ b/scripts/restore-data.sh @@ -9,10 +9,10 @@ HELPER_IMAGE=${BACKUP_HELPER_IMAGE:-busybox:1.36} ./scripts/preflight.sh getv() { sed -n "s/^$1=//p" "$ENV_FILE" | tail -n1 | tr -d '\r'; } -restart() { docker compose --env-file "$ENV_FILE" up -d knowledge neuroforge neuroforge-worker agent >/dev/null 2>&1 || true; } +restart() { docker compose --env-file "$ENV_FILE" up -d knowledge neuroforge neuroforge-worker-cpu neuroforge-worker-gpu agent >/dev/null 2>&1 || true; } trap restart EXIT INT TERM -docker compose --env-file "$ENV_FILE" stop neuroforge-worker agent neuroforge knowledge +docker compose --env-file "$ENV_FILE" stop neuroforge-worker-cpu neuroforge-worker-gpu agent neuroforge knowledge docker compose --env-file "$ENV_FILE" create neuroforge agent >/dev/null volume_for() { service=$1; destination=$2 diff --git a/services/agent/knowledge/01_active-directory.json b/services/agent/knowledge/01_active-directory.json index 7202608..9ae0117 100644 --- a/services/agent/knowledge/01_active-directory.json +++ b/services/agent/knowledge/01_active-directory.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-ACTIVE-DIRECTORY-SELECT", - "title": "Active Directory", - "text": "Auswahlziel: Server, Backend und Plattformen \u003e Active Directory. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn der zentrale Verzeichnisdienst, Domänencontroller, Replikation, Vertrauensstellung, LDAP-Funktion oder die Domäne als Plattform gestört oder zu ändern ist. Typische Ticketformulierungen sind: „AD-Replikation fehlerhaft“; „Domänencontroller nicht erreichbar“; „LDAP-Abfrage schlägt fehl“; „Domänendienst gestört“. Nicht auswählen, wenn nur ein Benutzerkonto angelegt, ein Kennwort zurückgesetzt oder eine einzelne Gruppenmitgliedschaft geändert werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Einzelne Benutzer- und Gruppenaufträge gehören in die entsprechenden Kategorien unter Benutzerkonten und Berechtigungen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Active Directory“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Server, Backend und Plattformen \u003e Active Directory" - ], - "keywords": [ - "Active Directory", - "AD", - "Domänencontroller", - "Domain Controller", - "LDAP", - "Replikation", - "Domäne", - "Verzeichnisdienst", - "Server, Backend und Plattformen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/active-directory", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-ACTIVE-DIRECTORY-SELECT", + "title": "Active Directory", + "text": "Auswahlziel: Server, Backend und Plattformen \u003e Active Directory. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn der zentrale Verzeichnisdienst, Domänencontroller, Replikation, Vertrauensstellung, LDAP-Funktion oder die Domäne als Plattform gestört oder zu ändern ist. Typische Ticketformulierungen sind: „AD-Replikation fehlerhaft“; „Domänencontroller nicht erreichbar“; „LDAP-Abfrage schlägt fehl“; „Domänendienst gestört“. Nicht auswählen, wenn nur ein Benutzerkonto angelegt, ein Kennwort zurückgesetzt oder eine einzelne Gruppenmitgliedschaft geändert werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Einzelne Benutzer- und Gruppenaufträge gehören in die entsprechenden Kategorien unter Benutzerkonten und Berechtigungen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Active Directory“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Server, Backend und Plattformen \u003e Active Directory" + ], + "keywords": [ + "Active Directory", + "AD", + "Domänencontroller", + "Domain Controller", + "LDAP", + "Replikation", + "Domäne", + "Verzeichnisdienst", + "Server, Backend und Plattformen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/active-directory", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/01_arbeitsplatzdrucker.json b/services/agent/knowledge/01_arbeitsplatzdrucker.json index d4b6463..5136857 100644 --- a/services/agent/knowledge/01_arbeitsplatzdrucker.json +++ b/services/agent/knowledge/01_arbeitsplatzdrucker.json @@ -1,25 +1,25 @@ -{ - "id": "KAT-ARBEITSPLATZDRUCKER-SELECT", - "title": "Arbeitsplatzdrucker", - "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Arbeitsplatzdrucker. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein direkt einem Arbeitsplatz zugeordneter Drucker nicht druckt, Papierstau, schlechte Druckqualität, lokale Verbindung oder einen Gerätefehler zeigt. Typische Ticketformulierungen sind: „Lokaler Drucker druckt nicht“; „Papierstau am Arbeitsplatzdrucker“; „Druck blass“; „USB-Drucker wird nicht erkannt“. Nicht auswählen, wenn ein zentraler Netzwerkdrucker, Multifunktionsgerät oder Druckserver betroffen ist oder ein neues Gerät beschafft werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Verbrauchsmaterial und Beschaffung werden bei Bedarf separat zugeordnet.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Arbeitsplatzdrucker“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Drucken, Scannen und Kopieren \u003e Arbeitsplatzdrucker" - ], - "keywords": [ - "Arbeitsplatzdrucker", - "lokaler Drucker", - "Papierstau", - "Druckqualität", - "USB-Drucker", - "druckt nicht", - "Toner", - "Drucken, Scannen und Kopieren" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/arbeitsplatzdrucker", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-ARBEITSPLATZDRUCKER-SELECT", + "title": "Arbeitsplatzdrucker", + "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Arbeitsplatzdrucker. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein direkt einem Arbeitsplatz zugeordneter Drucker nicht druckt, Papierstau, schlechte Druckqualität, lokale Verbindung oder einen Gerätefehler zeigt. Typische Ticketformulierungen sind: „Lokaler Drucker druckt nicht“; „Papierstau am Arbeitsplatzdrucker“; „Druck blass“; „USB-Drucker wird nicht erkannt“. Nicht auswählen, wenn ein zentraler Netzwerkdrucker, Multifunktionsgerät oder Druckserver betroffen ist oder ein neues Gerät beschafft werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Verbrauchsmaterial und Beschaffung werden bei Bedarf separat zugeordnet.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Arbeitsplatzdrucker“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Drucken, Scannen und Kopieren \u003e Arbeitsplatzdrucker" + ], + "keywords": [ + "Arbeitsplatzdrucker", + "lokaler Drucker", + "Papierstau", + "Druckqualität", + "USB-Drucker", + "druckt nicht", + "Toner", + "Drucken, Scannen und Kopieren" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/arbeitsplatzdrucker", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/01_fachanwendung-storung.json b/services/agent/knowledge/01_fachanwendung-storung.json index c5b619a..2f961d6 100644 --- a/services/agent/knowledge/01_fachanwendung-storung.json +++ b/services/agent/knowledge/01_fachanwendung-storung.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-FACHANWENDUNG-STORUNG-SELECT", - "title": "Fachanwendung – Störung", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Störung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn eine konkrete Fachanwendung eine Fehlermeldung zeigt, eine fachliche Funktion nicht arbeitet, Masken nicht laden, Verarbeitungsschritte abbrechen oder einzelne Module nicht verfügbar sind. Typische Ticketformulierungen sind: „Fachverfahren zeigt Fehler“; „Buchung kann nicht abgeschlossen werden“; „Maske bleibt leer“; „Modul startet nicht“. Nicht auswählen, wenn das Problem ausschließlich durch Netzwerk, Serverplattform, Datenbankplattform oder das zentrale Benutzerkonto verursacht wird; wenn es sich nur um eine Bedienungsfrage oder neue Anforderung handelt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Die konkrete Anwendung, Fehlermeldung, betroffene Funktion und Anzahl der Betroffenen sind entscheidend.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Störung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Störung" - ], - "keywords": [ - "Fachanwendung", - "Fachverfahren", - "Fehlermeldung", - "Störung", - "Modul", - "Maske", - "Verarbeitung abgebrochen", - "Anwendungsfehler", - "funktioniert nicht", - "Fachanwendung – Störung", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-storung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-FACHANWENDUNG-STORUNG-SELECT", + "title": "Fachanwendung – Störung", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Störung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn eine konkrete Fachanwendung eine Fehlermeldung zeigt, eine fachliche Funktion nicht arbeitet, Masken nicht laden, Verarbeitungsschritte abbrechen oder einzelne Module nicht verfügbar sind. Typische Ticketformulierungen sind: „Fachverfahren zeigt Fehler“; „Buchung kann nicht abgeschlossen werden“; „Maske bleibt leer“; „Modul startet nicht“. Nicht auswählen, wenn das Problem ausschließlich durch Netzwerk, Serverplattform, Datenbankplattform oder das zentrale Benutzerkonto verursacht wird; wenn es sich nur um eine Bedienungsfrage oder neue Anforderung handelt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Die konkrete Anwendung, Fehlermeldung, betroffene Funktion und Anzahl der Betroffenen sind entscheidend.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Störung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Störung" + ], + "keywords": [ + "Fachanwendung", + "Fachverfahren", + "Fehlermeldung", + "Störung", + "Modul", + "Maske", + "Verarbeitung abgebrochen", + "Anwendungsfehler", + "funktioniert nicht", + "Fachanwendung – Störung", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-storung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/01_festnetztelefon.json b/services/agent/knowledge/01_festnetztelefon.json index 84fa100..e13963c 100644 --- a/services/agent/knowledge/01_festnetztelefon.json +++ b/services/agent/knowledge/01_festnetztelefon.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-FESTNETZTELEFON-SELECT", - "title": "Festnetztelefon", - "text": "Auswahlziel: Telefonie und Kommunikation \u003e Festnetztelefon. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Tischtelefon oder Festnetzanschluss nicht klingelt, keine Gespräche ermöglicht, Tonprobleme zeigt, defekt ist oder lokal eingerichtet werden muss. Typische Ticketformulierungen sind: „Telefon hat keinen Wählton“; „Tischtelefon defekt“; „Anrufer nicht hörbar“; „Festnetztelefon startet nicht“. Nicht auswählen, wenn eine Rufnummer neu vergeben, eine Rufgruppe geändert oder ein Mobilfunkgerät betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei flächigem Telefonieausfall ist eine zentrale Störung zu prüfen und höher zu priorisieren.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Festnetztelefon“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Telefonie und Kommunikation \u003e Festnetztelefon" - ], - "keywords": [ - "Festnetz", - "Telefon", - "Tischtelefon", - "Hörer", - "Wählton", - "Telefonapparat", - "kein Ton", - "Telefon defekt", - "Festnetztelefon", - "Telefonie und Kommunikation" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/festnetztelefon", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-FESTNETZTELEFON-SELECT", + "title": "Festnetztelefon", + "text": "Auswahlziel: Telefonie und Kommunikation \u003e Festnetztelefon. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Tischtelefon oder Festnetzanschluss nicht klingelt, keine Gespräche ermöglicht, Tonprobleme zeigt, defekt ist oder lokal eingerichtet werden muss. Typische Ticketformulierungen sind: „Telefon hat keinen Wählton“; „Tischtelefon defekt“; „Anrufer nicht hörbar“; „Festnetztelefon startet nicht“. Nicht auswählen, wenn eine Rufnummer neu vergeben, eine Rufgruppe geändert oder ein Mobilfunkgerät betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei flächigem Telefonieausfall ist eine zentrale Störung zu prüfen und höher zu priorisieren.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Festnetztelefon“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Telefonie und Kommunikation \u003e Festnetztelefon" + ], + "keywords": [ + "Festnetz", + "Telefon", + "Tischtelefon", + "Hörer", + "Wählton", + "Telefonapparat", + "kein Ton", + "Telefon defekt", + "Festnetztelefon", + "Telefonie und Kommunikation" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/festnetztelefon", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/01_hardwarebeschaffung.json b/services/agent/knowledge/01_hardwarebeschaffung.json index c820cff..0de5b50 100644 --- a/services/agent/knowledge/01_hardwarebeschaffung.json +++ b/services/agent/knowledge/01_hardwarebeschaffung.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-HARDWAREBESCHAFFUNG-SELECT", - "title": "Hardwarebeschaffung", - "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Hardwarebeschaffung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn Hardware außerhalb eines einfachen Support-Ersatzes beschafft werden soll, etwa Server, Netzwerkkomponenten, Arbeitsplatzgeräte, Spezialhardware oder größere Stückzahlen. Typische Ticketformulierungen sind: „Serverhardware bestellen“; „Switches beschaffen“; „Spezialscanner kaufen“; „Rahmenbestellung für Notebooks“. Nicht auswählen, wenn ein vorhandenes Gerät nur repariert, umgesetzt oder zurückgegeben wird; für konkrete Arbeitsplatz-Neubeschaffung kann auch die spezialisierte Kategorie unter Arbeitsplatz genutzt werden. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Das jeweilige Fachteam liefert Spezifikation und Bedarf; Beschaffung führt kaufmännischen Prozess.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Hardwarebeschaffung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Beschaffung, Verträge und Lizenzen \u003e Hardwarebeschaffung" - ], - "keywords": [ - "Hardwarebeschaffung", - "Hardware bestellen", - "Kauf", - "Angebot", - "Server kaufen", - "Switch beschaffen", - "Geräte bestellen", - "Investition", - "Beschaffung, Verträge und Lizenzen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/hardwarebeschaffung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-HARDWAREBESCHAFFUNG-SELECT", + "title": "Hardwarebeschaffung", + "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Hardwarebeschaffung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn Hardware außerhalb eines einfachen Support-Ersatzes beschafft werden soll, etwa Server, Netzwerkkomponenten, Arbeitsplatzgeräte, Spezialhardware oder größere Stückzahlen. Typische Ticketformulierungen sind: „Serverhardware bestellen“; „Switches beschaffen“; „Spezialscanner kaufen“; „Rahmenbestellung für Notebooks“. Nicht auswählen, wenn ein vorhandenes Gerät nur repariert, umgesetzt oder zurückgegeben wird; für konkrete Arbeitsplatz-Neubeschaffung kann auch die spezialisierte Kategorie unter Arbeitsplatz genutzt werden. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Das jeweilige Fachteam liefert Spezifikation und Bedarf; Beschaffung führt kaufmännischen Prozess.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Hardwarebeschaffung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Beschaffung, Verträge und Lizenzen \u003e Hardwarebeschaffung" + ], + "keywords": [ + "Hardwarebeschaffung", + "Hardware bestellen", + "Kauf", + "Angebot", + "Server kaufen", + "Switch beschaffen", + "Geräte bestellen", + "Investition", + "Beschaffung, Verträge und Lizenzen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/hardwarebeschaffung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/01_kennwort-zurucksetzen.json b/services/agent/knowledge/01_kennwort-zurucksetzen.json index 33f28de..0e9e8c2 100644 --- a/services/agent/knowledge/01_kennwort-zurucksetzen.json +++ b/services/agent/knowledge/01_kennwort-zurucksetzen.json @@ -1,29 +1,29 @@ -{ - "id": "KAT-KENNWORT-ZURUCKSETZEN-SELECT", - "title": "Kennwort zurücksetzen", - "text": "Auswahlziel: Benutzerkonten und Berechtigungen \u003e Kennwort zurücksetzen. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Benutzer sein Kennwort vergessen hat, das Kennwort abgelaufen ist, eine Anmeldung wegen falscher Kennworteingaben scheitert oder das persönliche Domänenkonto gesperrt wurde. Auch typische Folgeprobleme nach einer Kennwortänderung, etwa gespeicherte alte Kennwörter auf weiteren Geräten, gehören hierher. Typische Ticketformulierungen sind: „Kennwort vergessen“; „Passwort abgelaufen“; „Konto gesperrt“; „Account locked“; „Anmeldung funktioniert nach Kennwortänderung nicht“. Nicht auswählen, wenn ein Konto neu angelegt, umbenannt oder gelöscht werden soll; wenn Rollen in einer Fachanwendung fehlen; wenn eine technische Störung des Active Directory mehrere Benutzer betrifft. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Der Support prüft Identität, betroffenen Dienst und mögliche Altkennwörter. Zentrale AD-Störungen werden an Infrastruktur und Backend übergeben.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Benutzerkonten und Berechtigungen \u003e Kennwort zurücksetzen“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Benutzerkonten und Berechtigungen \u003e Kennwort zurücksetzen" - ], - "keywords": [ - "Kennwort", - "Passwort", - "Kennwort zurücksetzen", - "Passwort vergessen", - "Konto gesperrt", - "Account locked", - "Login", - "Anmeldung", - "Domänenkonto", - "AD-Konto", - "falsches Kennwort", - "Benutzerkonten und Berechtigungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/benutzerkonten-und-berechtigungen/kennwort-zurucksetzen", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-KENNWORT-ZURUCKSETZEN-SELECT", + "title": "Kennwort zurücksetzen", + "text": "Auswahlziel: Benutzerkonten und Berechtigungen \u003e Kennwort zurücksetzen. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Benutzer sein Kennwort vergessen hat, das Kennwort abgelaufen ist, eine Anmeldung wegen falscher Kennworteingaben scheitert oder das persönliche Domänenkonto gesperrt wurde. Auch typische Folgeprobleme nach einer Kennwortänderung, etwa gespeicherte alte Kennwörter auf weiteren Geräten, gehören hierher. Typische Ticketformulierungen sind: „Kennwort vergessen“; „Passwort abgelaufen“; „Konto gesperrt“; „Account locked“; „Anmeldung funktioniert nach Kennwortänderung nicht“. Nicht auswählen, wenn ein Konto neu angelegt, umbenannt oder gelöscht werden soll; wenn Rollen in einer Fachanwendung fehlen; wenn eine technische Störung des Active Directory mehrere Benutzer betrifft. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Der Support prüft Identität, betroffenen Dienst und mögliche Altkennwörter. Zentrale AD-Störungen werden an Infrastruktur und Backend übergeben.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Benutzerkonten und Berechtigungen \u003e Kennwort zurücksetzen“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Benutzerkonten und Berechtigungen \u003e Kennwort zurücksetzen" + ], + "keywords": [ + "Kennwort", + "Passwort", + "Kennwort zurücksetzen", + "Passwort vergessen", + "Konto gesperrt", + "Account locked", + "Login", + "Anmeldung", + "Domänenkonto", + "AD-Konto", + "falsches Kennwort", + "Benutzerkonten und Berechtigungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/benutzerkonten-und-berechtigungen/kennwort-zurucksetzen", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/01_lan.json b/services/agent/knowledge/01_lan.json index 27c1c32..e479cae 100644 --- a/services/agent/knowledge/01_lan.json +++ b/services/agent/knowledge/01_lan.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-LAN-SELECT", - "title": "LAN", - "text": "Auswahlziel: Netzwerk und Verbindungen \u003e LAN. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn eine kabelgebundene Netzwerkverbindung, Netzwerkdose, Patchung oder Ethernet-Verbindung nicht funktioniert, instabil ist oder neu bereitgestellt werden soll. Typische Ticketformulierungen sind: „Netzwerkdose ohne Verbindung“; „LAN bricht ab“; „Kein Netzwerk über Kabel“; „Neue Dose patchen“. Nicht auswählen, wenn ausschließlich WLAN, VPN, Internetzugang oder ein einzelner defekter Dockingadapter betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei nur einem Arbeitsplatz prüft der Support zunächst Kabel, Dock und Gerät; zentrale Komponenten liegen bei Infrastruktur.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e LAN“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Netzwerk und Verbindungen \u003e LAN" - ], - "keywords": [ - "LAN", - "Ethernet", - "Netzwerkdose", - "Netzwerkkabel", - "Patchen", - "kabelgebunden", - "kein Netzwerk", - "Switchport", - "Netzwerk und Verbindungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/lan", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-LAN-SELECT", + "title": "LAN", + "text": "Auswahlziel: Netzwerk und Verbindungen \u003e LAN. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn eine kabelgebundene Netzwerkverbindung, Netzwerkdose, Patchung oder Ethernet-Verbindung nicht funktioniert, instabil ist oder neu bereitgestellt werden soll. Typische Ticketformulierungen sind: „Netzwerkdose ohne Verbindung“; „LAN bricht ab“; „Kein Netzwerk über Kabel“; „Neue Dose patchen“. Nicht auswählen, wenn ausschließlich WLAN, VPN, Internetzugang oder ein einzelner defekter Dockingadapter betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei nur einem Arbeitsplatz prüft der Support zunächst Kabel, Dock und Gerät; zentrale Komponenten liegen bei Infrastruktur.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e LAN“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Netzwerk und Verbindungen \u003e LAN" + ], + "keywords": [ + "LAN", + "Ethernet", + "Netzwerkdose", + "Netzwerkkabel", + "Patchen", + "kabelgebunden", + "kein Netzwerk", + "Switchport", + "Netzwerk und Verbindungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/lan", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/01_neue-it-anforderung.json b/services/agent/knowledge/01_neue-it-anforderung.json index 0ae5d63..9277582 100644 --- a/services/agent/knowledge/01_neue-it-anforderung.json +++ b/services/agent/knowledge/01_neue-it-anforderung.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-NEUE-IT-ANFORDERUNG-SELECT", - "title": "Neue IT-Anforderung", - "text": "Auswahlziel: Projekte, Änderungen und neue Anforderungen \u003e Neue IT-Anforderung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn ein bislang nicht vorhandener IT-Service, eine neue technische Fähigkeit oder ein organisationsübergreifender Bedarf zunächst bewertet, priorisiert und einem Fachteam zugeordnet werden soll. Typische Ticketformulierungen sind: „Neuen digitalen Dienst prüfen“; „Zusätzlichen IT-Service bereitstellen“; „Neue technische Lösung benötigt“; „Unklarer neuer IT-Bedarf“. Nicht auswählen, wenn die Lösung bereits eindeutig eine bestehende Fachanwendung betrifft, nur Hardware bestellt oder eine normale Störung gemeldet wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Diese Kategorie dient der qualifizierten Erstbewertung; danach erfolgt Übergabe an das zuständige Fachteam oder Projekt.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Projekte, Änderungen und neue Anforderungen \u003e Neue IT-Anforderung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Projekte, Änderungen und neue Anforderungen \u003e Neue IT-Anforderung" - ], - "keywords": [ - "neue IT-Anforderung", - "neuer Service", - "neue Lösung", - "Bedarf", - "Anforderung", - "Idee", - "Digitalisierung", - "Prüfauftrag", - "Neue IT-Anforderung", - "Projekte, Änderungen und neue Anforderungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/projekte-anderungen-und-neue-anforderungen/neue-it-anforderung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-NEUE-IT-ANFORDERUNG-SELECT", + "title": "Neue IT-Anforderung", + "text": "Auswahlziel: Projekte, Änderungen und neue Anforderungen \u003e Neue IT-Anforderung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn ein bislang nicht vorhandener IT-Service, eine neue technische Fähigkeit oder ein organisationsübergreifender Bedarf zunächst bewertet, priorisiert und einem Fachteam zugeordnet werden soll. Typische Ticketformulierungen sind: „Neuen digitalen Dienst prüfen“; „Zusätzlichen IT-Service bereitstellen“; „Neue technische Lösung benötigt“; „Unklarer neuer IT-Bedarf“. Nicht auswählen, wenn die Lösung bereits eindeutig eine bestehende Fachanwendung betrifft, nur Hardware bestellt oder eine normale Störung gemeldet wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Diese Kategorie dient der qualifizierten Erstbewertung; danach erfolgt Übergabe an das zuständige Fachteam oder Projekt.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Projekte, Änderungen und neue Anforderungen \u003e Neue IT-Anforderung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Projekte, Änderungen und neue Anforderungen \u003e Neue IT-Anforderung" + ], + "keywords": [ + "neue IT-Anforderung", + "neuer Service", + "neue Lösung", + "Bedarf", + "Anforderung", + "Idee", + "Digitalisierung", + "Prüfauftrag", + "Neue IT-Anforderung", + "Projekte, Änderungen und neue Anforderungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/projekte-anderungen-und-neue-anforderungen/neue-it-anforderung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/01_padagogisches-netzwerk.json b/services/agent/knowledge/01_padagogisches-netzwerk.json index 4413951..bc8a4e6 100644 --- a/services/agent/knowledge/01_padagogisches-netzwerk.json +++ b/services/agent/knowledge/01_padagogisches-netzwerk.json @@ -1,25 +1,25 @@ -{ - "id": "KAT-PADAGOGISCHES-NETZWERK-SELECT", - "title": "Pädagogisches Netzwerk", - "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Pädagogisches Netzwerk. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn das pädagogische Netz einer Schule, seine Zugänge, Segmentierung, Internetnutzung oder schulbezogene Netzwerkdienste betroffen sind. Typische Ticketformulierungen sind: „Schülernetz nicht erreichbar“; „Pädagogisches WLAN gestört“; „Unterrichtsnetz ausgefallen“; „Zugang im pädagogischen Netz fehlt“. Nicht auswählen, wenn ausschließlich das Verwaltungsnetz, eine einzelne allgemeine Netzwerkdose oder die zentrale kommunale Standortanbindung betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Während der Übergangszeit primär Team Schulen; zentrale Infrastrukturursachen werden an Infrastruktur und Backend übergeben.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Pädagogisches Netzwerk“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Schulspezifische IT-Systeme \u003e Pädagogisches Netzwerk" - ], - "keywords": [ - "pädagogisches Netzwerk", - "Schülernetz", - "Unterrichtsnetz", - "pädagogisches WLAN", - "Schulnetz", - "Pädagogiknetz", - "Pädagogisches Netzwerk", - "Schulspezifische IT-Systeme" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/padagogisches-netzwerk", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-PADAGOGISCHES-NETZWERK-SELECT", + "title": "Pädagogisches Netzwerk", + "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Pädagogisches Netzwerk. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn das pädagogische Netz einer Schule, seine Zugänge, Segmentierung, Internetnutzung oder schulbezogene Netzwerkdienste betroffen sind. Typische Ticketformulierungen sind: „Schülernetz nicht erreichbar“; „Pädagogisches WLAN gestört“; „Unterrichtsnetz ausgefallen“; „Zugang im pädagogischen Netz fehlt“. Nicht auswählen, wenn ausschließlich das Verwaltungsnetz, eine einzelne allgemeine Netzwerkdose oder die zentrale kommunale Standortanbindung betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Während der Übergangszeit primär Team Schulen; zentrale Infrastrukturursachen werden an Infrastruktur und Backend übergeben.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Pädagogisches Netzwerk“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Schulspezifische IT-Systeme \u003e Pädagogisches Netzwerk" + ], + "keywords": [ + "pädagogisches Netzwerk", + "Schülernetz", + "Unterrichtsnetz", + "pädagogisches WLAN", + "Schulnetz", + "Pädagogiknetz", + "Pädagogisches Netzwerk", + "Schulspezifische IT-Systeme" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/padagogisches-netzwerk", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/01_pc-und-notebook.json b/services/agent/knowledge/01_pc-und-notebook.json index 5aa4646..e2a66be 100644 --- a/services/agent/knowledge/01_pc-und-notebook.json +++ b/services/agent/knowledge/01_pc-und-notebook.json @@ -1,30 +1,30 @@ -{ - "id": "KAT-PC-UND-NOTEBOOK-SELECT", - "title": "PC und Notebook", - "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e PC und Notebook. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein einzelner Arbeitsplatz-PC oder ein dienstliches Notebook nicht startet, abstürzt, sehr langsam ist, ungewöhnliche Geräusche macht, einen Hardwaredefekt zeigt oder lokal nicht nutzbar ist. Typische Ticketformulierungen sind: „Notebook startet nicht“; „PC friert ein“; „Laptop-Akku defekt“; „Arbeitsplatzrechner sehr langsam“; „Gerät zeigt Bluescreen“. Nicht auswählen, wenn mehrere Geräte gleichzeitig betroffen sind, ein zentraler Dienst ausfällt, ein neues Gerät beschafft werden soll oder ausschließlich Monitor, Dockingstation oder Zubehör betroffen sind. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei mehreren gleichzeitig betroffenen Geräten ist ein zentraler Infrastruktur- oder Sicherheitsbezug zu prüfen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e PC und Notebook“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Arbeitsplatz und Endgeräte \u003e PC und Notebook" - ], - "keywords": [ - "PC", - "Computer", - "Notebook", - "Laptop", - "Arbeitsplatzrechner", - "startet nicht", - "Absturz", - "Bluescreen", - "langsam", - "Akku", - "Hardwaredefekt", - "PC und Notebook", - "Arbeitsplatz und Endgeräte" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/pc-und-notebook", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-PC-UND-NOTEBOOK-SELECT", + "title": "PC und Notebook", + "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e PC und Notebook. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein einzelner Arbeitsplatz-PC oder ein dienstliches Notebook nicht startet, abstürzt, sehr langsam ist, ungewöhnliche Geräusche macht, einen Hardwaredefekt zeigt oder lokal nicht nutzbar ist. Typische Ticketformulierungen sind: „Notebook startet nicht“; „PC friert ein“; „Laptop-Akku defekt“; „Arbeitsplatzrechner sehr langsam“; „Gerät zeigt Bluescreen“. Nicht auswählen, wenn mehrere Geräte gleichzeitig betroffen sind, ein zentraler Dienst ausfällt, ein neues Gerät beschafft werden soll oder ausschließlich Monitor, Dockingstation oder Zubehör betroffen sind. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei mehreren gleichzeitig betroffenen Geräten ist ein zentraler Infrastruktur- oder Sicherheitsbezug zu prüfen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e PC und Notebook“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Arbeitsplatz und Endgeräte \u003e PC und Notebook" + ], + "keywords": [ + "PC", + "Computer", + "Notebook", + "Laptop", + "Arbeitsplatzrechner", + "startet nicht", + "Absturz", + "Bluescreen", + "langsam", + "Akku", + "Hardwaredefekt", + "PC und Notebook", + "Arbeitsplatz und Endgeräte" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/pc-und-notebook", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/01_sonstiges-und-unklare-zuordnung.json b/services/agent/knowledge/01_sonstiges-und-unklare-zuordnung.json index 632b608..e4f3e5e 100644 --- a/services/agent/knowledge/01_sonstiges-und-unklare-zuordnung.json +++ b/services/agent/knowledge/01_sonstiges-und-unklare-zuordnung.json @@ -1,24 +1,24 @@ -{ - "id": "KAT-SONSTIGES-UND-UNKLARE-ZUORDNUNG-SELECT", - "title": "Sonstiges und unklare Zuordnung", - "text": "Auswahlziel: Sonstiges und unklare Zuordnung \u003e Sonstiges und unklare Zuordnung. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, nur wenn das Ticket trotz ausreichender Beschreibung keiner vorhandenen Kategorie zuverlässig zugeordnet werden kann, mehrere völlig unterschiedliche Anliegen untrennbar vermischt oder der betroffene IT-Service nicht erkennbar ist. Typische Ticketformulierungen sind: „Unklarer IT-Fehler ohne erkennbaren Dienst“; „Mehrere nicht trennbare Anliegen“; „Betroffenes System nicht identifizierbar“. Nicht auswählen, wenn anhand von Anwendung, Gerät, Fehlermeldung, Standort oder gewünschter Leistung eine spezifische Kategorie gewählt werden kann. Diese Kategorie darf nicht allein wegen kurzer oder unvollständiger Formulierung bevorzugt werden. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Diese Auffangkategorie soll selten verwendet und regelmäßig ausgewertet werden. Vor Auswahl sind Hauptbegriffe und Kontext gegen alle spezifischen Kategorien zu prüfen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Sonstiges und unklare Zuordnung \u003e Sonstiges und unklare Zuordnung“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Sonstiges und unklare Zuordnung \u003e Sonstiges und unklare Zuordnung" - ], - "keywords": [ - "sonstiges", - "unklar", - "keine Zuordnung", - "allgemeines IT-Problem", - "nicht näher beschrieben", - "divers", - "Sonstiges und unklare Zuordnung" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/sonstiges-und-unklare-zuordnung/sonstiges-und-unklare-zuordnung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-SONSTIGES-UND-UNKLARE-ZUORDNUNG-SELECT", + "title": "Sonstiges und unklare Zuordnung", + "text": "Auswahlziel: Sonstiges und unklare Zuordnung \u003e Sonstiges und unklare Zuordnung. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, nur wenn das Ticket trotz ausreichender Beschreibung keiner vorhandenen Kategorie zuverlässig zugeordnet werden kann, mehrere völlig unterschiedliche Anliegen untrennbar vermischt oder der betroffene IT-Service nicht erkennbar ist. Typische Ticketformulierungen sind: „Unklarer IT-Fehler ohne erkennbaren Dienst“; „Mehrere nicht trennbare Anliegen“; „Betroffenes System nicht identifizierbar“. Nicht auswählen, wenn anhand von Anwendung, Gerät, Fehlermeldung, Standort oder gewünschter Leistung eine spezifische Kategorie gewählt werden kann. Diese Kategorie darf nicht allein wegen kurzer oder unvollständiger Formulierung bevorzugt werden. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Diese Auffangkategorie soll selten verwendet und regelmäßig ausgewertet werden. Vor Auswahl sind Hauptbegriffe und Kontext gegen alle spezifischen Kategorien zu prüfen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Sonstiges und unklare Zuordnung \u003e Sonstiges und unklare Zuordnung“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Sonstiges und unklare Zuordnung \u003e Sonstiges und unklare Zuordnung" + ], + "keywords": [ + "sonstiges", + "unklar", + "keine Zuordnung", + "allgemeines IT-Problem", + "nicht näher beschrieben", + "divers", + "Sonstiges und unklare Zuordnung" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/sonstiges-und-unklare-zuordnung/sonstiges-und-unklare-zuordnung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/01_verdachtige-e-mail-und-phishing.json b/services/agent/knowledge/01_verdachtige-e-mail-und-phishing.json index fbdf56c..8bf3cf2 100644 --- a/services/agent/knowledge/01_verdachtige-e-mail-und-phishing.json +++ b/services/agent/knowledge/01_verdachtige-e-mail-und-phishing.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-VERDACHTIGE-E-MAIL-UND-PHISHING-SELECT", - "title": "Verdächtige E-Mail und Phishing", - "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Verdächtige E-Mail und Phishing. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn eine E-Mail verdächtig wirkt, einen ungewöhnlichen Link oder Anhang enthält, Zugangsdaten abfragt, eine Zahlung fordert oder der Absender möglicherweise gefälscht ist. Typische Ticketformulierungen sind: „Verdächtige Rechnung per E-Mail“; „Link in Mail angeklickt“; „Absender scheint gefälscht“; „Passwortabfrage per Mail“. Nicht auswählen, wenn es sich nur um normalen Spam ohne Sicherheitsbezug, eine Outlook-Client-Störung oder eine bekannte legitime Nachricht handelt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei geklicktem Link, geöffnetem Anhang oder eingegebenen Zugangsdaten ist die Dringlichkeit zu erhöhen und gegebenenfalls Sicherheitsvorfall zu wählen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Verdächtige E-Mail und Phishing“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "IT-Sicherheit und Datensicherung \u003e Verdächtige E-Mail und Phishing" - ], - "keywords": [ - "Phishing", - "verdächtige E-Mail", - "Fake Mail", - "gefälschter Absender", - "verdächtiger Link", - "Anhang", - "Spam", - "Zugangsdaten", - "CEO-Fraud", - "Verdächtige E-Mail und Phishing", - "IT-Sicherheit und Datensicherung" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/verdachtige-e-mail-und-phishing", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-VERDACHTIGE-E-MAIL-UND-PHISHING-SELECT", + "title": "Verdächtige E-Mail und Phishing", + "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Verdächtige E-Mail und Phishing. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn eine E-Mail verdächtig wirkt, einen ungewöhnlichen Link oder Anhang enthält, Zugangsdaten abfragt, eine Zahlung fordert oder der Absender möglicherweise gefälscht ist. Typische Ticketformulierungen sind: „Verdächtige Rechnung per E-Mail“; „Link in Mail angeklickt“; „Absender scheint gefälscht“; „Passwortabfrage per Mail“. Nicht auswählen, wenn es sich nur um normalen Spam ohne Sicherheitsbezug, eine Outlook-Client-Störung oder eine bekannte legitime Nachricht handelt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei geklicktem Link, geöffnetem Anhang oder eingegebenen Zugangsdaten ist die Dringlichkeit zu erhöhen und gegebenenfalls Sicherheitsvorfall zu wählen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Verdächtige E-Mail und Phishing“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "IT-Sicherheit und Datensicherung \u003e Verdächtige E-Mail und Phishing" + ], + "keywords": [ + "Phishing", + "verdächtige E-Mail", + "Fake Mail", + "gefälschter Absender", + "verdächtiger Link", + "Anhang", + "Spam", + "Zugangsdaten", + "CEO-Fraud", + "Verdächtige E-Mail und Phishing", + "IT-Sicherheit und Datensicherung" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/verdachtige-e-mail-und-phishing", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/02_benutzerkonto-anlegen-andern-oder-loschen.json b/services/agent/knowledge/02_benutzerkonto-anlegen-andern-oder-loschen.json index bc2d5e5..7b1ba31 100644 --- a/services/agent/knowledge/02_benutzerkonto-anlegen-andern-oder-loschen.json +++ b/services/agent/knowledge/02_benutzerkonto-anlegen-andern-oder-loschen.json @@ -1,30 +1,30 @@ -{ - "id": "KAT-BENUTZERKONTO-ANLEGEN-ANDERN-ODER-LOSCHEN-SELECT", - "title": "Benutzerkonto anlegen, ändern oder löschen", - "text": "Auswahlziel: Benutzerkonten und Berechtigungen \u003e Benutzerkonto anlegen, ändern oder löschen. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn ein persönliches Benutzerkonto für Eintritt, Versetzung, Namensänderung, Organisationswechsel, längere Abwesenheit oder Austritt erstellt, angepasst, deaktiviert oder gelöscht werden muss. Dazu zählen technische Kontodaten im zentralen Verzeichnisdienst. Typische Ticketformulierungen sind: „Neuer Mitarbeiter benötigt ein Konto“; „Nachname hat sich geändert“; „Konto zum Austritt deaktivieren“; „Benutzer in andere Organisationseinheit verschieben“. Nicht auswählen, wenn nur ein Kennwort zurückgesetzt werden muss; wenn ausschließlich eine Rolle in einer Fachanwendung betroffen ist; wenn ein Funktionspostfach benötigt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Fachliche Berechtigungen werden nicht automatisch mit dieser Kategorie abgedeckt und müssen gegebenenfalls separat beantragt werden.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Benutzerkonten und Berechtigungen \u003e Benutzerkonto anlegen, ändern oder löschen“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Benutzerkonten und Berechtigungen \u003e Benutzerkonto anlegen, ändern oder löschen" - ], - "keywords": [ - "Benutzerkonto anlegen", - "Account erstellen", - "neuer Mitarbeiter", - "Eintritt", - "Austritt", - "Konto löschen", - "Konto deaktivieren", - "Namensänderung", - "Versetzung", - "Organisationseinheit", - "AD-Benutzer", - "Benutzerkonto anlegen, ändern oder löschen", - "Benutzerkonten und Berechtigungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/benutzerkonten-und-berechtigungen/benutzerkonto-anlegen-andern-oder-loschen", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-BENUTZERKONTO-ANLEGEN-ANDERN-ODER-LOSCHEN-SELECT", + "title": "Benutzerkonto anlegen, ändern oder löschen", + "text": "Auswahlziel: Benutzerkonten und Berechtigungen \u003e Benutzerkonto anlegen, ändern oder löschen. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn ein persönliches Benutzerkonto für Eintritt, Versetzung, Namensänderung, Organisationswechsel, längere Abwesenheit oder Austritt erstellt, angepasst, deaktiviert oder gelöscht werden muss. Dazu zählen technische Kontodaten im zentralen Verzeichnisdienst. Typische Ticketformulierungen sind: „Neuer Mitarbeiter benötigt ein Konto“; „Nachname hat sich geändert“; „Konto zum Austritt deaktivieren“; „Benutzer in andere Organisationseinheit verschieben“. Nicht auswählen, wenn nur ein Kennwort zurückgesetzt werden muss; wenn ausschließlich eine Rolle in einer Fachanwendung betroffen ist; wenn ein Funktionspostfach benötigt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Fachliche Berechtigungen werden nicht automatisch mit dieser Kategorie abgedeckt und müssen gegebenenfalls separat beantragt werden.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Benutzerkonten und Berechtigungen \u003e Benutzerkonto anlegen, ändern oder löschen“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Benutzerkonten und Berechtigungen \u003e Benutzerkonto anlegen, ändern oder löschen" + ], + "keywords": [ + "Benutzerkonto anlegen", + "Account erstellen", + "neuer Mitarbeiter", + "Eintritt", + "Austritt", + "Konto löschen", + "Konto deaktivieren", + "Namensänderung", + "Versetzung", + "Organisationseinheit", + "AD-Benutzer", + "Benutzerkonto anlegen, ändern oder löschen", + "Benutzerkonten und Berechtigungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/benutzerkonten-und-berechtigungen/benutzerkonto-anlegen-andern-oder-loschen", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/02_fachanwendung-bedienung-und-beratung.json b/services/agent/knowledge/02_fachanwendung-bedienung-und-beratung.json index 6ea66a4..ad1d0c4 100644 --- a/services/agent/knowledge/02_fachanwendung-bedienung-und-beratung.json +++ b/services/agent/knowledge/02_fachanwendung-bedienung-und-beratung.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-FACHANWENDUNG-BEDIENUNG-UND-BERATUNG-SELECT", - "title": "Fachanwendung – Bedienung und Beratung", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Bedienung und Beratung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn die Anwendung technisch funktioniert, aber Unterstützung bei Bedienung, Prozessschritten, Eingaben, fachlicher Nutzung oder Best-Practice benötigt wird. Typische Ticketformulierungen sind: „Wie erfasse ich einen Vorgang“; „Wo finde ich die Auswertung“; „Unterstützung bei Arbeitsschritt“; „Frage zur Bedienung des Fachverfahrens“. Nicht auswählen, wenn eine Fehlermeldung oder ein technischer Ausfall vorliegt; wenn eine neue Funktion entwickelt oder eine Berechtigung geändert werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Reine Standardfragen können im First-Level angenommen werden; fachliche Prozessberatung bleibt bei Fachanwendungen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Bedienung und Beratung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Bedienung und Beratung" - ], - "keywords": [ - "Bedienung", - "Anleitung", - "Wie kann ich", - "Wo finde ich", - "Beratung", - "Nutzung", - "Arbeitsschritt", - "Fachverfahren Hilfe", - "Anwenderfrage", - "Fachanwendung – Bedienung und Beratung", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-bedienung-und-beratung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-FACHANWENDUNG-BEDIENUNG-UND-BERATUNG-SELECT", + "title": "Fachanwendung – Bedienung und Beratung", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Bedienung und Beratung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn die Anwendung technisch funktioniert, aber Unterstützung bei Bedienung, Prozessschritten, Eingaben, fachlicher Nutzung oder Best-Practice benötigt wird. Typische Ticketformulierungen sind: „Wie erfasse ich einen Vorgang“; „Wo finde ich die Auswertung“; „Unterstützung bei Arbeitsschritt“; „Frage zur Bedienung des Fachverfahrens“. Nicht auswählen, wenn eine Fehlermeldung oder ein technischer Ausfall vorliegt; wenn eine neue Funktion entwickelt oder eine Berechtigung geändert werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Reine Standardfragen können im First-Level angenommen werden; fachliche Prozessberatung bleibt bei Fachanwendungen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Bedienung und Beratung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Bedienung und Beratung" + ], + "keywords": [ + "Bedienung", + "Anleitung", + "Wie kann ich", + "Wo finde ich", + "Beratung", + "Nutzung", + "Arbeitsschritt", + "Fachverfahren Hilfe", + "Anwenderfrage", + "Fachanwendung – Bedienung und Beratung", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-bedienung-und-beratung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/02_gruppenrichtlinien.json b/services/agent/knowledge/02_gruppenrichtlinien.json index a0b7ac2..43ef190 100644 --- a/services/agent/knowledge/02_gruppenrichtlinien.json +++ b/services/agent/knowledge/02_gruppenrichtlinien.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-GRUPPENRICHTLINIEN-SELECT", - "title": "Gruppenrichtlinien", - "text": "Auswahlziel: Server, Backend und Plattformen \u003e Gruppenrichtlinien. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn zentrale GPOs technisch erstellt, getestet, verteilt, analysiert oder korrigiert werden sollen und mehrere Systeme oder definierte Organisationseinheiten betreffen. Typische Ticketformulierungen sind: „Neue GPO verteilen“; „Richtlinie wird nicht übernommen“; „Zentrale Windows-Einstellung ändern“; „GPO-Fehler analysieren“. Nicht auswählen, wenn lediglich eine Gruppenmitgliedschaft geändert oder eine lokale Client-Einstellung repariert werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Die ähnliche Kategorie AD-Gruppen und Gruppenrichtlinien dient eher konkreten Benutzer-/Gruppenaufträgen; diese Kategorie dem Plattformbetrieb und größeren GPO-Arbeiten.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Gruppenrichtlinien“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Server, Backend und Plattformen \u003e Gruppenrichtlinien" - ], - "keywords": [ - "Gruppenrichtlinie", - "GPO", - "Group Policy", - "gpupdate", - "Richtlinie", - "OU", - "zentrale Einstellung", - "Policy", - "Gruppenrichtlinien", - "Server, Backend und Plattformen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/gruppenrichtlinien", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-GRUPPENRICHTLINIEN-SELECT", + "title": "Gruppenrichtlinien", + "text": "Auswahlziel: Server, Backend und Plattformen \u003e Gruppenrichtlinien. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn zentrale GPOs technisch erstellt, getestet, verteilt, analysiert oder korrigiert werden sollen und mehrere Systeme oder definierte Organisationseinheiten betreffen. Typische Ticketformulierungen sind: „Neue GPO verteilen“; „Richtlinie wird nicht übernommen“; „Zentrale Windows-Einstellung ändern“; „GPO-Fehler analysieren“. Nicht auswählen, wenn lediglich eine Gruppenmitgliedschaft geändert oder eine lokale Client-Einstellung repariert werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Die ähnliche Kategorie AD-Gruppen und Gruppenrichtlinien dient eher konkreten Benutzer-/Gruppenaufträgen; diese Kategorie dem Plattformbetrieb und größeren GPO-Arbeiten.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Gruppenrichtlinien“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Server, Backend und Plattformen \u003e Gruppenrichtlinien" + ], + "keywords": [ + "Gruppenrichtlinie", + "GPO", + "Group Policy", + "gpupdate", + "Richtlinie", + "OU", + "zentrale Einstellung", + "Policy", + "Gruppenrichtlinien", + "Server, Backend und Plattformen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/gruppenrichtlinien", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/02_monitor-und-dockingstation.json b/services/agent/knowledge/02_monitor-und-dockingstation.json index a63b879..5c0cca5 100644 --- a/services/agent/knowledge/02_monitor-und-dockingstation.json +++ b/services/agent/knowledge/02_monitor-und-dockingstation.json @@ -1,30 +1,30 @@ -{ - "id": "KAT-MONITOR-UND-DOCKINGSTATION-SELECT", - "title": "Monitor und Dockingstation", - "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Monitor und Dockingstation. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Monitor kein Bild zeigt, flackert, falsch erkannt wird oder eine Dockingstation Bild, Netzwerk, USB oder Stromversorgung nicht korrekt durchreicht. Typische Ticketformulierungen sind: „Zweiter Bildschirm bleibt schwarz“; „Dockingstation erkennt Netzwerk nicht“; „Monitor flackert“; „Notebook lädt am Dock nicht“. Nicht auswählen, wenn der gesamte PC nicht startet, ein flächiges Netzwerkproblem vorliegt oder ein neuer Monitor beziehungsweise eine neue Dockingstation beschafft werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei Netzwerkproblemen über ein Dock zunächst lokale Prüfung durch den Support; bei mehreren Betroffenen Übergabe an Infrastruktur.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Monitor und Dockingstation“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Arbeitsplatz und Endgeräte \u003e Monitor und Dockingstation" - ], - "keywords": [ - "Monitor", - "Bildschirm", - "Display", - "Dockingstation", - "Dock", - "kein Bild", - "flackert", - "zweiter Bildschirm", - "USB-C", - "HDMI", - "DisplayPort", - "Monitor und Dockingstation", - "Arbeitsplatz und Endgeräte" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/monitor-und-dockingstation", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-MONITOR-UND-DOCKINGSTATION-SELECT", + "title": "Monitor und Dockingstation", + "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Monitor und Dockingstation. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Monitor kein Bild zeigt, flackert, falsch erkannt wird oder eine Dockingstation Bild, Netzwerk, USB oder Stromversorgung nicht korrekt durchreicht. Typische Ticketformulierungen sind: „Zweiter Bildschirm bleibt schwarz“; „Dockingstation erkennt Netzwerk nicht“; „Monitor flackert“; „Notebook lädt am Dock nicht“. Nicht auswählen, wenn der gesamte PC nicht startet, ein flächiges Netzwerkproblem vorliegt oder ein neuer Monitor beziehungsweise eine neue Dockingstation beschafft werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei Netzwerkproblemen über ein Dock zunächst lokale Prüfung durch den Support; bei mehreren Betroffenen Übergabe an Infrastruktur.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Monitor und Dockingstation“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Arbeitsplatz und Endgeräte \u003e Monitor und Dockingstation" + ], + "keywords": [ + "Monitor", + "Bildschirm", + "Display", + "Dockingstation", + "Dock", + "kein Bild", + "flackert", + "zweiter Bildschirm", + "USB-C", + "HDMI", + "DisplayPort", + "Monitor und Dockingstation", + "Arbeitsplatz und Endgeräte" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/monitor-und-dockingstation", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/02_netzwerkdrucker.json b/services/agent/knowledge/02_netzwerkdrucker.json index c74921b..868f645 100644 --- a/services/agent/knowledge/02_netzwerkdrucker.json +++ b/services/agent/knowledge/02_netzwerkdrucker.json @@ -1,25 +1,25 @@ -{ - "id": "KAT-NETZWERKDRUCKER-SELECT", - "title": "Netzwerkdrucker", - "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Netzwerkdrucker. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein gemeinsam genutzter Netzwerkdrucker nicht erreichbar ist, Druckaufträge hängen, eine Warteschlange fehlerhaft ist oder mehrere Benutzer auf dasselbe Gerät nicht drucken können. Typische Ticketformulierungen sind: „Netzwerkdrucker offline“; „Druckwarteschlange hängt“; „Mehrere Nutzer können nicht drucken“; „Drucker nicht verbunden“. Nicht auswählen, wenn ein lokaler USB-Drucker, Kopierer oder rein zentraler Druckserverdienst ohne konkretes Gerät betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei zentralem Druckserver- oder Netzwerkproblem wird an Infrastruktur und Backend übergeben.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Netzwerkdrucker“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Drucken, Scannen und Kopieren \u003e Netzwerkdrucker" - ], - "keywords": [ - "Netzwerkdrucker", - "Druckwarteschlange", - "Print Queue", - "offline", - "gemeinsamer Drucker", - "Druckauftrag hängt", - "IP-Drucker", - "Drucken, Scannen und Kopieren" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/netzwerkdrucker", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-NETZWERKDRUCKER-SELECT", + "title": "Netzwerkdrucker", + "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Netzwerkdrucker. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein gemeinsam genutzter Netzwerkdrucker nicht erreichbar ist, Druckaufträge hängen, eine Warteschlange fehlerhaft ist oder mehrere Benutzer auf dasselbe Gerät nicht drucken können. Typische Ticketformulierungen sind: „Netzwerkdrucker offline“; „Druckwarteschlange hängt“; „Mehrere Nutzer können nicht drucken“; „Drucker nicht verbunden“. Nicht auswählen, wenn ein lokaler USB-Drucker, Kopierer oder rein zentraler Druckserverdienst ohne konkretes Gerät betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei zentralem Druckserver- oder Netzwerkproblem wird an Infrastruktur und Backend übergeben.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Netzwerkdrucker“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Drucken, Scannen und Kopieren \u003e Netzwerkdrucker" + ], + "keywords": [ + "Netzwerkdrucker", + "Druckwarteschlange", + "Print Queue", + "offline", + "gemeinsamer Drucker", + "Druckauftrag hängt", + "IP-Drucker", + "Drucken, Scannen und Kopieren" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/netzwerkdrucker", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/02_rufnummer-und-nebenstelle.json b/services/agent/knowledge/02_rufnummer-und-nebenstelle.json index a06c2ec..b580fa8 100644 --- a/services/agent/knowledge/02_rufnummer-und-nebenstelle.json +++ b/services/agent/knowledge/02_rufnummer-und-nebenstelle.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-RUFNUMMER-UND-NEBENSTELLE-SELECT", - "title": "Rufnummer und Nebenstelle", - "text": "Auswahlziel: Telefonie und Kommunikation \u003e Rufnummer und Nebenstelle. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn eine Rufnummer oder Nebenstelle neu eingerichtet, geändert, einem Arbeitsplatz zugeordnet, portiert oder aufgehoben werden soll. Typische Ticketformulierungen sind: „Neue Nebenstelle einrichten“; „Rufnummer umziehen“; „Durchwahl ändern“; „Nebenstelle löschen“. Nicht auswählen, wenn nur das Telefon defekt ist, eine Weiterleitung benötigt wird oder ein Mobilfunkvertrag betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Beschaffungs- oder Vertragsanteile werden bei Bedarf an Leitung und Finanzen / Beschaffung übergeben.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Rufnummer und Nebenstelle“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Telefonie und Kommunikation \u003e Rufnummer und Nebenstelle" - ], - "keywords": [ - "Rufnummer", - "Nebenstelle", - "Durchwahl", - "Telefonnummer", - "Portierung", - "Nummer zuordnen", - "Nummer ändern", - "Rufnummer und Nebenstelle", - "Telefonie und Kommunikation" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/rufnummer-und-nebenstelle", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-RUFNUMMER-UND-NEBENSTELLE-SELECT", + "title": "Rufnummer und Nebenstelle", + "text": "Auswahlziel: Telefonie und Kommunikation \u003e Rufnummer und Nebenstelle. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn eine Rufnummer oder Nebenstelle neu eingerichtet, geändert, einem Arbeitsplatz zugeordnet, portiert oder aufgehoben werden soll. Typische Ticketformulierungen sind: „Neue Nebenstelle einrichten“; „Rufnummer umziehen“; „Durchwahl ändern“; „Nebenstelle löschen“. Nicht auswählen, wenn nur das Telefon defekt ist, eine Weiterleitung benötigt wird oder ein Mobilfunkvertrag betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Beschaffungs- oder Vertragsanteile werden bei Bedarf an Leitung und Finanzen / Beschaffung übergeben.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Rufnummer und Nebenstelle“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Telefonie und Kommunikation \u003e Rufnummer und Nebenstelle" + ], + "keywords": [ + "Rufnummer", + "Nebenstelle", + "Durchwahl", + "Telefonnummer", + "Portierung", + "Nummer zuordnen", + "Nummer ändern", + "Rufnummer und Nebenstelle", + "Telefonie und Kommunikation" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/rufnummer-und-nebenstelle", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/02_schadsoftware-und-virenfund.json b/services/agent/knowledge/02_schadsoftware-und-virenfund.json index 1650b22..f7fe331 100644 --- a/services/agent/knowledge/02_schadsoftware-und-virenfund.json +++ b/services/agent/knowledge/02_schadsoftware-und-virenfund.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-SCHADSOFTWARE-UND-VIRENFUND-SELECT", - "title": "Schadsoftware und Virenfund", - "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Schadsoftware und Virenfund. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Virenscanner, EDR oder ein anderes Schutzsystem Malware, Trojaner, Ransomware, unerwünschte Software oder eine verdächtige Datei auf einem Gerät meldet. Typische Ticketformulierungen sind: „Virenscanner meldet Trojaner“; „Datei in Quarantäne“; „Ransomware-Verdacht“; „Malware-Fund auf Notebook“. Nicht auswählen, wenn lediglich ein Virenscanner-Update fehlt, eine allgemeine Schwachstelle bekannt ist oder nur eine verdächtige E-Mail noch nicht geöffnet wurde. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Betroffene Geräte nicht weiter verwenden und nicht eigenständig bereinigen; bei möglicher Ausbreitung als Sicherheitsvorfall eskalieren.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Schadsoftware und Virenfund“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "IT-Sicherheit und Datensicherung \u003e Schadsoftware und Virenfund" - ], - "keywords": [ - "Virus", - "Malware", - "Trojaner", - "Ransomware", - "Virenfund", - "Quarantäne", - "EDR", - "infiziert", - "Schadsoftware", - "Schadsoftware und Virenfund", - "IT-Sicherheit und Datensicherung" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/schadsoftware-und-virenfund", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-SCHADSOFTWARE-UND-VIRENFUND-SELECT", + "title": "Schadsoftware und Virenfund", + "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Schadsoftware und Virenfund. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Virenscanner, EDR oder ein anderes Schutzsystem Malware, Trojaner, Ransomware, unerwünschte Software oder eine verdächtige Datei auf einem Gerät meldet. Typische Ticketformulierungen sind: „Virenscanner meldet Trojaner“; „Datei in Quarantäne“; „Ransomware-Verdacht“; „Malware-Fund auf Notebook“. Nicht auswählen, wenn lediglich ein Virenscanner-Update fehlt, eine allgemeine Schwachstelle bekannt ist oder nur eine verdächtige E-Mail noch nicht geöffnet wurde. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Betroffene Geräte nicht weiter verwenden und nicht eigenständig bereinigen; bei möglicher Ausbreitung als Sicherheitsvorfall eskalieren.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Schadsoftware und Virenfund“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "IT-Sicherheit und Datensicherung \u003e Schadsoftware und Virenfund" + ], + "keywords": [ + "Virus", + "Malware", + "Trojaner", + "Ransomware", + "Virenfund", + "Quarantäne", + "EDR", + "infiziert", + "Schadsoftware", + "Schadsoftware und Virenfund", + "IT-Sicherheit und Datensicherung" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/schadsoftware-und-virenfund", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/02_schulverwaltungsnetz.json b/services/agent/knowledge/02_schulverwaltungsnetz.json index 1ee6fb2..59e074d 100644 --- a/services/agent/knowledge/02_schulverwaltungsnetz.json +++ b/services/agent/knowledge/02_schulverwaltungsnetz.json @@ -1,23 +1,23 @@ -{ - "id": "KAT-SCHULVERWALTUNGSNETZ-SELECT", - "title": "Schulverwaltungsnetz", - "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Schulverwaltungsnetz. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn das getrennte Verwaltungsnetz einer Schule, Arbeitsplätze der Schulverwaltung oder schulverwaltungsspezifische Netzzugänge betroffen sind. Typische Ticketformulierungen sind: „Sekretariat ohne Verwaltungsnetz“; „Schulleitungs-PC erreicht Verwaltungsdienste nicht“; „Verwaltungs-WLAN gestört“. Nicht auswählen, wenn das pädagogische Schülernetz, eine konkrete Schulverwaltungsanwendung oder die gesamte Standortanbindung ausfällt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Zentrale Netzwerkkomponenten werden durch Team Schulen qualifiziert und an Infrastruktur und Backend weitergegeben.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Schulverwaltungsnetz“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Schulspezifische IT-Systeme \u003e Schulverwaltungsnetz" - ], - "keywords": [ - "Schulverwaltungsnetz", - "Verwaltungsnetz Schule", - "Sekretariat Netzwerk", - "Schulleitung Netzwerk", - "Schulverwaltung", - "Schulspezifische IT-Systeme" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/schulverwaltungsnetz", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-SCHULVERWALTUNGSNETZ-SELECT", + "title": "Schulverwaltungsnetz", + "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Schulverwaltungsnetz. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn das getrennte Verwaltungsnetz einer Schule, Arbeitsplätze der Schulverwaltung oder schulverwaltungsspezifische Netzzugänge betroffen sind. Typische Ticketformulierungen sind: „Sekretariat ohne Verwaltungsnetz“; „Schulleitungs-PC erreicht Verwaltungsdienste nicht“; „Verwaltungs-WLAN gestört“. Nicht auswählen, wenn das pädagogische Schülernetz, eine konkrete Schulverwaltungsanwendung oder die gesamte Standortanbindung ausfällt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Zentrale Netzwerkkomponenten werden durch Team Schulen qualifiziert und an Infrastruktur und Backend weitergegeben.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Schulverwaltungsnetz“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Schulspezifische IT-Systeme \u003e Schulverwaltungsnetz" + ], + "keywords": [ + "Schulverwaltungsnetz", + "Verwaltungsnetz Schule", + "Sekretariat Netzwerk", + "Schulleitung Netzwerk", + "Schulverwaltung", + "Schulspezifische IT-Systeme" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/schulverwaltungsnetz", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/02_softwarebeschaffung.json b/services/agent/knowledge/02_softwarebeschaffung.json index 6c5df7a..88a4f90 100644 --- a/services/agent/knowledge/02_softwarebeschaffung.json +++ b/services/agent/knowledge/02_softwarebeschaffung.json @@ -1,25 +1,25 @@ -{ - "id": "KAT-SOFTWAREBESCHAFFUNG-SELECT", - "title": "Softwarebeschaffung", - "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Softwarebeschaffung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn neue Software, ein neues Produkt, ein Abonnement oder eine kostenpflichtige Erweiterung beschafft und vertraglich beauftragt werden soll. Typische Ticketformulierungen sind: „Neue Software kaufen“; „SaaS-Angebot beauftragen“; „Kostenpflichtiges Modul beschaffen“; „Softwareangebot prüfen“. Nicht auswählen, wenn bereits freigegebene Software nur installiert, eine Fachanwendung eingeführt oder eine vorhandene Lizenz technisch nicht erkannt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische, datenschutzrechtliche und sicherheitsbezogene Prüfung erfolgt vor Beauftragung.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Softwarebeschaffung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Beschaffung, Verträge und Lizenzen \u003e Softwarebeschaffung" - ], - "keywords": [ - "Softwarebeschaffung", - "Software kaufen", - "SaaS", - "Abonnement", - "Lizenz kaufen", - "Angebot Software", - "Bestellung Software", - "Beschaffung, Verträge und Lizenzen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/softwarebeschaffung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-SOFTWAREBESCHAFFUNG-SELECT", + "title": "Softwarebeschaffung", + "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Softwarebeschaffung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn neue Software, ein neues Produkt, ein Abonnement oder eine kostenpflichtige Erweiterung beschafft und vertraglich beauftragt werden soll. Typische Ticketformulierungen sind: „Neue Software kaufen“; „SaaS-Angebot beauftragen“; „Kostenpflichtiges Modul beschaffen“; „Softwareangebot prüfen“. Nicht auswählen, wenn bereits freigegebene Software nur installiert, eine Fachanwendung eingeführt oder eine vorhandene Lizenz technisch nicht erkannt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische, datenschutzrechtliche und sicherheitsbezogene Prüfung erfolgt vor Beauftragung.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Softwarebeschaffung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Beschaffung, Verträge und Lizenzen \u003e Softwarebeschaffung" + ], + "keywords": [ + "Softwarebeschaffung", + "Software kaufen", + "SaaS", + "Abonnement", + "Lizenz kaufen", + "Angebot Software", + "Bestellung Software", + "Beschaffung, Verträge und Lizenzen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/softwarebeschaffung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/02_wlan.json b/services/agent/knowledge/02_wlan.json index 8fde4df..e94822c 100644 --- a/services/agent/knowledge/02_wlan.json +++ b/services/agent/knowledge/02_wlan.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-WLAN-SELECT", - "title": "WLAN", - "text": "Auswahlziel: Netzwerk und Verbindungen \u003e WLAN. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn eine drahtlose Netzwerkverbindung nicht hergestellt wird, häufig abbricht, zu schwach ist, ein WLAN-Bereich nicht versorgt wird oder ein SSID-/Authentifizierungsproblem besteht. Typische Ticketformulierungen sind: „WLAN verbindet nicht“; „Schlechter Empfang im Raum“; „SSID fehlt“; „WLAN bricht ständig ab“. Nicht auswählen, wenn Mobilfunkempfang, kabelgebundenes LAN oder ein allgemeines Internetproblem ohne WLAN-Bezug gemeint ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei einem einzelnen Gerät kann der Support vorprüfen; flächige Abdeckung und Access Points liegen bei Infrastruktur.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e WLAN“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Netzwerk und Verbindungen \u003e WLAN" - ], - "keywords": [ - "WLAN", - "Wi-Fi", - "SSID", - "Funknetz", - "Access Point", - "schlechter Empfang", - "keine Verbindung", - "WLAN-Abdeckung", - "Netzwerk und Verbindungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/wlan", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-WLAN-SELECT", + "title": "WLAN", + "text": "Auswahlziel: Netzwerk und Verbindungen \u003e WLAN. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn eine drahtlose Netzwerkverbindung nicht hergestellt wird, häufig abbricht, zu schwach ist, ein WLAN-Bereich nicht versorgt wird oder ein SSID-/Authentifizierungsproblem besteht. Typische Ticketformulierungen sind: „WLAN verbindet nicht“; „Schlechter Empfang im Raum“; „SSID fehlt“; „WLAN bricht ständig ab“. Nicht auswählen, wenn Mobilfunkempfang, kabelgebundenes LAN oder ein allgemeines Internetproblem ohne WLAN-Bezug gemeint ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei einem einzelnen Gerät kann der Support vorprüfen; flächige Abdeckung und Access Points liegen bei Infrastruktur.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e WLAN“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Netzwerk und Verbindungen \u003e WLAN" + ], + "keywords": [ + "WLAN", + "Wi-Fi", + "SSID", + "Funknetz", + "Access Point", + "schlechter Empfang", + "keine Verbindung", + "WLAN-Abdeckung", + "Netzwerk und Verbindungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/wlan", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/03_ad-gruppen-und-gruppenrichtlinien.json b/services/agent/knowledge/03_ad-gruppen-und-gruppenrichtlinien.json index e5be493..3d73420 100644 --- a/services/agent/knowledge/03_ad-gruppen-und-gruppenrichtlinien.json +++ b/services/agent/knowledge/03_ad-gruppen-und-gruppenrichtlinien.json @@ -1,29 +1,29 @@ -{ - "id": "KAT-AD-GRUPPEN-UND-GRUPPENRICHTLINIEN-SELECT", - "title": "AD-Gruppen und Gruppenrichtlinien", - "text": "Auswahlziel: Benutzerkonten und Berechtigungen \u003e AD-Gruppen und Gruppenrichtlinien. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Mitgliedschaften in zentralen Active-Directory-Gruppen, Sicherheitsgruppen, Verteilergruppen mit AD-Bezug oder technische Gruppenrichtlinien geprüft, geändert oder neu eingerichtet werden sollen. Auch fehlerhafte Laufwerkszuordnungen oder zentrale Windows-Einstellungen durch GPO gehören hierher. Typische Ticketformulierungen sind: „Benutzer in AD-Gruppe aufnehmen“; „GPO wird nicht angewendet“; „Netzlaufwerk fehlt wegen Gruppenmitgliedschaft“; „Zentrale Windows-Richtlinie ändern“. Nicht auswählen, wenn es um eine fachliche Rolle innerhalb einer Anwendung, ein einzelnes vergessenes Kennwort oder eine lokale Einstellung an nur einem Arbeitsplatz ohne Richtlinienbezug geht. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei fachlichen Rollen ist Fachanwendungen zuständig; bei reinen Arbeitsplatzproblemen ohne zentralen Bezug zunächst der Support.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Benutzerkonten und Berechtigungen \u003e AD-Gruppen und Gruppenrichtlinien“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Benutzerkonten und Berechtigungen \u003e AD-Gruppen und Gruppenrichtlinien" - ], - "keywords": [ - "Active Directory", - "AD-Gruppe", - "Sicherheitsgruppe", - "Gruppenmitgliedschaft", - "GPO", - "Gruppenrichtlinie", - "Group Policy", - "OU", - "Laufwerkszuordnung", - "zentrale Richtlinie", - "AD-Gruppen und Gruppenrichtlinien", - "Benutzerkonten und Berechtigungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/benutzerkonten-und-berechtigungen/ad-gruppen-und-gruppenrichtlinien", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-AD-GRUPPEN-UND-GRUPPENRICHTLINIEN-SELECT", + "title": "AD-Gruppen und Gruppenrichtlinien", + "text": "Auswahlziel: Benutzerkonten und Berechtigungen \u003e AD-Gruppen und Gruppenrichtlinien. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Mitgliedschaften in zentralen Active-Directory-Gruppen, Sicherheitsgruppen, Verteilergruppen mit AD-Bezug oder technische Gruppenrichtlinien geprüft, geändert oder neu eingerichtet werden sollen. Auch fehlerhafte Laufwerkszuordnungen oder zentrale Windows-Einstellungen durch GPO gehören hierher. Typische Ticketformulierungen sind: „Benutzer in AD-Gruppe aufnehmen“; „GPO wird nicht angewendet“; „Netzlaufwerk fehlt wegen Gruppenmitgliedschaft“; „Zentrale Windows-Richtlinie ändern“. Nicht auswählen, wenn es um eine fachliche Rolle innerhalb einer Anwendung, ein einzelnes vergessenes Kennwort oder eine lokale Einstellung an nur einem Arbeitsplatz ohne Richtlinienbezug geht. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei fachlichen Rollen ist Fachanwendungen zuständig; bei reinen Arbeitsplatzproblemen ohne zentralen Bezug zunächst der Support.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Benutzerkonten und Berechtigungen \u003e AD-Gruppen und Gruppenrichtlinien“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Benutzerkonten und Berechtigungen \u003e AD-Gruppen und Gruppenrichtlinien" + ], + "keywords": [ + "Active Directory", + "AD-Gruppe", + "Sicherheitsgruppe", + "Gruppenmitgliedschaft", + "GPO", + "Gruppenrichtlinie", + "Group Policy", + "OU", + "Laufwerkszuordnung", + "zentrale Richtlinie", + "AD-Gruppen und Gruppenrichtlinien", + "Benutzerkonten und Berechtigungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/benutzerkonten-und-berechtigungen/ad-gruppen-und-gruppenrichtlinien", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/03_dateiablagen-und-netzlaufwerke.json b/services/agent/knowledge/03_dateiablagen-und-netzlaufwerke.json index 7722cbe..1966361 100644 --- a/services/agent/knowledge/03_dateiablagen-und-netzlaufwerke.json +++ b/services/agent/knowledge/03_dateiablagen-und-netzlaufwerke.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-DATEIABLAGEN-UND-NETZLAUFWERKE-SELECT", - "title": "Dateiablagen und Netzlaufwerke", - "text": "Auswahlziel: Server, Backend und Plattformen \u003e Dateiablagen und Netzlaufwerke. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn zentrale Dateifreigaben, Netzlaufwerke, SMB-Ablagen oder Berechtigungsstrukturen nicht erreichbar sind, Speicherprobleme zeigen oder neu bereitgestellt werden sollen. Typische Ticketformulierungen sind: „Netzlaufwerk nicht erreichbar“; „Dateifreigabe anlegen“; „Ordnerberechtigung ändern“; „Speicherplatz auf Ablage voll“. Nicht auswählen, wenn nur eine lokale Datei beschädigt ist, eine Fachanwendung ihren Export nicht erzeugt oder lediglich eine Laufwerkszuordnung wegen fehlender AD-Gruppe fehlt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei gruppenbasierter Berechtigung kann zusätzlich Benutzerkonten \u003e AD-Gruppen relevant sein.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Dateiablagen und Netzlaufwerke“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Server, Backend und Plattformen \u003e Dateiablagen und Netzlaufwerke" - ], - "keywords": [ - "Netzlaufwerk", - "Dateifreigabe", - "Fileserver", - "SMB", - "Ordnerberechtigung", - "Laufwerk", - "Ablage", - "Speicherplatz", - "Dateiablagen und Netzlaufwerke", - "Server, Backend und Plattformen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/dateiablagen-und-netzlaufwerke", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-DATEIABLAGEN-UND-NETZLAUFWERKE-SELECT", + "title": "Dateiablagen und Netzlaufwerke", + "text": "Auswahlziel: Server, Backend und Plattformen \u003e Dateiablagen und Netzlaufwerke. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn zentrale Dateifreigaben, Netzlaufwerke, SMB-Ablagen oder Berechtigungsstrukturen nicht erreichbar sind, Speicherprobleme zeigen oder neu bereitgestellt werden sollen. Typische Ticketformulierungen sind: „Netzlaufwerk nicht erreichbar“; „Dateifreigabe anlegen“; „Ordnerberechtigung ändern“; „Speicherplatz auf Ablage voll“. Nicht auswählen, wenn nur eine lokale Datei beschädigt ist, eine Fachanwendung ihren Export nicht erzeugt oder lediglich eine Laufwerkszuordnung wegen fehlender AD-Gruppe fehlt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei gruppenbasierter Berechtigung kann zusätzlich Benutzerkonten \u003e AD-Gruppen relevant sein.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Dateiablagen und Netzlaufwerke“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Server, Backend und Plattformen \u003e Dateiablagen und Netzlaufwerke" + ], + "keywords": [ + "Netzlaufwerk", + "Dateifreigabe", + "Fileserver", + "SMB", + "Ordnerberechtigung", + "Laufwerk", + "Ablage", + "Speicherplatz", + "Dateiablagen und Netzlaufwerke", + "Server, Backend und Plattformen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/dateiablagen-und-netzlaufwerke", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/03_digitale-tafeln-und-prasentationstechnik.json b/services/agent/knowledge/03_digitale-tafeln-und-prasentationstechnik.json index 97bd119..9f6d7ad 100644 --- a/services/agent/knowledge/03_digitale-tafeln-und-prasentationstechnik.json +++ b/services/agent/knowledge/03_digitale-tafeln-und-prasentationstechnik.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-DIGITALE-TAFELN-UND-PRASENTATIONSTECHNIK-SELECT", - "title": "Digitale Tafeln und Präsentationstechnik", - "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Digitale Tafeln und Präsentationstechnik. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn interaktive Tafeln, Displays, Beamer, Dokumentenkameras oder fest installierte Präsentationstechnik im Unterricht nicht funktioniert oder eingerichtet werden muss. Typische Ticketformulierungen sind: „Digitale Tafel reagiert nicht“; „Beamer im Klassenraum ohne Bild“; „Dokumentenkamera defekt“; „Interaktives Display kalibrieren“. Nicht auswählen, wenn nur ein normales Arbeitsplatzmonitorproblem, eine allgemeine Videokonferenz oder ein privates Endgerät betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Netzwerk- oder Backendursachen werden nach Erstprüfung an Infrastruktur weitergegeben.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Digitale Tafeln und Präsentationstechnik“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Schulspezifische IT-Systeme \u003e Digitale Tafeln und Präsentationstechnik" - ], - "keywords": [ - "digitale Tafel", - "Whiteboard", - "Smartboard", - "Beamer", - "Dokumentenkamera", - "interaktives Display", - "Klassenraumtechnik", - "Digitale Tafeln und Präsentationstechnik", - "Schulspezifische IT-Systeme" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/digitale-tafeln-und-prasentationstechnik", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-DIGITALE-TAFELN-UND-PRASENTATIONSTECHNIK-SELECT", + "title": "Digitale Tafeln und Präsentationstechnik", + "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Digitale Tafeln und Präsentationstechnik. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn interaktive Tafeln, Displays, Beamer, Dokumentenkameras oder fest installierte Präsentationstechnik im Unterricht nicht funktioniert oder eingerichtet werden muss. Typische Ticketformulierungen sind: „Digitale Tafel reagiert nicht“; „Beamer im Klassenraum ohne Bild“; „Dokumentenkamera defekt“; „Interaktives Display kalibrieren“. Nicht auswählen, wenn nur ein normales Arbeitsplatzmonitorproblem, eine allgemeine Videokonferenz oder ein privates Endgerät betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Netzwerk- oder Backendursachen werden nach Erstprüfung an Infrastruktur weitergegeben.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Digitale Tafeln und Präsentationstechnik“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Schulspezifische IT-Systeme \u003e Digitale Tafeln und Präsentationstechnik" + ], + "keywords": [ + "digitale Tafel", + "Whiteboard", + "Smartboard", + "Beamer", + "Dokumentenkamera", + "interaktives Display", + "Klassenraumtechnik", + "Digitale Tafeln und Präsentationstechnik", + "Schulspezifische IT-Systeme" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/digitale-tafeln-und-prasentationstechnik", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/03_fachanwendung-berechtigung.json b/services/agent/knowledge/03_fachanwendung-berechtigung.json index 1457d4f..4b6ed79 100644 --- a/services/agent/knowledge/03_fachanwendung-berechtigung.json +++ b/services/agent/knowledge/03_fachanwendung-berechtigung.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-FACHANWENDUNG-BERECHTIGUNG-SELECT", - "title": "Fachanwendung – Berechtigung", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Berechtigung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Rollen, Rechte, Mandanten, Organisationseinheiten oder Funktionszugriffe innerhalb einer bestimmten Fachanwendung beantragt, geändert oder korrigiert werden sollen. Typische Ticketformulierungen sind: „Rolle Kassenverwalter vergeben“; „Zugriff auf Modul Personal“; „Mandant freischalten“; „Berechtigung im Fachverfahren fehlt“. Nicht auswählen, wenn das zentrale AD-Konto fehlt, das Kennwort gesperrt ist oder eine allgemeine AD-Gruppenmitgliedschaft ohne konkrete Anwendung betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Anwendung und genaue Soll-Rolle müssen genannt werden; Genehmigungswege bleiben unberührt.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Berechtigung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Berechtigung" - ], - "keywords": [ - "Fachanwendung Berechtigung", - "Rolle", - "Rechte", - "Freischaltung", - "Mandant", - "Modulzugriff", - "Benutzerrolle", - "Fachverfahren Zugriff", - "Fachanwendung – Berechtigung", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-berechtigung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-FACHANWENDUNG-BERECHTIGUNG-SELECT", + "title": "Fachanwendung – Berechtigung", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Berechtigung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Rollen, Rechte, Mandanten, Organisationseinheiten oder Funktionszugriffe innerhalb einer bestimmten Fachanwendung beantragt, geändert oder korrigiert werden sollen. Typische Ticketformulierungen sind: „Rolle Kassenverwalter vergeben“; „Zugriff auf Modul Personal“; „Mandant freischalten“; „Berechtigung im Fachverfahren fehlt“. Nicht auswählen, wenn das zentrale AD-Konto fehlt, das Kennwort gesperrt ist oder eine allgemeine AD-Gruppenmitgliedschaft ohne konkrete Anwendung betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Anwendung und genaue Soll-Rolle müssen genannt werden; Genehmigungswege bleiben unberührt.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Berechtigung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Berechtigung" + ], + "keywords": [ + "Fachanwendung Berechtigung", + "Rolle", + "Rechte", + "Freischaltung", + "Mandant", + "Modulzugriff", + "Benutzerrolle", + "Fachverfahren Zugriff", + "Fachanwendung – Berechtigung", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-berechtigung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/03_internetzugang.json b/services/agent/knowledge/03_internetzugang.json index c361cf0..7610ee1 100644 --- a/services/agent/knowledge/03_internetzugang.json +++ b/services/agent/knowledge/03_internetzugang.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-INTERNETZUGANG-SELECT", - "title": "Internetzugang", - "text": "Auswahlziel: Netzwerk und Verbindungen \u003e Internetzugang. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Webseiten oder externe Dienste allgemein nicht erreichbar sind, der Internetzugang eines Standorts oder mehrerer Benutzer ausfällt oder auffällig langsam ist. Typische Ticketformulierungen sind: „Kein Internet im Gebäude“; „Externe Webseiten nicht erreichbar“; „Internetzugang sehr langsam“; „Mehrere Nutzer offline“. Nicht auswählen, wenn nur eine einzelne Anwendung gestört ist, ein VPN-Tunnel nicht verbindet oder eine konkrete Adresse durch die Firewall freigeschaltet werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Einzelne blockierte Ziele können eine Firewall-Thematik sein; flächige Ausfälle sind zu priorisieren.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e Internetzugang“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Netzwerk und Verbindungen \u003e Internetzugang" - ], - "keywords": [ - "Internet", - "Internetzugang", - "Webseiten nicht erreichbar", - "offline", - "WAN", - "Provider", - "Internetausfall", - "langsam", - "Netzwerk und Verbindungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/internetzugang", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-INTERNETZUGANG-SELECT", + "title": "Internetzugang", + "text": "Auswahlziel: Netzwerk und Verbindungen \u003e Internetzugang. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Webseiten oder externe Dienste allgemein nicht erreichbar sind, der Internetzugang eines Standorts oder mehrerer Benutzer ausfällt oder auffällig langsam ist. Typische Ticketformulierungen sind: „Kein Internet im Gebäude“; „Externe Webseiten nicht erreichbar“; „Internetzugang sehr langsam“; „Mehrere Nutzer offline“. Nicht auswählen, wenn nur eine einzelne Anwendung gestört ist, ein VPN-Tunnel nicht verbindet oder eine konkrete Adresse durch die Firewall freigeschaltet werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Einzelne blockierte Ziele können eine Firewall-Thematik sein; flächige Ausfälle sind zu priorisieren.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e Internetzugang“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Netzwerk und Verbindungen \u003e Internetzugang" + ], + "keywords": [ + "Internet", + "Internetzugang", + "Webseiten nicht erreichbar", + "offline", + "WAN", + "Provider", + "Internetausfall", + "langsam", + "Netzwerk und Verbindungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/internetzugang", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/03_lizenzbestellung.json b/services/agent/knowledge/03_lizenzbestellung.json index 5acd6d3..86b2af4 100644 --- a/services/agent/knowledge/03_lizenzbestellung.json +++ b/services/agent/knowledge/03_lizenzbestellung.json @@ -1,25 +1,25 @@ -{ - "id": "KAT-LIZENZBESTELLUNG-SELECT", - "title": "Lizenzbestellung", - "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Lizenzbestellung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn zusätzliche Einzel-, Benutzer-, Geräte- oder Volumenlizenzen für bereits ausgewählte Produkte bestellt oder verlängert werden sollen. Typische Ticketformulierungen sind: „Zusätzliche Benutzerlizenz bestellen“; „Lizenz verlängern“; „Weitere Geräte lizenzieren“; „Volumenlizenz ergänzen“. Nicht auswählen, wenn nur die technische Aktivierung fehlschlägt, der gesamte Vertrag neu verhandelt oder der Lizenzbestand ausgewertet werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Produkt, Anzahl, Laufzeit, Kostenstelle und Genehmigung sollten angegeben werden.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Lizenzbestellung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Beschaffung, Verträge und Lizenzen \u003e Lizenzbestellung" - ], - "keywords": [ - "Lizenzbestellung", - "Lizenz bestellen", - "zusätzliche Lizenz", - "Seat", - "Subscription", - "Verlängerung", - "Volumenlizenz", - "Beschaffung, Verträge und Lizenzen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/lizenzbestellung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-LIZENZBESTELLUNG-SELECT", + "title": "Lizenzbestellung", + "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Lizenzbestellung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn zusätzliche Einzel-, Benutzer-, Geräte- oder Volumenlizenzen für bereits ausgewählte Produkte bestellt oder verlängert werden sollen. Typische Ticketformulierungen sind: „Zusätzliche Benutzerlizenz bestellen“; „Lizenz verlängern“; „Weitere Geräte lizenzieren“; „Volumenlizenz ergänzen“. Nicht auswählen, wenn nur die technische Aktivierung fehlschlägt, der gesamte Vertrag neu verhandelt oder der Lizenzbestand ausgewertet werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Produkt, Anzahl, Laufzeit, Kostenstelle und Genehmigung sollten angegeben werden.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Lizenzbestellung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Beschaffung, Verträge und Lizenzen \u003e Lizenzbestellung" + ], + "keywords": [ + "Lizenzbestellung", + "Lizenz bestellen", + "zusätzliche Lizenz", + "Seat", + "Subscription", + "Verlängerung", + "Volumenlizenz", + "Beschaffung, Verträge und Lizenzen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/lizenzbestellung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/03_multifunktionsgerat-und-kopierer.json b/services/agent/knowledge/03_multifunktionsgerat-und-kopierer.json index 8ae249c..5128de0 100644 --- a/services/agent/knowledge/03_multifunktionsgerat-und-kopierer.json +++ b/services/agent/knowledge/03_multifunktionsgerat-und-kopierer.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-MULTIFUNKTIONSGERAT-UND-KOPIERER-SELECT", - "title": "Multifunktionsgerät und Kopierer", - "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Multifunktionsgerät und Kopierer. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Multifunktionsgerät oder Kopierer beim Drucken, Kopieren, Einzug, Bedienfeld oder Gerätebetrieb fehlerhaft ist. Typische Ticketformulierungen sind: „Kopierer zeigt Fehlercode“; „Dokumenteneinzug klemmt“; „MFP kopiert nicht“; „Bedienfeld reagiert nicht“. Nicht auswählen, wenn ausschließlich Scan-to-Mail, ein einzelner Arbeitsplatzdrucker oder eine Neubeschaffung betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Vertragspartner- oder Wartungseinsätze können durch Support koordiniert werden.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Multifunktionsgerät und Kopierer“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Drucken, Scannen und Kopieren \u003e Multifunktionsgerät und Kopierer" - ], - "keywords": [ - "Kopierer", - "Multifunktionsgerät", - "MFP", - "Kopieren", - "Dokumenteneinzug", - "Fehlercode", - "Bedienfeld", - "Multifunktionsgerät und Kopierer", - "Drucken, Scannen und Kopieren" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/multifunktionsgerat-und-kopierer", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-MULTIFUNKTIONSGERAT-UND-KOPIERER-SELECT", + "title": "Multifunktionsgerät und Kopierer", + "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Multifunktionsgerät und Kopierer. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Multifunktionsgerät oder Kopierer beim Drucken, Kopieren, Einzug, Bedienfeld oder Gerätebetrieb fehlerhaft ist. Typische Ticketformulierungen sind: „Kopierer zeigt Fehlercode“; „Dokumenteneinzug klemmt“; „MFP kopiert nicht“; „Bedienfeld reagiert nicht“. Nicht auswählen, wenn ausschließlich Scan-to-Mail, ein einzelner Arbeitsplatzdrucker oder eine Neubeschaffung betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Vertragspartner- oder Wartungseinsätze können durch Support koordiniert werden.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Multifunktionsgerät und Kopierer“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Drucken, Scannen und Kopieren \u003e Multifunktionsgerät und Kopierer" + ], + "keywords": [ + "Kopierer", + "Multifunktionsgerät", + "MFP", + "Kopieren", + "Dokumenteneinzug", + "Fehlercode", + "Bedienfeld", + "Multifunktionsgerät und Kopierer", + "Drucken, Scannen und Kopieren" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/multifunktionsgerat-und-kopierer", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/03_rufgruppe-und-weiterleitung.json b/services/agent/knowledge/03_rufgruppe-und-weiterleitung.json index a56e673..8a82690 100644 --- a/services/agent/knowledge/03_rufgruppe-und-weiterleitung.json +++ b/services/agent/knowledge/03_rufgruppe-und-weiterleitung.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-RUFGRUPPE-UND-WEITERLEITUNG-SELECT", - "title": "Rufgruppe und Weiterleitung", - "text": "Auswahlziel: Telefonie und Kommunikation \u003e Rufgruppe und Weiterleitung. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn Rufgruppen, Sammelanschlüsse, Vertretungen, Anrufweiterleitungen, Zeitsteuerungen oder Erreichbarkeitsregeln eingerichtet oder geändert werden sollen. Typische Ticketformulierungen sind: „Rufumleitung für Urlaub“; „Mitarbeiter in Rufgruppe aufnehmen“; „Sammelruf ändern“; „Zeitsteuerung der Zentrale“. Nicht auswählen, wenn eine neue Rufnummer benötigt wird, das Telefon physisch defekt ist oder eine E-Mail-Weiterleitung gemeint ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Gewünschte Quell- und Zielnummer sowie Zeitraum müssen klar angegeben werden.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Rufgruppe und Weiterleitung“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Telefonie und Kommunikation \u003e Rufgruppe und Weiterleitung" - ], - "keywords": [ - "Rufgruppe", - "Weiterleitung", - "Rufumleitung", - "Sammelruf", - "Vertretung", - "Anrufweiterleitung", - "Zeitsteuerung", - "Erreichbarkeit", - "Rufgruppe und Weiterleitung", - "Telefonie und Kommunikation" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/rufgruppe-und-weiterleitung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-RUFGRUPPE-UND-WEITERLEITUNG-SELECT", + "title": "Rufgruppe und Weiterleitung", + "text": "Auswahlziel: Telefonie und Kommunikation \u003e Rufgruppe und Weiterleitung. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn Rufgruppen, Sammelanschlüsse, Vertretungen, Anrufweiterleitungen, Zeitsteuerungen oder Erreichbarkeitsregeln eingerichtet oder geändert werden sollen. Typische Ticketformulierungen sind: „Rufumleitung für Urlaub“; „Mitarbeiter in Rufgruppe aufnehmen“; „Sammelruf ändern“; „Zeitsteuerung der Zentrale“. Nicht auswählen, wenn eine neue Rufnummer benötigt wird, das Telefon physisch defekt ist oder eine E-Mail-Weiterleitung gemeint ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Gewünschte Quell- und Zielnummer sowie Zeitraum müssen klar angegeben werden.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Rufgruppe und Weiterleitung“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Telefonie und Kommunikation \u003e Rufgruppe und Weiterleitung" + ], + "keywords": [ + "Rufgruppe", + "Weiterleitung", + "Rufumleitung", + "Sammelruf", + "Vertretung", + "Anrufweiterleitung", + "Zeitsteuerung", + "Erreichbarkeit", + "Rufgruppe und Weiterleitung", + "Telefonie und Kommunikation" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/rufgruppe-und-weiterleitung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/03_sicherheitsvorfall.json b/services/agent/knowledge/03_sicherheitsvorfall.json index c55f811..8e33a3a 100644 --- a/services/agent/knowledge/03_sicherheitsvorfall.json +++ b/services/agent/knowledge/03_sicherheitsvorfall.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-SICHERHEITSVORFALL-SELECT", - "title": "Sicherheitsvorfall", - "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Sicherheitsvorfall. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn ein tatsächlicher oder ernsthaft vermuteter unbefugter Zugriff, Datenabfluss, kompromittiertes Konto, Verlust sensibler Daten, aktive Attacke oder erhebliche Sicherheitsverletzung vorliegt. Typische Ticketformulierungen sind: „Konto möglicherweise übernommen“; „Unbefugter Zugriff festgestellt“; „Daten an falschen Empfänger“; „Aktiver Angriff“; „Dienstgerät mit sensiblen Daten verloren“. Nicht auswählen, wenn nur eine allgemeine Sicherheitsfrage, Schwachstellenmeldung ohne Ausnutzung oder verdächtige E-Mail ohne Interaktion vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Unmittelbar priorisieren und Leitung sowie erforderliche Datenschutz-/Informationssicherheitsstellen nach internen Meldewegen beteiligen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Sicherheitsvorfall“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "IT-Sicherheit und Datensicherung \u003e Sicherheitsvorfall" - ], - "keywords": [ - "Sicherheitsvorfall", - "Datenabfluss", - "kompromittiert", - "unbefugter Zugriff", - "Account übernommen", - "Cyberangriff", - "Datenverlust", - "Incident", - "Security Breach", - "IT-Sicherheit und Datensicherung" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/sicherheitsvorfall", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-SICHERHEITSVORFALL-SELECT", + "title": "Sicherheitsvorfall", + "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Sicherheitsvorfall. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn ein tatsächlicher oder ernsthaft vermuteter unbefugter Zugriff, Datenabfluss, kompromittiertes Konto, Verlust sensibler Daten, aktive Attacke oder erhebliche Sicherheitsverletzung vorliegt. Typische Ticketformulierungen sind: „Konto möglicherweise übernommen“; „Unbefugter Zugriff festgestellt“; „Daten an falschen Empfänger“; „Aktiver Angriff“; „Dienstgerät mit sensiblen Daten verloren“. Nicht auswählen, wenn nur eine allgemeine Sicherheitsfrage, Schwachstellenmeldung ohne Ausnutzung oder verdächtige E-Mail ohne Interaktion vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Unmittelbar priorisieren und Leitung sowie erforderliche Datenschutz-/Informationssicherheitsstellen nach internen Meldewegen beteiligen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Sicherheitsvorfall“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "IT-Sicherheit und Datensicherung \u003e Sicherheitsvorfall" + ], + "keywords": [ + "Sicherheitsvorfall", + "Datenabfluss", + "kompromittiert", + "unbefugter Zugriff", + "Account übernommen", + "Cyberangriff", + "Datenverlust", + "Incident", + "Security Breach", + "IT-Sicherheit und Datensicherung" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/sicherheitsvorfall", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/03_tastatur-maus-und-zubehor.json b/services/agent/knowledge/03_tastatur-maus-und-zubehor.json index d96002f..89a6999 100644 --- a/services/agent/knowledge/03_tastatur-maus-und-zubehor.json +++ b/services/agent/knowledge/03_tastatur-maus-und-zubehor.json @@ -1,29 +1,29 @@ -{ - "id": "KAT-TASTATUR-MAUS-UND-ZUBEHOR-SELECT", - "title": "Tastatur, Maus und Zubehör", - "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Tastatur, Maus und Zubehör. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn Tastatur, Maus, Headset, Webcam, Netzteil, Adapter, Kabel oder sonstige Arbeitsplatzperipherie defekt, nicht erkannt oder nicht vorhanden ist. Typische Ticketformulierungen sind: „Maus reagiert nicht“; „Tastatur defekt“; „Webcam wird nicht erkannt“; „Headset ohne Ton“; „Netzteil fehlt“. Nicht auswählen, wenn ein komplettes Endgerät ausfällt, ein Telekommunikationsgerät betroffen ist oder eine Neubeschaffung außerhalb eines Ersatzfalls beantragt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Einfache Ersatzteile bearbeitet der Support; kostenpflichtige Neubeschaffungen können an Beschaffung übergeben werden.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Tastatur, Maus und Zubehör“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Arbeitsplatz und Endgeräte \u003e Tastatur, Maus und Zubehör" - ], - "keywords": [ - "Tastatur", - "Maus", - "Headset", - "Webcam", - "Netzteil", - "Adapter", - "Kabel", - "USB-Gerät", - "Peripherie", - "Zubehör", - "Tastatur, Maus und Zubehör", - "Arbeitsplatz und Endgeräte" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/tastatur-maus-und-zubehor", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-TASTATUR-MAUS-UND-ZUBEHOR-SELECT", + "title": "Tastatur, Maus und Zubehör", + "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Tastatur, Maus und Zubehör. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn Tastatur, Maus, Headset, Webcam, Netzteil, Adapter, Kabel oder sonstige Arbeitsplatzperipherie defekt, nicht erkannt oder nicht vorhanden ist. Typische Ticketformulierungen sind: „Maus reagiert nicht“; „Tastatur defekt“; „Webcam wird nicht erkannt“; „Headset ohne Ton“; „Netzteil fehlt“. Nicht auswählen, wenn ein komplettes Endgerät ausfällt, ein Telekommunikationsgerät betroffen ist oder eine Neubeschaffung außerhalb eines Ersatzfalls beantragt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Einfache Ersatzteile bearbeitet der Support; kostenpflichtige Neubeschaffungen können an Beschaffung übergeben werden.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Tastatur, Maus und Zubehör“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Arbeitsplatz und Endgeräte \u003e Tastatur, Maus und Zubehör" + ], + "keywords": [ + "Tastatur", + "Maus", + "Headset", + "Webcam", + "Netzteil", + "Adapter", + "Kabel", + "USB-Gerät", + "Peripherie", + "Zubehör", + "Tastatur, Maus und Zubehör", + "Arbeitsplatz und Endgeräte" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/tastatur-maus-und-zubehor", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/04_anwendungsberechtigung.json b/services/agent/knowledge/04_anwendungsberechtigung.json index 12054c2..287cc42 100644 --- a/services/agent/knowledge/04_anwendungsberechtigung.json +++ b/services/agent/knowledge/04_anwendungsberechtigung.json @@ -1,30 +1,30 @@ -{ - "id": "KAT-ANWENDUNGSBERECHTIGUNG-SELECT", - "title": "Anwendungsberechtigung", - "text": "Auswahlziel: Benutzerkonten und Berechtigungen \u003e Anwendungsberechtigung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn ein Benutzer Zugriff, eine Rolle, ein Funktionsrecht oder eine organisatorische Zuordnung innerhalb einer konkreten Fachanwendung benötigt oder wenn eine vorhandene Berechtigung dort nicht korrekt wirkt. Typische Ticketformulierungen sind: „Rolle Sachbearbeitung fehlt“; „Kein Zugriff auf Modul Kasse“; „Berechtigung in Fachverfahren beantragen“; „Benutzer sieht falsche Organisationseinheit“. Nicht auswählen, wenn das zentrale Benutzerkonto selbst fehlt oder gesperrt ist; wenn eine AD-Gruppe oder GPO geändert werden soll; wenn der Zugriff technisch wegen Netzwerk, VPN oder Serverausfall scheitert. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Die betroffene Anwendung und die gewünschte Rolle sollten genannt werden. Technische Konten bleiben bei Infrastruktur und Backend.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Benutzerkonten und Berechtigungen \u003e Anwendungsberechtigung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Benutzerkonten und Berechtigungen \u003e Anwendungsberechtigung" - ], - "keywords": [ - "Berechtigung", - "Rolle", - "Zugriff", - "Freischaltung", - "Fachanwendung", - "Fachverfahren", - "Modul", - "Rechte", - "Benutzerrolle", - "Mandant", - "Organisationseinheit", - "Anwendungsberechtigung", - "Benutzerkonten und Berechtigungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/benutzerkonten-und-berechtigungen/anwendungsberechtigung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-ANWENDUNGSBERECHTIGUNG-SELECT", + "title": "Anwendungsberechtigung", + "text": "Auswahlziel: Benutzerkonten und Berechtigungen \u003e Anwendungsberechtigung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn ein Benutzer Zugriff, eine Rolle, ein Funktionsrecht oder eine organisatorische Zuordnung innerhalb einer konkreten Fachanwendung benötigt oder wenn eine vorhandene Berechtigung dort nicht korrekt wirkt. Typische Ticketformulierungen sind: „Rolle Sachbearbeitung fehlt“; „Kein Zugriff auf Modul Kasse“; „Berechtigung in Fachverfahren beantragen“; „Benutzer sieht falsche Organisationseinheit“. Nicht auswählen, wenn das zentrale Benutzerkonto selbst fehlt oder gesperrt ist; wenn eine AD-Gruppe oder GPO geändert werden soll; wenn der Zugriff technisch wegen Netzwerk, VPN oder Serverausfall scheitert. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Die betroffene Anwendung und die gewünschte Rolle sollten genannt werden. Technische Konten bleiben bei Infrastruktur und Backend.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Benutzerkonten und Berechtigungen \u003e Anwendungsberechtigung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Benutzerkonten und Berechtigungen \u003e Anwendungsberechtigung" + ], + "keywords": [ + "Berechtigung", + "Rolle", + "Zugriff", + "Freischaltung", + "Fachanwendung", + "Fachverfahren", + "Modul", + "Rechte", + "Benutzerrolle", + "Mandant", + "Organisationseinheit", + "Anwendungsberechtigung", + "Benutzerkonten und Berechtigungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/benutzerkonten-und-berechtigungen/anwendungsberechtigung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/04_computerraume.json b/services/agent/knowledge/04_computerraume.json index 919d3dd..88bd2c5 100644 --- a/services/agent/knowledge/04_computerraume.json +++ b/services/agent/knowledge/04_computerraume.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-COMPUTERRAUME-SELECT", - "title": "Computerräume", - "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Computerräume. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn mehrere PCs, Peripheriegeräte, Anmeldungen oder die technische Ausstattung eines Computerraums betroffen sind oder der Raum neu eingerichtet werden soll. Typische Ticketformulierungen sind: „Mehrere PCs im Computerraum starten nicht“; „Computerraum neu ausstatten“; „Schüler können sich im Raum nicht anmelden“; „Raumsoftware verteilen“. Nicht auswählen, wenn nur ein einzelner Lehrerarbeitsplatz oder eine allgemeine Standortstörung vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Mehrgeräteprobleme sprechen oft für zentrale Richtlinien, Images oder Netzwerkursachen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Computerräume“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Schulspezifische IT-Systeme \u003e Computerräume" - ], - "keywords": [ - "Computerraum", - "PC-Raum", - "Informatikraum", - "Schüler-PC", - "Raumausstattung", - "mehrere Rechner", - "Unterrichtsraum", - "Computerräume", - "Schulspezifische IT-Systeme" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/computerraume", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-COMPUTERRAUME-SELECT", + "title": "Computerräume", + "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Computerräume. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn mehrere PCs, Peripheriegeräte, Anmeldungen oder die technische Ausstattung eines Computerraums betroffen sind oder der Raum neu eingerichtet werden soll. Typische Ticketformulierungen sind: „Mehrere PCs im Computerraum starten nicht“; „Computerraum neu ausstatten“; „Schüler können sich im Raum nicht anmelden“; „Raumsoftware verteilen“. Nicht auswählen, wenn nur ein einzelner Lehrerarbeitsplatz oder eine allgemeine Standortstörung vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Mehrgeräteprobleme sprechen oft für zentrale Richtlinien, Images oder Netzwerkursachen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Computerräume“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Schulspezifische IT-Systeme \u003e Computerräume" + ], + "keywords": [ + "Computerraum", + "PC-Raum", + "Informatikraum", + "Schüler-PC", + "Raumausstattung", + "mehrere Rechner", + "Unterrichtsraum", + "Computerräume", + "Schulspezifische IT-Systeme" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/computerraume", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/04_fachanwendung-konfiguration.json b/services/agent/knowledge/04_fachanwendung-konfiguration.json index 3e68246..42ec045 100644 --- a/services/agent/knowledge/04_fachanwendung-konfiguration.json +++ b/services/agent/knowledge/04_fachanwendung-konfiguration.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-FACHANWENDUNG-KONFIGURATION-SELECT", - "title": "Fachanwendung – Konfiguration", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Konfiguration. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Parameter, Vorlagen, Masken, Stammdaten, Nummernkreise, Workflows oder organisatorische Einstellungen einer Fachanwendung angepasst werden sollen. Typische Ticketformulierungen sind: „Neue Vorlage hinterlegen“; „Workflow anpassen“; „Stammdaten konfigurieren“; „Nummernkreis ändern“. Nicht auswählen, wenn nur ein einzelner Benutzer eine lokale Einstellung benötigt, eine neue umfangreiche Funktion gefordert wird oder die technische Serverplattform geändert werden muss. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Größere funktionale Erweiterungen gehören zu Neue Anforderung; technische Plattformparameter zu Infrastruktur und Backend.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Konfiguration“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Konfiguration" - ], - "keywords": [ - "Konfiguration", - "Parameter", - "Vorlage", - "Stammdaten", - "Workflow", - "Maske anpassen", - "Nummernkreis", - "Einstellung Fachanwendung", - "Fachanwendung – Konfiguration", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-konfiguration", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-FACHANWENDUNG-KONFIGURATION-SELECT", + "title": "Fachanwendung – Konfiguration", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Konfiguration. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Parameter, Vorlagen, Masken, Stammdaten, Nummernkreise, Workflows oder organisatorische Einstellungen einer Fachanwendung angepasst werden sollen. Typische Ticketformulierungen sind: „Neue Vorlage hinterlegen“; „Workflow anpassen“; „Stammdaten konfigurieren“; „Nummernkreis ändern“. Nicht auswählen, wenn nur ein einzelner Benutzer eine lokale Einstellung benötigt, eine neue umfangreiche Funktion gefordert wird oder die technische Serverplattform geändert werden muss. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Größere funktionale Erweiterungen gehören zu Neue Anforderung; technische Plattformparameter zu Infrastruktur und Backend.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Konfiguration“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Konfiguration" + ], + "keywords": [ + "Konfiguration", + "Parameter", + "Vorlage", + "Stammdaten", + "Workflow", + "Maske anpassen", + "Nummernkreis", + "Einstellung Fachanwendung", + "Fachanwendung – Konfiguration", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-konfiguration", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/04_lizenzverwaltung.json b/services/agent/knowledge/04_lizenzverwaltung.json index e418204..5c43d3b 100644 --- a/services/agent/knowledge/04_lizenzverwaltung.json +++ b/services/agent/knowledge/04_lizenzverwaltung.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-LIZENZVERWALTUNG-SELECT", - "title": "Lizenzverwaltung", - "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Lizenzverwaltung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn Lizenzbestände, Zuordnungen, Nutzungsrechte, Laufzeiten, Compliance oder verfügbare Kontingente dokumentiert und geprüft werden sollen. Typische Ticketformulierungen sind: „Lizenzbestand prüfen“; „Lizenz einem Benutzer zuordnen“; „Unterlizenzierung bewerten“; „Laufzeiten auswerten“. Nicht auswählen, wenn neue Lizenzen konkret bestellt oder eine technische Aktivierungsstörung behoben werden muss. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Zuweisung kann durch Fachteam erfolgen; kaufmännischer Bestand bleibt bei Beschaffung.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Lizenzverwaltung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Beschaffung, Verträge und Lizenzen \u003e Lizenzverwaltung" - ], - "keywords": [ - "Lizenzverwaltung", - "Lizenzbestand", - "Compliance", - "Nutzungsrecht", - "Lizenzzuordnung", - "Kontingent", - "Ablaufdatum", - "Asset Management", - "Beschaffung, Verträge und Lizenzen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/lizenzverwaltung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-LIZENZVERWALTUNG-SELECT", + "title": "Lizenzverwaltung", + "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Lizenzverwaltung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn Lizenzbestände, Zuordnungen, Nutzungsrechte, Laufzeiten, Compliance oder verfügbare Kontingente dokumentiert und geprüft werden sollen. Typische Ticketformulierungen sind: „Lizenzbestand prüfen“; „Lizenz einem Benutzer zuordnen“; „Unterlizenzierung bewerten“; „Laufzeiten auswerten“. Nicht auswählen, wenn neue Lizenzen konkret bestellt oder eine technische Aktivierungsstörung behoben werden muss. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Zuweisung kann durch Fachteam erfolgen; kaufmännischer Bestand bleibt bei Beschaffung.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Lizenzverwaltung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Beschaffung, Verträge und Lizenzen \u003e Lizenzverwaltung" + ], + "keywords": [ + "Lizenzverwaltung", + "Lizenzbestand", + "Compliance", + "Nutzungsrecht", + "Lizenzzuordnung", + "Kontingent", + "Ablaufdatum", + "Asset Management", + "Beschaffung, Verträge und Lizenzen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/lizenzverwaltung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/04_mobile-endgerate.json b/services/agent/knowledge/04_mobile-endgerate.json index b75d679..4c9eeb5 100644 --- a/services/agent/knowledge/04_mobile-endgerate.json +++ b/services/agent/knowledge/04_mobile-endgerate.json @@ -1,29 +1,29 @@ -{ - "id": "KAT-MOBILE-ENDGERATE-SELECT", - "title": "Mobile Endgeräte", - "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Mobile Endgeräte. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein dienstliches Smartphone oder Tablet eingerichtet, zurückgesetzt, ausgetauscht oder bei einem Geräte-, App-, Synchronisations- oder lokalen Bedienproblem unterstützt werden muss. Typische Ticketformulierungen sind: „Diensthandy lässt sich nicht entsperren“; „Tablet synchronisiert nicht“; „Smartphone einrichten“; „Mobiles Gerät zurücksetzen“. Nicht auswählen, wenn es um Mobilfunktarif, SIM-Karte oder Rufnummer geht; wenn MFA lediglich auf ein neues Gerät übertragen werden muss; wenn das Gerät neu beschafft werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: SIM- und Vertragsfragen gehören zu Telefonie und Kommunikation \u003e Mobilfunk. Sicherheitsrelevanter Verlust ist zusätzlich als Sicherheitsvorfall zu behandeln.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Mobile Endgeräte“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Arbeitsplatz und Endgeräte \u003e Mobile Endgeräte" - ], - "keywords": [ - "Smartphone", - "Diensthandy", - "Tablet", - "Mobilgerät", - "iPhone", - "Android", - "iPad", - "Synchronisation", - "Geräteeinrichtung", - "Zurücksetzen", - "Mobile Endgeräte", - "Arbeitsplatz und Endgeräte" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/mobile-endgerate", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-MOBILE-ENDGERATE-SELECT", + "title": "Mobile Endgeräte", + "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Mobile Endgeräte. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein dienstliches Smartphone oder Tablet eingerichtet, zurückgesetzt, ausgetauscht oder bei einem Geräte-, App-, Synchronisations- oder lokalen Bedienproblem unterstützt werden muss. Typische Ticketformulierungen sind: „Diensthandy lässt sich nicht entsperren“; „Tablet synchronisiert nicht“; „Smartphone einrichten“; „Mobiles Gerät zurücksetzen“. Nicht auswählen, wenn es um Mobilfunktarif, SIM-Karte oder Rufnummer geht; wenn MFA lediglich auf ein neues Gerät übertragen werden muss; wenn das Gerät neu beschafft werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: SIM- und Vertragsfragen gehören zu Telefonie und Kommunikation \u003e Mobilfunk. Sicherheitsrelevanter Verlust ist zusätzlich als Sicherheitsvorfall zu behandeln.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Mobile Endgeräte“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Arbeitsplatz und Endgeräte \u003e Mobile Endgeräte" + ], + "keywords": [ + "Smartphone", + "Diensthandy", + "Tablet", + "Mobilgerät", + "iPhone", + "Android", + "iPad", + "Synchronisation", + "Geräteeinrichtung", + "Zurücksetzen", + "Mobile Endgeräte", + "Arbeitsplatz und Endgeräte" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/mobile-endgerate", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/04_mobilfunk.json b/services/agent/knowledge/04_mobilfunk.json index 4422242..7565efe 100644 --- a/services/agent/knowledge/04_mobilfunk.json +++ b/services/agent/knowledge/04_mobilfunk.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-MOBILFUNK-SELECT", - "title": "Mobilfunk", - "text": "Auswahlziel: Telefonie und Kommunikation \u003e Mobilfunk. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn SIM-Karte, Mobilfunktarif, Mobilfunkvertrag, Empfang, Roaming, mobile Daten oder eine dienstliche Mobilfunkrufnummer betroffen sind. Typische Ticketformulierungen sind: „SIM-Karte gesperrt“; „Kein Mobilfunkempfang“; „Roaming freischalten“; „Mobilfunktarif ändern“; „Neue eSIM“. Nicht auswählen, wenn das Smartphone selbst defekt ist, MFA übertragen werden soll oder eine reine App-/Geräteeinrichtung ohne Mobilfunkbezug vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Kaufmännische Vertragsänderungen erfolgen in Abstimmung mit Beschaffung.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Mobilfunk“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Telefonie und Kommunikation \u003e Mobilfunk" - ], - "keywords": [ - "Mobilfunk", - "SIM-Karte", - "eSIM", - "Roaming", - "mobile Daten", - "Mobilfunkvertrag", - "Empfang", - "PIN", - "PUK", - "Handynummer", - "Telefonie und Kommunikation" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/mobilfunk", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-MOBILFUNK-SELECT", + "title": "Mobilfunk", + "text": "Auswahlziel: Telefonie und Kommunikation \u003e Mobilfunk. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn SIM-Karte, Mobilfunktarif, Mobilfunkvertrag, Empfang, Roaming, mobile Daten oder eine dienstliche Mobilfunkrufnummer betroffen sind. Typische Ticketformulierungen sind: „SIM-Karte gesperrt“; „Kein Mobilfunkempfang“; „Roaming freischalten“; „Mobilfunktarif ändern“; „Neue eSIM“. Nicht auswählen, wenn das Smartphone selbst defekt ist, MFA übertragen werden soll oder eine reine App-/Geräteeinrichtung ohne Mobilfunkbezug vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Kaufmännische Vertragsänderungen erfolgen in Abstimmung mit Beschaffung.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Mobilfunk“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Telefonie und Kommunikation \u003e Mobilfunk" + ], + "keywords": [ + "Mobilfunk", + "SIM-Karte", + "eSIM", + "Roaming", + "mobile Daten", + "Mobilfunkvertrag", + "Empfang", + "PIN", + "PUK", + "Handynummer", + "Telefonie und Kommunikation" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/mobilfunk", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/04_scanner.json b/services/agent/knowledge/04_scanner.json index 2ae499d..7ed40ae 100644 --- a/services/agent/knowledge/04_scanner.json +++ b/services/agent/knowledge/04_scanner.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-SCANNER-SELECT", - "title": "Scanner", - "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Scanner. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Arbeitsplatz- oder Dokumentenscanner nicht erkannt wird, nicht scannt, Einzugsprobleme zeigt oder die lokale Scansoftware fehlerhaft ist. Typische Ticketformulierungen sind: „Scanner wird nicht erkannt“; „Dokumenteneinzug fehlerhaft“; „Scanprogramm startet nicht“; „Scandatei wird nicht erzeugt“. Nicht auswählen, wenn die zentrale Übertragung per Scan-to-Mail oder Scan-to-Folder scheitert oder ein Multifunktionsgerät insgesamt betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Übertragungsziele und zentrale Dienste werden in Scan-to-Mail und Scan-to-Folder erfasst.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Scanner“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Drucken, Scannen und Kopieren \u003e Scanner" - ], - "keywords": [ - "Scanner", - "Scannen", - "Dokumentenscanner", - "Einzug", - "Scanprogramm", - "TWAIN", - "WIA", - "Scanfehler", - "Drucken, Scannen und Kopieren" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/scanner", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-SCANNER-SELECT", + "title": "Scanner", + "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Scanner. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Arbeitsplatz- oder Dokumentenscanner nicht erkannt wird, nicht scannt, Einzugsprobleme zeigt oder die lokale Scansoftware fehlerhaft ist. Typische Ticketformulierungen sind: „Scanner wird nicht erkannt“; „Dokumenteneinzug fehlerhaft“; „Scanprogramm startet nicht“; „Scandatei wird nicht erzeugt“. Nicht auswählen, wenn die zentrale Übertragung per Scan-to-Mail oder Scan-to-Folder scheitert oder ein Multifunktionsgerät insgesamt betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Übertragungsziele und zentrale Dienste werden in Scan-to-Mail und Scan-to-Folder erfasst.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Scanner“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Drucken, Scannen und Kopieren \u003e Scanner" + ], + "keywords": [ + "Scanner", + "Scannen", + "Dokumentenscanner", + "Einzug", + "Scanprogramm", + "TWAIN", + "WIA", + "Scanfehler", + "Drucken, Scannen und Kopieren" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/scanner", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/04_schwachstelle.json b/services/agent/knowledge/04_schwachstelle.json index d19e833..645af5d 100644 --- a/services/agent/knowledge/04_schwachstelle.json +++ b/services/agent/knowledge/04_schwachstelle.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-SCHWACHSTELLE-SELECT", - "title": "Schwachstelle", - "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Schwachstelle. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn eine bekannte oder vermutete Sicherheitslücke, unsichere Konfiguration, CVE, offene Angriffsfläche oder fehlende Härtung gemeldet, bewertet oder behoben werden soll. Typische Ticketformulierungen sind: „CVE betrifft Server“; „Unsichere TLS-Konfiguration“; „Offener Dienst entdeckt“; „System muss gehärtet werden“. Nicht auswählen, wenn bereits ein Angriff oder Datenabfluss stattgefunden hat, nur ein normales Update geplant ist oder Malware gefunden wurde. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei aktiver Ausnutzung wird daraus ein Sicherheitsvorfall; bei reinem Patchbedarf kann Sicherheitsupdate ergänzend passen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Schwachstelle“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "IT-Sicherheit und Datensicherung \u003e Schwachstelle" - ], - "keywords": [ - "Schwachstelle", - "CVE", - "Vulnerability", - "Sicherheitslücke", - "Härtung", - "Hardening", - "unsichere Konfiguration", - "Exposure", - "IT-Sicherheit und Datensicherung" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/schwachstelle", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-SCHWACHSTELLE-SELECT", + "title": "Schwachstelle", + "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Schwachstelle. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn eine bekannte oder vermutete Sicherheitslücke, unsichere Konfiguration, CVE, offene Angriffsfläche oder fehlende Härtung gemeldet, bewertet oder behoben werden soll. Typische Ticketformulierungen sind: „CVE betrifft Server“; „Unsichere TLS-Konfiguration“; „Offener Dienst entdeckt“; „System muss gehärtet werden“. Nicht auswählen, wenn bereits ein Angriff oder Datenabfluss stattgefunden hat, nur ein normales Update geplant ist oder Malware gefunden wurde. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei aktiver Ausnutzung wird daraus ein Sicherheitsvorfall; bei reinem Patchbedarf kann Sicherheitsupdate ergänzend passen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Schwachstelle“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "IT-Sicherheit und Datensicherung \u003e Schwachstelle" + ], + "keywords": [ + "Schwachstelle", + "CVE", + "Vulnerability", + "Sicherheitslücke", + "Härtung", + "Hardening", + "unsichere Konfiguration", + "Exposure", + "IT-Sicherheit und Datensicherung" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/schwachstelle", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/04_serverbetrieb.json b/services/agent/knowledge/04_serverbetrieb.json index d0e9a9f..bfde3e5 100644 --- a/services/agent/knowledge/04_serverbetrieb.json +++ b/services/agent/knowledge/04_serverbetrieb.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-SERVERBETRIEB-SELECT", - "title": "Serverbetrieb", - "text": "Auswahlziel: Server, Backend und Plattformen \u003e Serverbetrieb. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn ein Windows- oder Linux-Server als Betriebssystemplattform ausfällt, gewartet, gepatcht, konfiguriert oder analysiert werden muss. Typische Ticketformulierungen sind: „Server nicht erreichbar“; „Linux-Dienst startet nicht“; „Windows Server patchen“; „Serverleistung analysieren“. Nicht auswählen, wenn ausschließlich eine darauf laufende Fachanwendung, virtuelle Maschine, Datenbank, Containerplattform oder ein Arbeitsplatz-PC betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Die konkrete Anwendungsebene wird getrennt klassifiziert; diese Kategorie betrifft die Serverplattform selbst.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Serverbetrieb“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Server, Backend und Plattformen \u003e Serverbetrieb" - ], - "keywords": [ - "Server", - "Windows Server", - "Linux Server", - "Dienst", - "Systemdienst", - "Patchen", - "Serverausfall", - "Betriebssystem Server", - "Serverbetrieb", - "Server, Backend und Plattformen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/serverbetrieb", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-SERVERBETRIEB-SELECT", + "title": "Serverbetrieb", + "text": "Auswahlziel: Server, Backend und Plattformen \u003e Serverbetrieb. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn ein Windows- oder Linux-Server als Betriebssystemplattform ausfällt, gewartet, gepatcht, konfiguriert oder analysiert werden muss. Typische Ticketformulierungen sind: „Server nicht erreichbar“; „Linux-Dienst startet nicht“; „Windows Server patchen“; „Serverleistung analysieren“. Nicht auswählen, wenn ausschließlich eine darauf laufende Fachanwendung, virtuelle Maschine, Datenbank, Containerplattform oder ein Arbeitsplatz-PC betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Die konkrete Anwendungsebene wird getrennt klassifiziert; diese Kategorie betrifft die Serverplattform selbst.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Serverbetrieb“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Server, Backend und Plattformen \u003e Serverbetrieb" + ], + "keywords": [ + "Server", + "Windows Server", + "Linux Server", + "Dienst", + "Systemdienst", + "Patchen", + "Serverausfall", + "Betriebssystem Server", + "Serverbetrieb", + "Server, Backend und Plattformen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/serverbetrieb", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/04_standortanbindung.json b/services/agent/knowledge/04_standortanbindung.json index adde936..d024f47 100644 --- a/services/agent/knowledge/04_standortanbindung.json +++ b/services/agent/knowledge/04_standortanbindung.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-STANDORTANBINDUNG-SELECT", - "title": "Standortanbindung", - "text": "Auswahlziel: Netzwerk und Verbindungen \u003e Standortanbindung. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn eine Außenstelle, Schule, Verwaltungsstelle oder ein gesamtes Gebäude keine Verbindung zum kommunalen Netz oder Rechenzentrum hat beziehungsweise eine neue Standortverbindung benötigt. Typische Ticketformulierungen sind: „Außenstelle nicht erreichbar“; „Standortverbindung ausgefallen“; „Neue Liegenschaft anbinden“; „Gesamte Schule ohne Verwaltungsnetz“. Nicht auswählen, wenn nur ein einzelner Arbeitsplatz oder eine einzelne Netzwerkdose betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Standortausfälle betreffen meist mehrere Nutzer und sind höher zu priorisieren als Einzelplatzprobleme.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e Standortanbindung“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Netzwerk und Verbindungen \u003e Standortanbindung" - ], - "keywords": [ - "Standortanbindung", - "Außenstelle", - "Liegenschaft", - "Gebäude offline", - "Standleitung", - "WAN", - "Standortverbindung", - "MPLS", - "Glasfaser", - "Netzwerk und Verbindungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/standortanbindung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-STANDORTANBINDUNG-SELECT", + "title": "Standortanbindung", + "text": "Auswahlziel: Netzwerk und Verbindungen \u003e Standortanbindung. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn eine Außenstelle, Schule, Verwaltungsstelle oder ein gesamtes Gebäude keine Verbindung zum kommunalen Netz oder Rechenzentrum hat beziehungsweise eine neue Standortverbindung benötigt. Typische Ticketformulierungen sind: „Außenstelle nicht erreichbar“; „Standortverbindung ausgefallen“; „Neue Liegenschaft anbinden“; „Gesamte Schule ohne Verwaltungsnetz“. Nicht auswählen, wenn nur ein einzelner Arbeitsplatz oder eine einzelne Netzwerkdose betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Standortausfälle betreffen meist mehrere Nutzer und sind höher zu priorisieren als Einzelplatzprobleme.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e Standortanbindung“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Netzwerk und Verbindungen \u003e Standortanbindung" + ], + "keywords": [ + "Standortanbindung", + "Außenstelle", + "Liegenschaft", + "Gebäude offline", + "Standleitung", + "WAN", + "Standortverbindung", + "MPLS", + "Glasfaser", + "Netzwerk und Verbindungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/standortanbindung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/04_standorteroffnung-und-umzug.json b/services/agent/knowledge/04_standorteroffnung-und-umzug.json index c19984e..5a2975f 100644 --- a/services/agent/knowledge/04_standorteroffnung-und-umzug.json +++ b/services/agent/knowledge/04_standorteroffnung-und-umzug.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-STANDORTEROFFNUNG-UND-UMZUG-SELECT", - "title": "Standorteröffnung und Umzug", - "text": "Auswahlziel: Projekte, Änderungen und neue Anforderungen \u003e Standorteröffnung und Umzug. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn eine neue Liegenschaft, Außenstelle, Schule oder ein größerer Gebäudebereich IT-seitig ausgestattet, angebunden oder vollständig umgezogen werden soll. Typische Ticketformulierungen sind: „Neue Außenstelle ausstatten“; „Verwaltungsbereich zieht um“; „Neues Gebäude ans Netz anbinden“; „Kompletter Standortwechsel“. Nicht auswählen, wenn nur ein einzelner Arbeitsplatz innerhalb eines Gebäudes umgesetzt oder eine bestehende Standortleitung gestört ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Die Leitung koordiniert; Infrastruktur, Support, Fachanwendungen, Telekommunikation und Beschaffung liefern Teilaufgaben.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Projekte, Änderungen und neue Anforderungen \u003e Standorteröffnung und Umzug“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Projekte, Änderungen und neue Anforderungen \u003e Standorteröffnung und Umzug" - ], - "keywords": [ - "Standorteröffnung", - "Standortumzug", - "neue Liegenschaft", - "Außenstelle", - "Gebäudeumzug", - "IT-Ausstattung Standort", - "Umzugsprojekt", - "Standorteröffnung und Umzug", - "Projekte, Änderungen und neue Anforderungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/projekte-anderungen-und-neue-anforderungen/standorteroffnung-und-umzug", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-STANDORTEROFFNUNG-UND-UMZUG-SELECT", + "title": "Standorteröffnung und Umzug", + "text": "Auswahlziel: Projekte, Änderungen und neue Anforderungen \u003e Standorteröffnung und Umzug. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn eine neue Liegenschaft, Außenstelle, Schule oder ein größerer Gebäudebereich IT-seitig ausgestattet, angebunden oder vollständig umgezogen werden soll. Typische Ticketformulierungen sind: „Neue Außenstelle ausstatten“; „Verwaltungsbereich zieht um“; „Neues Gebäude ans Netz anbinden“; „Kompletter Standortwechsel“. Nicht auswählen, wenn nur ein einzelner Arbeitsplatz innerhalb eines Gebäudes umgesetzt oder eine bestehende Standortleitung gestört ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Die Leitung koordiniert; Infrastruktur, Support, Fachanwendungen, Telekommunikation und Beschaffung liefern Teilaufgaben.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Projekte, Änderungen und neue Anforderungen \u003e Standorteröffnung und Umzug“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Projekte, Änderungen und neue Anforderungen \u003e Standorteröffnung und Umzug" + ], + "keywords": [ + "Standorteröffnung", + "Standortumzug", + "neue Liegenschaft", + "Außenstelle", + "Gebäudeumzug", + "IT-Ausstattung Standort", + "Umzugsprojekt", + "Standorteröffnung und Umzug", + "Projekte, Änderungen und neue Anforderungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/projekte-anderungen-und-neue-anforderungen/standorteroffnung-und-umzug", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/05_dienstliches-smartphone.json b/services/agent/knowledge/05_dienstliches-smartphone.json index d544846..a88ab00 100644 --- a/services/agent/knowledge/05_dienstliches-smartphone.json +++ b/services/agent/knowledge/05_dienstliches-smartphone.json @@ -1,25 +1,25 @@ -{ - "id": "KAT-DIENSTLICHES-SMARTPHONE-SELECT", - "title": "Dienstliches Smartphone", - "text": "Auswahlziel: Telefonie und Kommunikation \u003e Dienstliches Smartphone. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein dienstliches Smartphone als Telekommunikationsgerät neu eingerichtet, getauscht, repariert oder für Telefonie, Kontakte und dienstliche Kommunikation konfiguriert werden muss. Typische Ticketformulierungen sind: „Diensthandy einrichten“; „Smartphone austauschen“; „Kontakte synchronisieren“; „Telefon-App funktioniert nicht“. Nicht auswählen, wenn ausschließlich SIM, Tarif oder Roaming betroffen ist; wenn ein Tablet ohne Telefoniefunktion oder ein MFA-Token im Mittelpunkt steht. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Allgemeine mobile Endgeräteprobleme können zunächst beim Support bleiben; Mobilfunkvertrag und SIM sind separat.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Dienstliches Smartphone“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Telefonie und Kommunikation \u003e Dienstliches Smartphone" - ], - "keywords": [ - "Dienstliches Smartphone", - "Diensthandy", - "Telefon-App", - "Kontakte", - "Smartphone einrichten", - "Handytausch", - "Mobiltelefon", - "Telefonie und Kommunikation" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/dienstliches-smartphone", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-DIENSTLICHES-SMARTPHONE-SELECT", + "title": "Dienstliches Smartphone", + "text": "Auswahlziel: Telefonie und Kommunikation \u003e Dienstliches Smartphone. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein dienstliches Smartphone als Telekommunikationsgerät neu eingerichtet, getauscht, repariert oder für Telefonie, Kontakte und dienstliche Kommunikation konfiguriert werden muss. Typische Ticketformulierungen sind: „Diensthandy einrichten“; „Smartphone austauschen“; „Kontakte synchronisieren“; „Telefon-App funktioniert nicht“. Nicht auswählen, wenn ausschließlich SIM, Tarif oder Roaming betroffen ist; wenn ein Tablet ohne Telefoniefunktion oder ein MFA-Token im Mittelpunkt steht. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Allgemeine mobile Endgeräteprobleme können zunächst beim Support bleiben; Mobilfunkvertrag und SIM sind separat.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Dienstliches Smartphone“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Telefonie und Kommunikation \u003e Dienstliches Smartphone" + ], + "keywords": [ + "Dienstliches Smartphone", + "Diensthandy", + "Telefon-App", + "Kontakte", + "Smartphone einrichten", + "Handytausch", + "Mobiltelefon", + "Telefonie und Kommunikation" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/dienstliches-smartphone", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/05_digitalisierungsvorhaben.json b/services/agent/knowledge/05_digitalisierungsvorhaben.json index b5dfa19..e2dbc3c 100644 --- a/services/agent/knowledge/05_digitalisierungsvorhaben.json +++ b/services/agent/knowledge/05_digitalisierungsvorhaben.json @@ -1,25 +1,25 @@ -{ - "id": "KAT-DIGITALISIERUNGSVORHABEN-SELECT", - "title": "Digitalisierungsvorhaben", - "text": "Auswahlziel: Projekte, Änderungen und neue Anforderungen \u003e Digitalisierungsvorhaben. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn ein Verwaltungsprozess grundlegend digitalisiert, medienbruchfrei gestaltet oder durch mehrere IT-Komponenten neu unterstützt werden soll. Typische Ticketformulierungen sind: „Papierprozess digitalisieren“; „Digitalen Antrag einführen“; „Medienbruch beseitigen“; „End-to-End-Prozess neu gestalten“. Nicht auswählen, wenn lediglich eine kleine Funktion in einer bestehenden Anwendung ergänzt oder ein einzelnes Gerät beschafft wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Fachanwendungen übernimmt fachnahe Umsetzung; Leitung priorisiert und koordiniert organisationsübergreifend.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Projekte, Änderungen und neue Anforderungen \u003e Digitalisierungsvorhaben“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Projekte, Änderungen und neue Anforderungen \u003e Digitalisierungsvorhaben" - ], - "keywords": [ - "Digitalisierungsvorhaben", - "digitaler Prozess", - "Online-Antrag", - "medienbruchfrei", - "Prozessdigitalisierung", - "E-Government", - "Workflow", - "Projekte, Änderungen und neue Anforderungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/projekte-anderungen-und-neue-anforderungen/digitalisierungsvorhaben", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-DIGITALISIERUNGSVORHABEN-SELECT", + "title": "Digitalisierungsvorhaben", + "text": "Auswahlziel: Projekte, Änderungen und neue Anforderungen \u003e Digitalisierungsvorhaben. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn ein Verwaltungsprozess grundlegend digitalisiert, medienbruchfrei gestaltet oder durch mehrere IT-Komponenten neu unterstützt werden soll. Typische Ticketformulierungen sind: „Papierprozess digitalisieren“; „Digitalen Antrag einführen“; „Medienbruch beseitigen“; „End-to-End-Prozess neu gestalten“. Nicht auswählen, wenn lediglich eine kleine Funktion in einer bestehenden Anwendung ergänzt oder ein einzelnes Gerät beschafft wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Fachanwendungen übernimmt fachnahe Umsetzung; Leitung priorisiert und koordiniert organisationsübergreifend.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Projekte, Änderungen und neue Anforderungen \u003e Digitalisierungsvorhaben“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Projekte, Änderungen und neue Anforderungen \u003e Digitalisierungsvorhaben" + ], + "keywords": [ + "Digitalisierungsvorhaben", + "digitaler Prozess", + "Online-Antrag", + "medienbruchfrei", + "Prozessdigitalisierung", + "E-Government", + "Workflow", + "Projekte, Änderungen und neue Anforderungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/projekte-anderungen-und-neue-anforderungen/digitalisierungsvorhaben", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/05_druckertreiber.json b/services/agent/knowledge/05_druckertreiber.json index d8b6445..b5f2105 100644 --- a/services/agent/knowledge/05_druckertreiber.json +++ b/services/agent/knowledge/05_druckertreiber.json @@ -1,25 +1,25 @@ -{ - "id": "KAT-DRUCKERTREIBER-SELECT", - "title": "Druckertreiber", - "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Druckertreiber. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Druckertreiber fehlt, fehlerhaft ist, aktualisiert werden muss oder falsche Papierfächer, Formate und Gerätefunktionen bereitstellt. Typische Ticketformulierungen sind: „Treiber lässt sich nicht installieren“; „Falsches Papierformat“; „Duplexoption fehlt“; „Druckertreiber aktualisieren“. Nicht auswählen, wenn der Drucker physisch defekt, das Netzwerk ausgefallen oder eine zentrale Druckserverplattform gestört ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Zentrale Treiberpakete können eine Abstimmung mit Infrastruktur und Backend erfordern.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Druckertreiber“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Drucken, Scannen und Kopieren \u003e Druckertreiber" - ], - "keywords": [ - "Druckertreiber", - "Treiber", - "Printer Driver", - "Duplex", - "Papierfach", - "Druckerinstallation", - "Treiberfehler", - "Drucken, Scannen und Kopieren" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/druckertreiber", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-DRUCKERTREIBER-SELECT", + "title": "Druckertreiber", + "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Druckertreiber. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Druckertreiber fehlt, fehlerhaft ist, aktualisiert werden muss oder falsche Papierfächer, Formate und Gerätefunktionen bereitstellt. Typische Ticketformulierungen sind: „Treiber lässt sich nicht installieren“; „Falsches Papierformat“; „Duplexoption fehlt“; „Druckertreiber aktualisieren“. Nicht auswählen, wenn der Drucker physisch defekt, das Netzwerk ausgefallen oder eine zentrale Druckserverplattform gestört ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Zentrale Treiberpakete können eine Abstimmung mit Infrastruktur und Backend erfordern.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Druckertreiber“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Drucken, Scannen und Kopieren \u003e Druckertreiber" + ], + "keywords": [ + "Druckertreiber", + "Treiber", + "Printer Driver", + "Duplex", + "Papierfach", + "Druckerinstallation", + "Treiberfehler", + "Drucken, Scannen und Kopieren" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/druckertreiber", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/05_fachanwendung-schnittstelle-und-datenaustausch.json b/services/agent/knowledge/05_fachanwendung-schnittstelle-und-datenaustausch.json index 9784a76..81f6b0c 100644 --- a/services/agent/knowledge/05_fachanwendung-schnittstelle-und-datenaustausch.json +++ b/services/agent/knowledge/05_fachanwendung-schnittstelle-und-datenaustausch.json @@ -1,29 +1,29 @@ -{ - "id": "KAT-FACHANWENDUNG-SCHNITTSTELLE-UND-DATENAUSTAUSCH-SELECT", - "title": "Fachanwendung – Schnittstelle und Datenaustausch", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Schnittstelle und Datenaustausch. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Import, Export, Webservice, Dateiübergabe oder Datenaustausch zwischen einer Fachanwendung und einem anderen System fehlschlägt oder neu eingerichtet werden soll. Typische Ticketformulierungen sind: „Importdatei wird abgewiesen“; „Export kommt nicht im Zielsystem an“; „Schnittstelle liefert Fehler“; „Datenaustausch einrichten“. Nicht auswählen, wenn nur die Netzwerkverbindung eines Standorts gestört ist, ein allgemeiner Dateiablagefehler vorliegt oder eine rein manuelle Auswertung benötigt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei Firewall- oder Netzwerkfreischaltungen arbeitet Fachanwendungen mit Infrastruktur und Backend zusammen; fachliche Datenformate bleiben bei Fachanwendungen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Schnittstelle und Datenaustausch“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Schnittstelle und Datenaustausch" - ], - "keywords": [ - "Schnittstelle", - "Datenaustausch", - "Import", - "Export", - "Webservice", - "API", - "Dateiübergabe", - "Interface", - "Übertragung", - "Fremdsystem", - "Fachanwendung – Schnittstelle und Datenaustausch", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-schnittstelle-und-datenaustausch", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-FACHANWENDUNG-SCHNITTSTELLE-UND-DATENAUSTAUSCH-SELECT", + "title": "Fachanwendung – Schnittstelle und Datenaustausch", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Schnittstelle und Datenaustausch. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Import, Export, Webservice, Dateiübergabe oder Datenaustausch zwischen einer Fachanwendung und einem anderen System fehlschlägt oder neu eingerichtet werden soll. Typische Ticketformulierungen sind: „Importdatei wird abgewiesen“; „Export kommt nicht im Zielsystem an“; „Schnittstelle liefert Fehler“; „Datenaustausch einrichten“. Nicht auswählen, wenn nur die Netzwerkverbindung eines Standorts gestört ist, ein allgemeiner Dateiablagefehler vorliegt oder eine rein manuelle Auswertung benötigt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei Firewall- oder Netzwerkfreischaltungen arbeitet Fachanwendungen mit Infrastruktur und Backend zusammen; fachliche Datenformate bleiben bei Fachanwendungen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Schnittstelle und Datenaustausch“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Schnittstelle und Datenaustausch" + ], + "keywords": [ + "Schnittstelle", + "Datenaustausch", + "Import", + "Export", + "Webservice", + "API", + "Dateiübergabe", + "Interface", + "Übertragung", + "Fremdsystem", + "Fachanwendung – Schnittstelle und Datenaustausch", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-schnittstelle-und-datenaustausch", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/05_hypervisor.json b/services/agent/knowledge/05_hypervisor.json index 217c7bc..c2c13ff 100644 --- a/services/agent/knowledge/05_hypervisor.json +++ b/services/agent/knowledge/05_hypervisor.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-HYPERVISOR-SELECT", - "title": "Hypervisor", - "text": "Auswahlziel: Server, Backend und Plattformen \u003e Hypervisor. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn die Virtualisierungsplattform, Hosts, Cluster, Ressourcenverwaltung oder zentrale Hypervisor-Funktionen gestört, erweitert oder gewartet werden müssen. Typische Ticketformulierungen sind: „VMware-Host gestört“; „Hyper-V-Cluster meldet Fehler“; „Virtualisierungshost warten“; „Clusterressourcen knapp“. Nicht auswählen, wenn nur eine einzelne virtuelle Maschine betroffen ist oder ein Container- beziehungsweise Kubernetes-Problem vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Einzelne VMs werden unter Virtuelle Maschinen erfasst; die Trägerschicht unter Hypervisor.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Hypervisor“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Server, Backend und Plattformen \u003e Hypervisor" - ], - "keywords": [ - "Hypervisor", - "VMware", - "vSphere", - "ESXi", - "Hyper-V", - "Virtualisierung", - "Host", - "Cluster", - "Proxmox", - "Server, Backend und Plattformen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/hypervisor", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-HYPERVISOR-SELECT", + "title": "Hypervisor", + "text": "Auswahlziel: Server, Backend und Plattformen \u003e Hypervisor. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn die Virtualisierungsplattform, Hosts, Cluster, Ressourcenverwaltung oder zentrale Hypervisor-Funktionen gestört, erweitert oder gewartet werden müssen. Typische Ticketformulierungen sind: „VMware-Host gestört“; „Hyper-V-Cluster meldet Fehler“; „Virtualisierungshost warten“; „Clusterressourcen knapp“. Nicht auswählen, wenn nur eine einzelne virtuelle Maschine betroffen ist oder ein Container- beziehungsweise Kubernetes-Problem vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Einzelne VMs werden unter Virtuelle Maschinen erfasst; die Trägerschicht unter Hypervisor.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Hypervisor“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Server, Backend und Plattformen \u003e Hypervisor" + ], + "keywords": [ + "Hypervisor", + "VMware", + "vSphere", + "ESXi", + "Hyper-V", + "Virtualisierung", + "Host", + "Cluster", + "Proxmox", + "Server, Backend und Plattformen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/hypervisor", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/05_remotezugriff-und-vpn.json b/services/agent/knowledge/05_remotezugriff-und-vpn.json index dba98fb..ccf8ca6 100644 --- a/services/agent/knowledge/05_remotezugriff-und-vpn.json +++ b/services/agent/knowledge/05_remotezugriff-und-vpn.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-REMOTEZUGRIFF-UND-VPN-SELECT", - "title": "Remotezugriff und VPN", - "text": "Auswahlziel: Benutzerkonten und Berechtigungen \u003e Remotezugriff und VPN. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn ein neuer oder geänderter Fernzugriff auf interne Systeme benötigt wird, ein VPN-Zugang beantragt werden soll oder Zugriffsrechte für Homeoffice, Bereitschaft oder externe Administration eingerichtet werden müssen. Typische Ticketformulierungen sind: „VPN-Zugang beantragen“; „Homeoffice-Zugriff freischalten“; „Remotezugriff für Bereitschaft“; „Externer Dienstleister benötigt zeitlich begrenzten Zugang“. Nicht auswählen, wenn ein bereits eingerichteter VPN-Tunnel technisch nicht verbindet; dafür ist die Kategorie Netzwerk und Verbindungen \u003e VPN vorgesehen. Ein allgemeines Kennwortproblem gehört zu Kennwort zurücksetzen. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Diese Kategorie beschreibt die Berechtigung beziehungsweise Bereitstellung. Technische Verbindungsstörungen eines vorhandenen VPN werden unter Netzwerk \u003e VPN erfasst.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Benutzerkonten und Berechtigungen \u003e Remotezugriff und VPN“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Benutzerkonten und Berechtigungen \u003e Remotezugriff und VPN" - ], - "keywords": [ - "Remotezugriff", - "Fernzugriff", - "VPN-Zugang", - "Homeoffice", - "Remote Access", - "Zugriff von außen", - "Bereitschaft", - "externer Zugriff", - "Freischaltung VPN", - "Remotezugriff und VPN", - "Benutzerkonten und Berechtigungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/benutzerkonten-und-berechtigungen/remotezugriff-und-vpn", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-REMOTEZUGRIFF-UND-VPN-SELECT", + "title": "Remotezugriff und VPN", + "text": "Auswahlziel: Benutzerkonten und Berechtigungen \u003e Remotezugriff und VPN. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn ein neuer oder geänderter Fernzugriff auf interne Systeme benötigt wird, ein VPN-Zugang beantragt werden soll oder Zugriffsrechte für Homeoffice, Bereitschaft oder externe Administration eingerichtet werden müssen. Typische Ticketformulierungen sind: „VPN-Zugang beantragen“; „Homeoffice-Zugriff freischalten“; „Remotezugriff für Bereitschaft“; „Externer Dienstleister benötigt zeitlich begrenzten Zugang“. Nicht auswählen, wenn ein bereits eingerichteter VPN-Tunnel technisch nicht verbindet; dafür ist die Kategorie Netzwerk und Verbindungen \u003e VPN vorgesehen. Ein allgemeines Kennwortproblem gehört zu Kennwort zurücksetzen. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Diese Kategorie beschreibt die Berechtigung beziehungsweise Bereitstellung. Technische Verbindungsstörungen eines vorhandenen VPN werden unter Netzwerk \u003e VPN erfasst.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Benutzerkonten und Berechtigungen \u003e Remotezugriff und VPN“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Benutzerkonten und Berechtigungen \u003e Remotezugriff und VPN" + ], + "keywords": [ + "Remotezugriff", + "Fernzugriff", + "VPN-Zugang", + "Homeoffice", + "Remote Access", + "Zugriff von außen", + "Bereitschaft", + "externer Zugriff", + "Freischaltung VPN", + "Remotezugriff und VPN", + "Benutzerkonten und Berechtigungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/benutzerkonten-und-berechtigungen/remotezugriff-und-vpn", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/05_schuler-und-lehrkraftekonten.json b/services/agent/knowledge/05_schuler-und-lehrkraftekonten.json index 8ae93ab..ba10cd3 100644 --- a/services/agent/knowledge/05_schuler-und-lehrkraftekonten.json +++ b/services/agent/knowledge/05_schuler-und-lehrkraftekonten.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-SCHULER-UND-LEHRKRAFTEKONTEN-SELECT", - "title": "Schüler- und Lehrkräftekonten", - "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Schüler- und Lehrkräftekonten. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn Konten für Schüler, Lehrkräfte oder schulische Gruppen angelegt, geändert, zurückgesetzt, synchronisiert oder deaktiviert werden müssen. Typische Ticketformulierungen sind: „Schülerpasswort zurücksetzen“; „Lehrkraftkonto anlegen“; „Klassenwechsel synchronisieren“; „Schülerkonto deaktivieren“. Nicht auswählen, wenn es um kommunale Verwaltungsaccounts, reine Fachanwendungsrollen oder Konten externer Anbieter ohne Schulbezug geht. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei zentralem AD- oder Identitätsplattformproblem arbeitet Team Schulen mit Infrastruktur und Backend zusammen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Schüler- und Lehrkräftekonten“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Schulspezifische IT-Systeme \u003e Schüler- und Lehrkräftekonten" - ], - "keywords": [ - "Schülerkonto", - "Lehrerkonto", - "Lehrkräftekonto", - "Schulaccount", - "Klassenkonto", - "Passwort Schule", - "Kontensynchronisation", - "Schüler- und Lehrkräftekonten", - "Schulspezifische IT-Systeme" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/schuler-und-lehrkraftekonten", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-SCHULER-UND-LEHRKRAFTEKONTEN-SELECT", + "title": "Schüler- und Lehrkräftekonten", + "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Schüler- und Lehrkräftekonten. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn Konten für Schüler, Lehrkräfte oder schulische Gruppen angelegt, geändert, zurückgesetzt, synchronisiert oder deaktiviert werden müssen. Typische Ticketformulierungen sind: „Schülerpasswort zurücksetzen“; „Lehrkraftkonto anlegen“; „Klassenwechsel synchronisieren“; „Schülerkonto deaktivieren“. Nicht auswählen, wenn es um kommunale Verwaltungsaccounts, reine Fachanwendungsrollen oder Konten externer Anbieter ohne Schulbezug geht. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei zentralem AD- oder Identitätsplattformproblem arbeitet Team Schulen mit Infrastruktur und Backend zusammen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Schüler- und Lehrkräftekonten“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Schulspezifische IT-Systeme \u003e Schüler- und Lehrkräftekonten" + ], + "keywords": [ + "Schülerkonto", + "Lehrerkonto", + "Lehrkräftekonto", + "Schulaccount", + "Klassenkonto", + "Passwort Schule", + "Kontensynchronisation", + "Schüler- und Lehrkräftekonten", + "Schulspezifische IT-Systeme" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/schuler-und-lehrkraftekonten", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/05_softwareinstallation-am-arbeitsplatz.json b/services/agent/knowledge/05_softwareinstallation-am-arbeitsplatz.json index 27bbac2..0b03c12 100644 --- a/services/agent/knowledge/05_softwareinstallation-am-arbeitsplatz.json +++ b/services/agent/knowledge/05_softwareinstallation-am-arbeitsplatz.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-SOFTWAREINSTALLATION-AM-ARBEITSPLATZ-SELECT", - "title": "Softwareinstallation am Arbeitsplatz", - "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Softwareinstallation am Arbeitsplatz. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn freigegebene Standardsoftware auf einem einzelnen Arbeitsplatz installiert, aktualisiert, repariert oder deinstalliert werden soll und keine zentrale Plattformänderung erforderlich ist. Typische Ticketformulierungen sind: „PDF-Programm installieren“; „Freigegebene Software fehlt“; „Client-Anwendung neu installieren“; „Programm deinstallieren“. Nicht auswählen, wenn eine neue, bisher nicht freigegebene Software beschafft oder fachlich eingeführt werden soll; wenn ein Server, Container oder eine größere Clientverteilung betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Neue oder lizenzpflichtige Software wird zunächst über Beschaffung beziehungsweise Fachanwendungen geprüft.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Softwareinstallation am Arbeitsplatz“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Arbeitsplatz und Endgeräte \u003e Softwareinstallation am Arbeitsplatz" - ], - "keywords": [ - "Softwareinstallation", - "Programm installieren", - "Anwendung installieren", - "Client", - "Setup", - "Deinstallation", - "Standardsoftware", - "Software fehlt", - "Neuinstallation", - "Softwareinstallation am Arbeitsplatz", - "Arbeitsplatz und Endgeräte" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/softwareinstallation-am-arbeitsplatz", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-SOFTWAREINSTALLATION-AM-ARBEITSPLATZ-SELECT", + "title": "Softwareinstallation am Arbeitsplatz", + "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Softwareinstallation am Arbeitsplatz. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn freigegebene Standardsoftware auf einem einzelnen Arbeitsplatz installiert, aktualisiert, repariert oder deinstalliert werden soll und keine zentrale Plattformänderung erforderlich ist. Typische Ticketformulierungen sind: „PDF-Programm installieren“; „Freigegebene Software fehlt“; „Client-Anwendung neu installieren“; „Programm deinstallieren“. Nicht auswählen, wenn eine neue, bisher nicht freigegebene Software beschafft oder fachlich eingeführt werden soll; wenn ein Server, Container oder eine größere Clientverteilung betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Neue oder lizenzpflichtige Software wird zunächst über Beschaffung beziehungsweise Fachanwendungen geprüft.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Softwareinstallation am Arbeitsplatz“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Arbeitsplatz und Endgeräte \u003e Softwareinstallation am Arbeitsplatz" + ], + "keywords": [ + "Softwareinstallation", + "Programm installieren", + "Anwendung installieren", + "Client", + "Setup", + "Deinstallation", + "Standardsoftware", + "Software fehlt", + "Neuinstallation", + "Softwareinstallation am Arbeitsplatz", + "Arbeitsplatz und Endgeräte" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/softwareinstallation-am-arbeitsplatz", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/05_telekommunikationsvertrag.json b/services/agent/knowledge/05_telekommunikationsvertrag.json index f2326c1..737ee8c 100644 --- a/services/agent/knowledge/05_telekommunikationsvertrag.json +++ b/services/agent/knowledge/05_telekommunikationsvertrag.json @@ -1,25 +1,25 @@ -{ - "id": "KAT-TELEKOMMUNIKATIONSVERTRAG-SELECT", - "title": "Telekommunikationsvertrag", - "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Telekommunikationsvertrag. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn Mobilfunk-, Festnetz-, Datenleitungs- oder sonstige Telekommunikationsverträge neu abgeschlossen, verlängert, angepasst oder gekündigt werden sollen. Typische Ticketformulierungen sind: „Mobilfunkvertrag verlängern“; „Festnetzvertrag kündigen“; „Datentarif anpassen“; „Providerangebot prüfen“. Nicht auswählen, wenn eine technische Telefonstörung, SIM-Sperre oder Rufumleitung betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Bedarfe werden mit Support und Infrastruktur abgestimmt.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Telekommunikationsvertrag“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Beschaffung, Verträge und Lizenzen \u003e Telekommunikationsvertrag" - ], - "keywords": [ - "Telekommunikationsvertrag", - "Mobilfunkvertrag", - "Festnetzvertrag", - "Providervertrag", - "Tarif", - "Vertragsverlängerung", - "Kündigung", - "Beschaffung, Verträge und Lizenzen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/telekommunikationsvertrag", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-TELEKOMMUNIKATIONSVERTRAG-SELECT", + "title": "Telekommunikationsvertrag", + "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Telekommunikationsvertrag. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn Mobilfunk-, Festnetz-, Datenleitungs- oder sonstige Telekommunikationsverträge neu abgeschlossen, verlängert, angepasst oder gekündigt werden sollen. Typische Ticketformulierungen sind: „Mobilfunkvertrag verlängern“; „Festnetzvertrag kündigen“; „Datentarif anpassen“; „Providerangebot prüfen“. Nicht auswählen, wenn eine technische Telefonstörung, SIM-Sperre oder Rufumleitung betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Bedarfe werden mit Support und Infrastruktur abgestimmt.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Telekommunikationsvertrag“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Beschaffung, Verträge und Lizenzen \u003e Telekommunikationsvertrag" + ], + "keywords": [ + "Telekommunikationsvertrag", + "Mobilfunkvertrag", + "Festnetzvertrag", + "Providervertrag", + "Tarif", + "Vertragsverlängerung", + "Kündigung", + "Beschaffung, Verträge und Lizenzen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/telekommunikationsvertrag", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/05_virenschutz.json b/services/agent/knowledge/05_virenschutz.json index ca79ebb..2565444 100644 --- a/services/agent/knowledge/05_virenschutz.json +++ b/services/agent/knowledge/05_virenschutz.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-VIRENSCHUTZ-SELECT", - "title": "Virenschutz", - "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Virenschutz. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Virenscanner oder EDR-Agent nicht läuft, Signaturen veraltet sind, Richtlinien nicht greifen, ein Agent fehlt oder Quarantäne- und Ausnahmeregeln administriert werden müssen. Typische Ticketformulierungen sind: „Virenscanner nicht aktiv“; „Signaturen veraltet“; „EDR-Agent offline“; „Ausnahme prüfen“; „Quarantäne verwalten“. Nicht auswählen, wenn bereits Malware gefunden wurde oder ein allgemeines Betriebssystemupdate betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Ein konkreter Schadsoftwarefund wird unter Schadsoftware und Virenfund klassifiziert.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Virenschutz“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "IT-Sicherheit und Datensicherung \u003e Virenschutz" - ], - "keywords": [ - "Virenscanner", - "Antivirus", - "EDR", - "Signatur", - "Agent", - "Quarantäne", - "Ausnahme", - "Schutzstatus", - "Defender", - "Virenschutz", - "IT-Sicherheit und Datensicherung" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/virenschutz", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-VIRENSCHUTZ-SELECT", + "title": "Virenschutz", + "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Virenschutz. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Virenscanner oder EDR-Agent nicht läuft, Signaturen veraltet sind, Richtlinien nicht greifen, ein Agent fehlt oder Quarantäne- und Ausnahmeregeln administriert werden müssen. Typische Ticketformulierungen sind: „Virenscanner nicht aktiv“; „Signaturen veraltet“; „EDR-Agent offline“; „Ausnahme prüfen“; „Quarantäne verwalten“. Nicht auswählen, wenn bereits Malware gefunden wurde oder ein allgemeines Betriebssystemupdate betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Ein konkreter Schadsoftwarefund wird unter Schadsoftware und Virenfund klassifiziert.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Virenschutz“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "IT-Sicherheit und Datensicherung \u003e Virenschutz" + ], + "keywords": [ + "Virenscanner", + "Antivirus", + "EDR", + "Signatur", + "Agent", + "Quarantäne", + "Ausnahme", + "Schutzstatus", + "Defender", + "Virenschutz", + "IT-Sicherheit und Datensicherung" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/virenschutz", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/05_vpn.json b/services/agent/knowledge/05_vpn.json index 6bbeda0..fc07aea 100644 --- a/services/agent/knowledge/05_vpn.json +++ b/services/agent/knowledge/05_vpn.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-VPN-SELECT", - "title": "VPN", - "text": "Auswahlziel: Netzwerk und Verbindungen \u003e VPN. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn ein bereits eingerichteter VPN-Tunnel technisch nicht aufgebaut wird, abbricht, keine internen Ziele erreicht oder eine Client- beziehungsweise Gateway-Fehlermeldung zeigt. Typische Ticketformulierungen sind: „VPN verbindet nicht“; „Tunnel bricht ab“; „Interne Laufwerke über VPN nicht erreichbar“; „VPN-Client meldet Fehler“. Nicht auswählen, wenn der VPN-Zugang erstmals beantragt oder berechtigt werden soll; dafür ist Benutzerkonten und Berechtigungen \u003e Remotezugriff und VPN vorgesehen. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Berechtigungsfragen und technische Störungen sind bewusst getrennt, um Fehlzuordnungen zu vermeiden.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e VPN“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Netzwerk und Verbindungen \u003e VPN" - ], - "keywords": [ - "VPN", - "Tunnel", - "VPN-Client", - "Remote Access", - "Verbindung von außen", - "Gateway", - "VPN Fehler", - "Homeoffice Verbindung", - "Netzwerk und Verbindungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/vpn", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-VPN-SELECT", + "title": "VPN", + "text": "Auswahlziel: Netzwerk und Verbindungen \u003e VPN. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn ein bereits eingerichteter VPN-Tunnel technisch nicht aufgebaut wird, abbricht, keine internen Ziele erreicht oder eine Client- beziehungsweise Gateway-Fehlermeldung zeigt. Typische Ticketformulierungen sind: „VPN verbindet nicht“; „Tunnel bricht ab“; „Interne Laufwerke über VPN nicht erreichbar“; „VPN-Client meldet Fehler“. Nicht auswählen, wenn der VPN-Zugang erstmals beantragt oder berechtigt werden soll; dafür ist Benutzerkonten und Berechtigungen \u003e Remotezugriff und VPN vorgesehen. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Berechtigungsfragen und technische Störungen sind bewusst getrennt, um Fehlzuordnungen zu vermeiden.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e VPN“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Netzwerk und Verbindungen \u003e VPN" + ], + "keywords": [ + "VPN", + "Tunnel", + "VPN-Client", + "Remote Access", + "Verbindung von außen", + "Gateway", + "VPN Fehler", + "Homeoffice Verbindung", + "Netzwerk und Verbindungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/vpn", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/06_betriebssystem-am-arbeitsplatz.json b/services/agent/knowledge/06_betriebssystem-am-arbeitsplatz.json index 1a90a2e..a346ab5 100644 --- a/services/agent/knowledge/06_betriebssystem-am-arbeitsplatz.json +++ b/services/agent/knowledge/06_betriebssystem-am-arbeitsplatz.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-BETRIEBSSYSTEM-AM-ARBEITSPLATZ-SELECT", - "title": "Betriebssystem am Arbeitsplatz", - "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Betriebssystem am Arbeitsplatz. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn Windows oder ein anderes Client-Betriebssystem an einem einzelnen Arbeitsplatz lokale Fehler zeigt, Updates scheitern, Anmeldung am Gerät fehlerhaft ist oder Systemeinstellungen repariert werden müssen. Typische Ticketformulierungen sind: „Windows-Update schlägt fehl“; „Benutzerprofil defekt“; „Startmenü funktioniert nicht“; „Lokale Anmeldung fehlerhaft“. Nicht auswählen, wenn die Ursache eine zentrale Gruppenrichtlinie, das Active Directory, eine flächige Update-Störung oder ein Serverbetriebssystem ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Zentrale Richtlinien und mehrere gleichzeitig betroffene Clients werden an Infrastruktur und Backend übergeben.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Betriebssystem am Arbeitsplatz“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Arbeitsplatz und Endgeräte \u003e Betriebssystem am Arbeitsplatz" - ], - "keywords": [ - "Windows", - "Betriebssystem", - "Client", - "Windows Update", - "Benutzerprofil", - "Startmenü", - "lokale Anmeldung", - "Systemfehler", - "Treiberproblem", - "Betriebssystem am Arbeitsplatz", - "Arbeitsplatz und Endgeräte" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/betriebssystem-am-arbeitsplatz", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-BETRIEBSSYSTEM-AM-ARBEITSPLATZ-SELECT", + "title": "Betriebssystem am Arbeitsplatz", + "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Betriebssystem am Arbeitsplatz. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn Windows oder ein anderes Client-Betriebssystem an einem einzelnen Arbeitsplatz lokale Fehler zeigt, Updates scheitern, Anmeldung am Gerät fehlerhaft ist oder Systemeinstellungen repariert werden müssen. Typische Ticketformulierungen sind: „Windows-Update schlägt fehl“; „Benutzerprofil defekt“; „Startmenü funktioniert nicht“; „Lokale Anmeldung fehlerhaft“. Nicht auswählen, wenn die Ursache eine zentrale Gruppenrichtlinie, das Active Directory, eine flächige Update-Störung oder ein Serverbetriebssystem ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Zentrale Richtlinien und mehrere gleichzeitig betroffene Clients werden an Infrastruktur und Backend übergeben.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Betriebssystem am Arbeitsplatz“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Arbeitsplatz und Endgeräte \u003e Betriebssystem am Arbeitsplatz" + ], + "keywords": [ + "Windows", + "Betriebssystem", + "Client", + "Windows Update", + "Benutzerprofil", + "Startmenü", + "lokale Anmeldung", + "Systemfehler", + "Treiberproblem", + "Betriebssystem am Arbeitsplatz", + "Arbeitsplatz und Endgeräte" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/betriebssystem-am-arbeitsplatz", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/06_dns-und-dhcp.json b/services/agent/knowledge/06_dns-und-dhcp.json index 437105e..d2910bf 100644 --- a/services/agent/knowledge/06_dns-und-dhcp.json +++ b/services/agent/knowledge/06_dns-und-dhcp.json @@ -1,29 +1,29 @@ -{ - "id": "KAT-DNS-UND-DHCP-SELECT", - "title": "DNS und DHCP", - "text": "Auswahlziel: Netzwerk und Verbindungen \u003e DNS und DHCP. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Namensauflösung, DNS-Einträge, IP-Adressvergabe, DHCP-Leases, Reservierungen oder damit verbundene zentrale Netzwerkfunktionen fehlerhaft sind oder geändert werden sollen. Typische Ticketformulierungen sind: „Hostname wird nicht aufgelöst“; „Falsche IP-Adresse“; „DHCP-Reservierung anlegen“; „DNS-Eintrag ändern“. Nicht auswählen, wenn lediglich ein Benutzer keine Internetverbindung hat, eine Firewallfreigabe benötigt wird oder eine Fachanwendung einen eigenen Namensfehler meldet. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Einzelne Symptome ohne technische Hinweise sollten zunächst unter LAN, WLAN oder Internetzugang eingeordnet werden.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e DNS und DHCP“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Netzwerk und Verbindungen \u003e DNS und DHCP" - ], - "keywords": [ - "DNS", - "DHCP", - "Namensauflösung", - "IP-Adresse", - "Lease", - "Reservierung", - "Hostname", - "A-Record", - "CNAME", - "IP-Vergabe", - "DNS und DHCP", - "Netzwerk und Verbindungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/dns-und-dhcp", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-DNS-UND-DHCP-SELECT", + "title": "DNS und DHCP", + "text": "Auswahlziel: Netzwerk und Verbindungen \u003e DNS und DHCP. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Namensauflösung, DNS-Einträge, IP-Adressvergabe, DHCP-Leases, Reservierungen oder damit verbundene zentrale Netzwerkfunktionen fehlerhaft sind oder geändert werden sollen. Typische Ticketformulierungen sind: „Hostname wird nicht aufgelöst“; „Falsche IP-Adresse“; „DHCP-Reservierung anlegen“; „DNS-Eintrag ändern“. Nicht auswählen, wenn lediglich ein Benutzer keine Internetverbindung hat, eine Firewallfreigabe benötigt wird oder eine Fachanwendung einen eigenen Namensfehler meldet. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Einzelne Symptome ohne technische Hinweise sollten zunächst unter LAN, WLAN oder Internetzugang eingeordnet werden.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e DNS und DHCP“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Netzwerk und Verbindungen \u003e DNS und DHCP" + ], + "keywords": [ + "DNS", + "DHCP", + "Namensauflösung", + "IP-Adresse", + "Lease", + "Reservierung", + "Hostname", + "A-Record", + "CNAME", + "IP-Vergabe", + "DNS und DHCP", + "Netzwerk und Verbindungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/dns-und-dhcp", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/06_fachanwendung-bericht-und-auswertung.json b/services/agent/knowledge/06_fachanwendung-bericht-und-auswertung.json index 1b962f6..b8722b5 100644 --- a/services/agent/knowledge/06_fachanwendung-bericht-und-auswertung.json +++ b/services/agent/knowledge/06_fachanwendung-bericht-und-auswertung.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-FACHANWENDUNG-BERICHT-UND-AUSWERTUNG-SELECT", - "title": "Fachanwendung – Bericht und Auswertung", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Bericht und Auswertung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn ein vorhandener Bericht, eine Liste, Statistik, Druckausgabe oder fachliche Auswertung fehlerhaft ist, angepasst oder bereitgestellt werden soll. Typische Ticketformulierungen sind: „Bericht zeigt falsche Spalten“; „Statistik fehlt“; „Auswertung anpassen“; „Druckausgabe aus Fachverfahren fehlerhaft“. Nicht auswählen, wenn eine allgemeine Excel-Auswertung ohne Fachanwendungsbezug gemeint ist, ein Drucker physisch nicht druckt oder eine komplett neue Fachfunktion benötigt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Dateninhalt und Filterlogik gehören zu Fachanwendungen; physische Druckprobleme zum Support.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Bericht und Auswertung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Bericht und Auswertung" - ], - "keywords": [ - "Bericht", - "Auswertung", - "Statistik", - "Liste", - "Reporting", - "Druckausgabe", - "Abfrage", - "Kennzahl", - "Fachanwendung Bericht", - "Fachanwendung – Bericht und Auswertung", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-bericht-und-auswertung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-FACHANWENDUNG-BERICHT-UND-AUSWERTUNG-SELECT", + "title": "Fachanwendung – Bericht und Auswertung", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Bericht und Auswertung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn ein vorhandener Bericht, eine Liste, Statistik, Druckausgabe oder fachliche Auswertung fehlerhaft ist, angepasst oder bereitgestellt werden soll. Typische Ticketformulierungen sind: „Bericht zeigt falsche Spalten“; „Statistik fehlt“; „Auswertung anpassen“; „Druckausgabe aus Fachverfahren fehlerhaft“. Nicht auswählen, wenn eine allgemeine Excel-Auswertung ohne Fachanwendungsbezug gemeint ist, ein Drucker physisch nicht druckt oder eine komplett neue Fachfunktion benötigt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Dateninhalt und Filterlogik gehören zu Fachanwendungen; physische Druckprobleme zum Support.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Bericht und Auswertung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Bericht und Auswertung" + ], + "keywords": [ + "Bericht", + "Auswertung", + "Statistik", + "Liste", + "Reporting", + "Druckausgabe", + "Abfrage", + "Kennzahl", + "Fachanwendung Bericht", + "Fachanwendung – Bericht und Auswertung", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-bericht-und-auswertung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/06_mobile-schulgerate.json b/services/agent/knowledge/06_mobile-schulgerate.json index 449a78f..4d7385a 100644 --- a/services/agent/knowledge/06_mobile-schulgerate.json +++ b/services/agent/knowledge/06_mobile-schulgerate.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-MOBILE-SCHULGERATE-SELECT", - "title": "Mobile Schulgeräte", - "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Mobile Schulgeräte. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn schulische Tablets, Notebooks, Leihgeräte oder Gerätekoffer eingerichtet, ausgegeben, zurückgenommen, repariert oder im Unterricht unterstützt werden müssen. Typische Ticketformulierungen sind: „Schüler-iPad defekt“; „Tablet-Koffer einrichten“; „Leihgerät zurücknehmen“; „Schulnotebook startet nicht“. Nicht auswählen, wenn ausschließlich das zentrale MDM, ein privates Gerät oder ein allgemeines Verwaltungs-Smartphone betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: MDM-Profil- und Plattformprobleme werden in Mobile-Device-Management für Schulen erfasst.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Mobile Schulgeräte“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Schulspezifische IT-Systeme \u003e Mobile Schulgeräte" - ], - "keywords": [ - "Schultablet", - "Schul-iPad", - "Leihgerät", - "Tablet-Koffer", - "Schulnotebook", - "mobiles Schulgerät", - "Schülergerät", - "Mobile Schulgeräte", - "Schulspezifische IT-Systeme" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/mobile-schulgerate", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-MOBILE-SCHULGERATE-SELECT", + "title": "Mobile Schulgeräte", + "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Mobile Schulgeräte. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn schulische Tablets, Notebooks, Leihgeräte oder Gerätekoffer eingerichtet, ausgegeben, zurückgenommen, repariert oder im Unterricht unterstützt werden müssen. Typische Ticketformulierungen sind: „Schüler-iPad defekt“; „Tablet-Koffer einrichten“; „Leihgerät zurücknehmen“; „Schulnotebook startet nicht“. Nicht auswählen, wenn ausschließlich das zentrale MDM, ein privates Gerät oder ein allgemeines Verwaltungs-Smartphone betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: MDM-Profil- und Plattformprobleme werden in Mobile-Device-Management für Schulen erfasst.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Mobile Schulgeräte“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Schulspezifische IT-Systeme \u003e Mobile Schulgeräte" + ], + "keywords": [ + "Schultablet", + "Schul-iPad", + "Leihgerät", + "Tablet-Koffer", + "Schulnotebook", + "mobiles Schulgerät", + "Schülergerät", + "Mobile Schulgeräte", + "Schulspezifische IT-Systeme" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/mobile-schulgerate", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/06_scan-to-mail-und-scan-to-folder.json b/services/agent/knowledge/06_scan-to-mail-und-scan-to-folder.json index e5cc27a..354be22 100644 --- a/services/agent/knowledge/06_scan-to-mail-und-scan-to-folder.json +++ b/services/agent/knowledge/06_scan-to-mail-und-scan-to-folder.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-SCAN-TO-MAIL-UND-SCAN-TO-FOLDER-SELECT", - "title": "Scan-to-Mail und Scan-to-Folder", - "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Scan-to-Mail und Scan-to-Folder. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Scanner oder Multifunktionsgerät Scans nicht per E-Mail versendet oder nicht in einen zentralen Ordner ablegt, obwohl das Scannen selbst funktioniert. Typische Ticketformulierungen sind: „Scan kommt nicht per Mail an“; „Scan-to-Folder schlägt fehl“; „Zielordner nicht erreichbar“; „SMTP-Fehler am Kopierer“. Nicht auswählen, wenn das Gerät generell nicht scannt, das E-Mail-System organisationsweit ausfällt oder eine allgemeine Dateifreigabe gestört ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Support übernimmt Erstprüfung; Mail-, Netzwerk- und Dateidienste werden bei zentraler Ursache an Infrastruktur übergeben.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Scan-to-Mail und Scan-to-Folder“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Drucken, Scannen und Kopieren \u003e Scan-to-Mail und Scan-to-Folder" - ], - "keywords": [ - "Scan-to-Mail", - "Scan-to-Folder", - "SMTP", - "Zielordner", - "Scanversand", - "Netzwerkordner", - "Scannen per E-Mail", - "Scan-to-Mail und Scan-to-Folder", - "Drucken, Scannen und Kopieren" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/scan-to-mail-und-scan-to-folder", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-SCAN-TO-MAIL-UND-SCAN-TO-FOLDER-SELECT", + "title": "Scan-to-Mail und Scan-to-Folder", + "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Scan-to-Mail und Scan-to-Folder. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Scanner oder Multifunktionsgerät Scans nicht per E-Mail versendet oder nicht in einen zentralen Ordner ablegt, obwohl das Scannen selbst funktioniert. Typische Ticketformulierungen sind: „Scan kommt nicht per Mail an“; „Scan-to-Folder schlägt fehl“; „Zielordner nicht erreichbar“; „SMTP-Fehler am Kopierer“. Nicht auswählen, wenn das Gerät generell nicht scannt, das E-Mail-System organisationsweit ausfällt oder eine allgemeine Dateifreigabe gestört ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Support übernimmt Erstprüfung; Mail-, Netzwerk- und Dateidienste werden bei zentraler Ursache an Infrastruktur übergeben.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Scan-to-Mail und Scan-to-Folder“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Drucken, Scannen und Kopieren \u003e Scan-to-Mail und Scan-to-Folder" + ], + "keywords": [ + "Scan-to-Mail", + "Scan-to-Folder", + "SMTP", + "Zielordner", + "Scanversand", + "Netzwerkordner", + "Scannen per E-Mail", + "Scan-to-Mail und Scan-to-Folder", + "Drucken, Scannen und Kopieren" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/scan-to-mail-und-scan-to-folder", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/06_sicherheitsupdate.json b/services/agent/knowledge/06_sicherheitsupdate.json index ef944da..e9d7993 100644 --- a/services/agent/knowledge/06_sicherheitsupdate.json +++ b/services/agent/knowledge/06_sicherheitsupdate.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-SICHERHEITSUPDATE-SELECT", - "title": "Sicherheitsupdate", - "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Sicherheitsupdate. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn ein sicherheitskritischer Patch, Herstellerhinweis oder dringendes Update bewertet, getestet und zeitnah auf Servern, Plattformen oder zentralen Komponenten ausgerollt werden soll. Typische Ticketformulierungen sind: „Kritischen Patch einspielen“; „Hersteller meldet Security Update“; „Zero-Day-Patch planen“; „Sicherheitsaktualisierung verteilen“. Nicht auswählen, wenn es um ein reguläres Fachanwendungsrelease, ein einzelnes Clientupdate ohne Sicherheitsbezug oder eine bereits ausgenutzte Schwachstelle geht. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei bestätigter aktiver Ausnutzung zusätzlich Sicherheitsvorfall wählen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Sicherheitsupdate“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "IT-Sicherheit und Datensicherung \u003e Sicherheitsupdate" - ], - "keywords": [ - "Sicherheitsupdate", - "Security Patch", - "kritischer Patch", - "Zero Day", - "CVE Patch", - "Update", - "Herstellerwarnung", - "Patchmanagement", - "IT-Sicherheit und Datensicherung" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/sicherheitsupdate", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-SICHERHEITSUPDATE-SELECT", + "title": "Sicherheitsupdate", + "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Sicherheitsupdate. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn ein sicherheitskritischer Patch, Herstellerhinweis oder dringendes Update bewertet, getestet und zeitnah auf Servern, Plattformen oder zentralen Komponenten ausgerollt werden soll. Typische Ticketformulierungen sind: „Kritischen Patch einspielen“; „Hersteller meldet Security Update“; „Zero-Day-Patch planen“; „Sicherheitsaktualisierung verteilen“. Nicht auswählen, wenn es um ein reguläres Fachanwendungsrelease, ein einzelnes Clientupdate ohne Sicherheitsbezug oder eine bereits ausgenutzte Schwachstelle geht. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei bestätigter aktiver Ausnutzung zusätzlich Sicherheitsvorfall wählen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Sicherheitsupdate“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "IT-Sicherheit und Datensicherung \u003e Sicherheitsupdate" + ], + "keywords": [ + "Sicherheitsupdate", + "Security Patch", + "kritischer Patch", + "Zero Day", + "CVE Patch", + "Update", + "Herstellerwarnung", + "Patchmanagement", + "IT-Sicherheit und Datensicherung" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/sicherheitsupdate", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/06_videokonferenz.json b/services/agent/knowledge/06_videokonferenz.json index b3a1e19..fc5114f 100644 --- a/services/agent/knowledge/06_videokonferenz.json +++ b/services/agent/knowledge/06_videokonferenz.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-VIDEOKONFERENZ-SELECT", - "title": "Videokonferenz", - "text": "Auswahlziel: Telefonie und Kommunikation \u003e Videokonferenz. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn Teilnahme, Kamera, Mikrofon, Lautsprecher, Bildschirmfreigabe oder Bedienung bei einer Videokonferenz am Arbeitsplatz nicht funktioniert. Typische Ticketformulierungen sind: „Kamera in Besprechung nicht verfügbar“; „Mikrofon wird nicht erkannt“; „Keine Tonwiedergabe in Videokonferenz“; „Bildschirmfreigabe klappt nicht“. Nicht auswählen, wenn ein zentrales Konferenzsystem, Netzwerkstandort oder eine Fachanwendung ohne Konferenzbezug betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei vielen gleichzeitig Betroffenen oder zentralem Dienstausfall an Infrastruktur beziehungsweise zuständige Plattformbetreuung übergeben.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Videokonferenz“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Telefonie und Kommunikation \u003e Videokonferenz" - ], - "keywords": [ - "Videokonferenz", - "Kamera", - "Mikrofon", - "Besprechung", - "Meeting", - "Bildschirmfreigabe", - "Teams-Konferenz", - "Webex", - "Zoom", - "Telefonie und Kommunikation" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/videokonferenz", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-VIDEOKONFERENZ-SELECT", + "title": "Videokonferenz", + "text": "Auswahlziel: Telefonie und Kommunikation \u003e Videokonferenz. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn Teilnahme, Kamera, Mikrofon, Lautsprecher, Bildschirmfreigabe oder Bedienung bei einer Videokonferenz am Arbeitsplatz nicht funktioniert. Typische Ticketformulierungen sind: „Kamera in Besprechung nicht verfügbar“; „Mikrofon wird nicht erkannt“; „Keine Tonwiedergabe in Videokonferenz“; „Bildschirmfreigabe klappt nicht“. Nicht auswählen, wenn ein zentrales Konferenzsystem, Netzwerkstandort oder eine Fachanwendung ohne Konferenzbezug betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei vielen gleichzeitig Betroffenen oder zentralem Dienstausfall an Infrastruktur beziehungsweise zuständige Plattformbetreuung übergeben.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Videokonferenz“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Telefonie und Kommunikation \u003e Videokonferenz" + ], + "keywords": [ + "Videokonferenz", + "Kamera", + "Mikrofon", + "Besprechung", + "Meeting", + "Bildschirmfreigabe", + "Teams-Konferenz", + "Webex", + "Zoom", + "Telefonie und Kommunikation" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/videokonferenz", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/06_virtuelle-maschinen.json b/services/agent/knowledge/06_virtuelle-maschinen.json index c177333..0440b40 100644 --- a/services/agent/knowledge/06_virtuelle-maschinen.json +++ b/services/agent/knowledge/06_virtuelle-maschinen.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-VIRTUELLE-MASCHINEN-SELECT", - "title": "Virtuelle Maschinen", - "text": "Auswahlziel: Server, Backend und Plattformen \u003e Virtuelle Maschinen. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn eine konkrete virtuelle Maschine neu bereitgestellt, geändert, vergrößert, geklont, gestartet, wiederhergestellt oder bei einem VM-spezifischen Fehler bearbeitet werden soll. Typische Ticketformulierungen sind: „Neue VM bereitstellen“; „Virtuelle Maschine startet nicht“; „RAM oder CPU erhöhen“; „VM klonen“. Nicht auswählen, wenn der gesamte Hypervisor-Cluster betroffen ist, nur die Anwendung in der VM fehlerhaft ist oder ein Container benötigt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Betriebssystem- und Anwendungsprobleme innerhalb der VM sind getrennt zu betrachten.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Virtuelle Maschinen“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Server, Backend und Plattformen \u003e Virtuelle Maschinen" - ], - "keywords": [ - "virtuelle Maschine", - "VM", - "vCPU", - "virtueller Server", - "VM bereitstellen", - "VM startet nicht", - "Snapshot", - "Ressourcen erhöhen", - "Virtuelle Maschinen", - "Server, Backend und Plattformen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/virtuelle-maschinen", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-VIRTUELLE-MASCHINEN-SELECT", + "title": "Virtuelle Maschinen", + "text": "Auswahlziel: Server, Backend und Plattformen \u003e Virtuelle Maschinen. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn eine konkrete virtuelle Maschine neu bereitgestellt, geändert, vergrößert, geklont, gestartet, wiederhergestellt oder bei einem VM-spezifischen Fehler bearbeitet werden soll. Typische Ticketformulierungen sind: „Neue VM bereitstellen“; „Virtuelle Maschine startet nicht“; „RAM oder CPU erhöhen“; „VM klonen“. Nicht auswählen, wenn der gesamte Hypervisor-Cluster betroffen ist, nur die Anwendung in der VM fehlerhaft ist oder ein Container benötigt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Betriebssystem- und Anwendungsprobleme innerhalb der VM sind getrennt zu betrachten.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Virtuelle Maschinen“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Server, Backend und Plattformen \u003e Virtuelle Maschinen" + ], + "keywords": [ + "virtuelle Maschine", + "VM", + "vCPU", + "virtueller Server", + "VM bereitstellen", + "VM startet nicht", + "Snapshot", + "Ressourcen erhöhen", + "Virtuelle Maschinen", + "Server, Backend und Plattformen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/virtuelle-maschinen", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/06_wartungs-und-supportvertrag.json b/services/agent/knowledge/06_wartungs-und-supportvertrag.json index 201324a..445f7a0 100644 --- a/services/agent/knowledge/06_wartungs-und-supportvertrag.json +++ b/services/agent/knowledge/06_wartungs-und-supportvertrag.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-WARTUNGS-UND-SUPPORTVERTRAG-SELECT", - "title": "Wartungs- und Supportvertrag", - "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Wartungs- und Supportvertrag. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn Wartungs-, Pflege-, Hersteller- oder Supportverträge abgeschlossen, verlängert, angepasst, geprüft oder gekündigt werden sollen. Typische Ticketformulierungen sind: „Wartungsvertrag verlängern“; „Herstellersupport beauftragen“; „Pflegevertrag prüfen“; „Supportvertrag kündigen“. Nicht auswählen, wenn ein konkreter technischer Supportfall beim Hersteller eröffnet oder eine Rechnung ohne Vertragsänderung geprüft wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Leistungsumfang, Laufzeit, Kündigungsfrist und zuständiges Fachteam sind zu dokumentieren.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Wartungs- und Supportvertrag“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Beschaffung, Verträge und Lizenzen \u003e Wartungs- und Supportvertrag" - ], - "keywords": [ - "Wartungsvertrag", - "Supportvertrag", - "Pflegevertrag", - "Herstellersupport", - "Verlängerung", - "Kündigung", - "SLA", - "Maintenance", - "Wartungs- und Supportvertrag", - "Beschaffung, Verträge und Lizenzen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/wartungs-und-supportvertrag", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-WARTUNGS-UND-SUPPORTVERTRAG-SELECT", + "title": "Wartungs- und Supportvertrag", + "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Wartungs- und Supportvertrag. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn Wartungs-, Pflege-, Hersteller- oder Supportverträge abgeschlossen, verlängert, angepasst, geprüft oder gekündigt werden sollen. Typische Ticketformulierungen sind: „Wartungsvertrag verlängern“; „Herstellersupport beauftragen“; „Pflegevertrag prüfen“; „Supportvertrag kündigen“. Nicht auswählen, wenn ein konkreter technischer Supportfall beim Hersteller eröffnet oder eine Rechnung ohne Vertragsänderung geprüft wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Leistungsumfang, Laufzeit, Kündigungsfrist und zuständiges Fachteam sind zu dokumentieren.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Wartungs- und Supportvertrag“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Beschaffung, Verträge und Lizenzen \u003e Wartungs- und Supportvertrag" + ], + "keywords": [ + "Wartungsvertrag", + "Supportvertrag", + "Pflegevertrag", + "Herstellersupport", + "Verlängerung", + "Kündigung", + "SLA", + "Maintenance", + "Wartungs- und Supportvertrag", + "Beschaffung, Verträge und Lizenzen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/wartungs-und-supportvertrag", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/07_angebot-und-wirtschaftlichkeitsprufung.json b/services/agent/knowledge/07_angebot-und-wirtschaftlichkeitsprufung.json index e957d84..8691689 100644 --- a/services/agent/knowledge/07_angebot-und-wirtschaftlichkeitsprufung.json +++ b/services/agent/knowledge/07_angebot-und-wirtschaftlichkeitsprufung.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-ANGEBOT-UND-WIRTSCHAFTLICHKEITSPRUFUNG-SELECT", - "title": "Angebot und Wirtschaftlichkeitsprüfung", - "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Angebot und Wirtschaftlichkeitsprüfung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn Angebote eingeholt, Preise verglichen, Wirtschaftlichkeit bewertet, Vergabevermerke vorbereitet oder Beschaffungsvarianten kaufmännisch geprüft werden sollen. Typische Ticketformulierungen sind: „Drei Angebote vergleichen“; „Wirtschaftlichkeitsbetrachtung erstellen“; „Vergabe vorbereiten“; „Kostenvarianten bewerten“. Nicht auswählen, wenn die technische Produktauswahl allein im Vordergrund steht oder bereits eine Rechnung zur Zahlung vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Bewertung erfolgt durch das zuständige Fachteam; kaufmännische und vergaberechtliche Bewertung durch Beschaffung.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Angebot und Wirtschaftlichkeitsprüfung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Beschaffung, Verträge und Lizenzen \u003e Angebot und Wirtschaftlichkeitsprüfung" - ], - "keywords": [ - "Angebot", - "Preisvergleich", - "Wirtschaftlichkeit", - "Vergabe", - "Vergabevermerk", - "Kostenvergleich", - "Markterkundung", - "Angebot und Wirtschaftlichkeitsprüfung", - "Beschaffung, Verträge und Lizenzen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/angebot-und-wirtschaftlichkeitsprufung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-ANGEBOT-UND-WIRTSCHAFTLICHKEITSPRUFUNG-SELECT", + "title": "Angebot und Wirtschaftlichkeitsprüfung", + "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Angebot und Wirtschaftlichkeitsprüfung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn Angebote eingeholt, Preise verglichen, Wirtschaftlichkeit bewertet, Vergabevermerke vorbereitet oder Beschaffungsvarianten kaufmännisch geprüft werden sollen. Typische Ticketformulierungen sind: „Drei Angebote vergleichen“; „Wirtschaftlichkeitsbetrachtung erstellen“; „Vergabe vorbereiten“; „Kostenvarianten bewerten“. Nicht auswählen, wenn die technische Produktauswahl allein im Vordergrund steht oder bereits eine Rechnung zur Zahlung vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Bewertung erfolgt durch das zuständige Fachteam; kaufmännische und vergaberechtliche Bewertung durch Beschaffung.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Angebot und Wirtschaftlichkeitsprüfung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Beschaffung, Verträge und Lizenzen \u003e Angebot und Wirtschaftlichkeitsprüfung" + ], + "keywords": [ + "Angebot", + "Preisvergleich", + "Wirtschaftlichkeit", + "Vergabe", + "Vergabevermerk", + "Kostenvergleich", + "Markterkundung", + "Angebot und Wirtschaftlichkeitsprüfung", + "Beschaffung, Verträge und Lizenzen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/angebot-und-wirtschaftlichkeitsprufung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/07_datensicherung.json b/services/agent/knowledge/07_datensicherung.json index 644da16..46470f6 100644 --- a/services/agent/knowledge/07_datensicherung.json +++ b/services/agent/knowledge/07_datensicherung.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-DATENSICHERUNG-SELECT", - "title": "Datensicherung", - "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Datensicherung. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Backup-Jobs fehlschlagen, Sicherungspläne, Aufbewahrung, Sicherungsziele, Kapazitäten oder Backup-Konzepte eingerichtet, geändert oder geprüft werden müssen. Typische Ticketformulierungen sind: „Backup fehlgeschlagen“; „Sicherungsjob rot“; „Aufbewahrung ändern“; „Neues System in Datensicherung aufnehmen“. Nicht auswählen, wenn konkrete Daten wiederhergestellt werden sollen oder eine Fachanwendung lediglich keine Exportdatei erzeugt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Wiederherstellungsanforderungen werden getrennt unter Datenwiederherstellung erfasst.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Datensicherung“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "IT-Sicherheit und Datensicherung \u003e Datensicherung" - ], - "keywords": [ - "Datensicherung", - "Backup", - "Sicherungsjob", - "Aufbewahrung", - "Retention", - "Backupziel", - "Sicherungskonzept", - "Backup fehlgeschlagen", - "IT-Sicherheit und Datensicherung" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/datensicherung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-DATENSICHERUNG-SELECT", + "title": "Datensicherung", + "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Datensicherung. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Backup-Jobs fehlschlagen, Sicherungspläne, Aufbewahrung, Sicherungsziele, Kapazitäten oder Backup-Konzepte eingerichtet, geändert oder geprüft werden müssen. Typische Ticketformulierungen sind: „Backup fehlgeschlagen“; „Sicherungsjob rot“; „Aufbewahrung ändern“; „Neues System in Datensicherung aufnehmen“. Nicht auswählen, wenn konkrete Daten wiederhergestellt werden sollen oder eine Fachanwendung lediglich keine Exportdatei erzeugt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Wiederherstellungsanforderungen werden getrennt unter Datenwiederherstellung erfasst.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Datensicherung“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "IT-Sicherheit und Datensicherung \u003e Datensicherung" + ], + "keywords": [ + "Datensicherung", + "Backup", + "Sicherungsjob", + "Aufbewahrung", + "Retention", + "Backupziel", + "Sicherungskonzept", + "Backup fehlgeschlagen", + "IT-Sicherheit und Datensicherung" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/datensicherung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/07_fachanwendung-update-und-release.json b/services/agent/knowledge/07_fachanwendung-update-und-release.json index c1f2c50..25c1a21 100644 --- a/services/agent/knowledge/07_fachanwendung-update-und-release.json +++ b/services/agent/knowledge/07_fachanwendung-update-und-release.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-FACHANWENDUNG-UPDATE-UND-RELEASE-SELECT", - "title": "Fachanwendung – Update und Release", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Update und Release. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn ein Update, Patch, Releasewechsel oder Versionsupgrade einer Fachanwendung geplant, getestet, freigegeben oder nachbereitet werden soll. Typische Ticketformulierungen sind: „Neue Fachverfahrensversion testen“; „Release einspielen“; „Herstellerupdate planen“; „Patch der Anwendung freigeben“. Nicht auswählen, wenn nur ein Windows-Clientupdate scheitert, ein Sicherheitspatch der Serverplattform betroffen ist oder eine neue Anwendung erstmals eingeführt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Server- und Datenbankarbeiten werden mit Infrastruktur und Backend abgestimmt.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Update und Release“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Update und Release" - ], - "keywords": [ - "Update", - "Release", - "Version", - "Patch", - "Upgrade", - "Herstellerupdate", - "Testsystem", - "Freigabe", - "Fachanwendung aktualisieren", - "Fachanwendung – Update und Release", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-update-und-release", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-FACHANWENDUNG-UPDATE-UND-RELEASE-SELECT", + "title": "Fachanwendung – Update und Release", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Update und Release. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn ein Update, Patch, Releasewechsel oder Versionsupgrade einer Fachanwendung geplant, getestet, freigegeben oder nachbereitet werden soll. Typische Ticketformulierungen sind: „Neue Fachverfahrensversion testen“; „Release einspielen“; „Herstellerupdate planen“; „Patch der Anwendung freigeben“. Nicht auswählen, wenn nur ein Windows-Clientupdate scheitert, ein Sicherheitspatch der Serverplattform betroffen ist oder eine neue Anwendung erstmals eingeführt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Server- und Datenbankarbeiten werden mit Infrastruktur und Backend abgestimmt.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Update und Release“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Update und Release" + ], + "keywords": [ + "Update", + "Release", + "Version", + "Patch", + "Upgrade", + "Herstellerupdate", + "Testsystem", + "Freigabe", + "Fachanwendung aktualisieren", + "Fachanwendung – Update und Release", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-update-und-release", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/07_firewall-freischaltung.json b/services/agent/knowledge/07_firewall-freischaltung.json index ece4875..cca99b4 100644 --- a/services/agent/knowledge/07_firewall-freischaltung.json +++ b/services/agent/knowledge/07_firewall-freischaltung.json @@ -1,29 +1,29 @@ -{ - "id": "KAT-FIREWALL-FREISCHALTUNG-SELECT", - "title": "Firewall-Freischaltung", - "text": "Auswahlziel: Netzwerk und Verbindungen \u003e Firewall-Freischaltung. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn eine konkrete Netzwerkkommunikation durch Firewallregeln erlaubt, geändert, zeitlich begrenzt oder geprüft werden soll, typischerweise mit Quelle, Ziel, Port und Protokoll. Typische Ticketformulierungen sind: „Port 443 zu Zielsystem freischalten“; „Firewall blockiert Anwendung“; „Kommunikation zwischen Netzen erlauben“; „Externer Dienst benötigt Zugriff“. Nicht auswählen, wenn ein allgemeiner Internetausfall, ein VPN-Berechtigungsantrag oder eine fachliche Schnittstellenkonfiguration ohne Firewallbezug vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Unklare pauschale Freischaltungen sind nicht ausreichend; Sicherheitsprüfung und Minimalprinzip gelten.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e Firewall-Freischaltung“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Netzwerk und Verbindungen \u003e Firewall-Freischaltung" - ], - "keywords": [ - "Firewall", - "Freischaltung", - "Port", - "Quelle", - "Ziel", - "Protokoll", - "Regel", - "blockiert", - "Netzwerkfreigabe", - "Whitelist", - "Firewall-Freischaltung", - "Netzwerk und Verbindungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/firewall-freischaltung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-FIREWALL-FREISCHALTUNG-SELECT", + "title": "Firewall-Freischaltung", + "text": "Auswahlziel: Netzwerk und Verbindungen \u003e Firewall-Freischaltung. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn eine konkrete Netzwerkkommunikation durch Firewallregeln erlaubt, geändert, zeitlich begrenzt oder geprüft werden soll, typischerweise mit Quelle, Ziel, Port und Protokoll. Typische Ticketformulierungen sind: „Port 443 zu Zielsystem freischalten“; „Firewall blockiert Anwendung“; „Kommunikation zwischen Netzen erlauben“; „Externer Dienst benötigt Zugriff“. Nicht auswählen, wenn ein allgemeiner Internetausfall, ein VPN-Berechtigungsantrag oder eine fachliche Schnittstellenkonfiguration ohne Firewallbezug vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Unklare pauschale Freischaltungen sind nicht ausreichend; Sicherheitsprüfung und Minimalprinzip gelten.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e Firewall-Freischaltung“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Netzwerk und Verbindungen \u003e Firewall-Freischaltung" + ], + "keywords": [ + "Firewall", + "Freischaltung", + "Port", + "Quelle", + "Ziel", + "Protokoll", + "Regel", + "blockiert", + "Netzwerkfreigabe", + "Whitelist", + "Firewall-Freischaltung", + "Netzwerk und Verbindungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/firewall-freischaltung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/07_funktions-und-sammelpostfacher.json b/services/agent/knowledge/07_funktions-und-sammelpostfacher.json index e32edfc..f8527b5 100644 --- a/services/agent/knowledge/07_funktions-und-sammelpostfacher.json +++ b/services/agent/knowledge/07_funktions-und-sammelpostfacher.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-FUNKTIONS-UND-SAMMELPOSTFACHER-SELECT", - "title": "Funktions- und Sammelpostfächer", - "text": "Auswahlziel: Benutzerkonten und Berechtigungen \u003e Funktions- und Sammelpostfächer. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn ein Funktionspostfach, Sammelpostfach, Team-Postfach oder gemeinsam genutztes Postfach neu angelegt, geändert, umbenannt, berechtigt oder außer Betrieb genommen werden soll. Typische Ticketformulierungen sind: „Postfach info@ anlegen“; „Zugriff auf gemeinsames Postfach“; „Funktionspostfach umbenennen“; „Sammelpostfach schließen“. Nicht auswählen, wenn der Outlook-Client lokal nicht startet, eine einzelne E-Mail nicht zugestellt wird oder eine Verteilerliste ohne Postfachbezug gemeint ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Lokale Outlook-Probleme gehören zu Microsoft Office \u003e Outlook-Client; serverseitige Mailstörungen zu Infrastruktur und Backend.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Benutzerkonten und Berechtigungen \u003e Funktions- und Sammelpostfächer“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Benutzerkonten und Berechtigungen \u003e Funktions- und Sammelpostfächer" - ], - "keywords": [ - "Funktionspostfach", - "Sammelpostfach", - "gemeinsames Postfach", - "Shared Mailbox", - "Team-Postfach", - "Postfachberechtigung", - "Senden als", - "Postfach anlegen", - "Funktions- und Sammelpostfächer", - "Benutzerkonten und Berechtigungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/benutzerkonten-und-berechtigungen/funktions-und-sammelpostfacher", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-FUNKTIONS-UND-SAMMELPOSTFACHER-SELECT", + "title": "Funktions- und Sammelpostfächer", + "text": "Auswahlziel: Benutzerkonten und Berechtigungen \u003e Funktions- und Sammelpostfächer. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn ein Funktionspostfach, Sammelpostfach, Team-Postfach oder gemeinsam genutztes Postfach neu angelegt, geändert, umbenannt, berechtigt oder außer Betrieb genommen werden soll. Typische Ticketformulierungen sind: „Postfach info@ anlegen“; „Zugriff auf gemeinsames Postfach“; „Funktionspostfach umbenennen“; „Sammelpostfach schließen“. Nicht auswählen, wenn der Outlook-Client lokal nicht startet, eine einzelne E-Mail nicht zugestellt wird oder eine Verteilerliste ohne Postfachbezug gemeint ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Lokale Outlook-Probleme gehören zu Microsoft Office \u003e Outlook-Client; serverseitige Mailstörungen zu Infrastruktur und Backend.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Benutzerkonten und Berechtigungen \u003e Funktions- und Sammelpostfächer“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Benutzerkonten und Berechtigungen \u003e Funktions- und Sammelpostfächer" + ], + "keywords": [ + "Funktionspostfach", + "Sammelpostfach", + "gemeinsames Postfach", + "Shared Mailbox", + "Team-Postfach", + "Postfachberechtigung", + "Senden als", + "Postfach anlegen", + "Funktions- und Sammelpostfächer", + "Benutzerkonten und Berechtigungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/benutzerkonten-und-berechtigungen/funktions-und-sammelpostfacher", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/07_geratewechsel-und-umzug.json b/services/agent/knowledge/07_geratewechsel-und-umzug.json index cae96af..81fc8a2 100644 --- a/services/agent/knowledge/07_geratewechsel-und-umzug.json +++ b/services/agent/knowledge/07_geratewechsel-und-umzug.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-GERATEWECHSEL-UND-UMZUG-SELECT", - "title": "Gerätewechsel und Umzug", - "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Gerätewechsel und Umzug. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn vorhandene IT-Arbeitsmittel bei Arbeitsplatzwechsel, Raumumzug, Stellenwechsel oder Gerätetausch umgesetzt, neu angeschlossen, migriert oder ausgetauscht werden müssen. Typische Ticketformulierungen sind: „Arbeitsplatz in anderes Büro umziehen“; „Notebook gegen Ersatzgerät tauschen“; „PC und Monitore umsetzen“; „Daten auf Austauschgerät übernehmen“. Nicht auswählen, wenn neue zusätzliche Hardware beschafft werden soll, ein kompletter Standort umzieht oder nur ein technischer Defekt ohne Umzug vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Größere Standortumzüge werden als Projekt beziehungsweise Standorteröffnung oder Umzug geplant.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Gerätewechsel und Umzug“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Arbeitsplatz und Endgeräte \u003e Gerätewechsel und Umzug" - ], - "keywords": [ - "Umzug", - "Arbeitsplatzwechsel", - "Gerätewechsel", - "Gerätetausch", - "Austauschgerät", - "Bürowechsel", - "Umsetzen", - "Migration Arbeitsplatz", - "Gerätewechsel und Umzug", - "Arbeitsplatz und Endgeräte" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/geratewechsel-und-umzug", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-GERATEWECHSEL-UND-UMZUG-SELECT", + "title": "Gerätewechsel und Umzug", + "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Gerätewechsel und Umzug. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn vorhandene IT-Arbeitsmittel bei Arbeitsplatzwechsel, Raumumzug, Stellenwechsel oder Gerätetausch umgesetzt, neu angeschlossen, migriert oder ausgetauscht werden müssen. Typische Ticketformulierungen sind: „Arbeitsplatz in anderes Büro umziehen“; „Notebook gegen Ersatzgerät tauschen“; „PC und Monitore umsetzen“; „Daten auf Austauschgerät übernehmen“. Nicht auswählen, wenn neue zusätzliche Hardware beschafft werden soll, ein kompletter Standort umzieht oder nur ein technischer Defekt ohne Umzug vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Größere Standortumzüge werden als Projekt beziehungsweise Standorteröffnung oder Umzug geplant.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Gerätewechsel und Umzug“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Arbeitsplatz und Endgeräte \u003e Gerätewechsel und Umzug" + ], + "keywords": [ + "Umzug", + "Arbeitsplatzwechsel", + "Gerätewechsel", + "Gerätetausch", + "Austauschgerät", + "Bürowechsel", + "Umsetzen", + "Migration Arbeitsplatz", + "Gerätewechsel und Umzug", + "Arbeitsplatz und Endgeräte" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/geratewechsel-und-umzug", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/07_mobile-device-management-fur-schulen.json b/services/agent/knowledge/07_mobile-device-management-fur-schulen.json index 7f41da8..d86d9f7 100644 --- a/services/agent/knowledge/07_mobile-device-management-fur-schulen.json +++ b/services/agent/knowledge/07_mobile-device-management-fur-schulen.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-MOBILE-DEVICE-MANAGEMENT-FUR-SCHULEN-SELECT", - "title": "Mobile-Device-Management für Schulen", - "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Mobile-Device-Management für Schulen. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn die zentrale Verwaltung schulischer mobiler Geräte, MDM-Profile, App-Verteilung, Gerätegruppen, Richtlinien oder Enrollment betroffen ist. Typische Ticketformulierungen sind: „MDM-Profil wird nicht installiert“; „App an Schülergeräte verteilen“; „Gerät aus MDM entfernen“; „Enrollment schlägt fehl“. Nicht auswählen, wenn nur ein einzelnes Gerät physisch defekt ist oder ein allgemeines kommunales Smartphone eingerichtet werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Plattformbetrieb kann mittelfristig an Infrastruktur und Backend übergehen; schulfachliche Gerätezuordnung bleibt zu klären.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Mobile-Device-Management für Schulen“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Schulspezifische IT-Systeme \u003e Mobile-Device-Management für Schulen" - ], - "keywords": [ - "MDM", - "Mobile Device Management", - "Enrollment", - "Geräteprofil", - "App-Verteilung", - "Schülergeräte verwalten", - "Gerätegruppe", - "DEP", - "Mobile-Device-Management für Schulen", - "Schulspezifische IT-Systeme" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/mobile-device-management-fur-schulen", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-MOBILE-DEVICE-MANAGEMENT-FUR-SCHULEN-SELECT", + "title": "Mobile-Device-Management für Schulen", + "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Mobile-Device-Management für Schulen. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn die zentrale Verwaltung schulischer mobiler Geräte, MDM-Profile, App-Verteilung, Gerätegruppen, Richtlinien oder Enrollment betroffen ist. Typische Ticketformulierungen sind: „MDM-Profil wird nicht installiert“; „App an Schülergeräte verteilen“; „Gerät aus MDM entfernen“; „Enrollment schlägt fehl“. Nicht auswählen, wenn nur ein einzelnes Gerät physisch defekt ist oder ein allgemeines kommunales Smartphone eingerichtet werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Plattformbetrieb kann mittelfristig an Infrastruktur und Backend übergehen; schulfachliche Gerätezuordnung bleibt zu klären.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Mobile-Device-Management für Schulen“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Schulspezifische IT-Systeme \u003e Mobile-Device-Management für Schulen" + ], + "keywords": [ + "MDM", + "Mobile Device Management", + "Enrollment", + "Geräteprofil", + "App-Verteilung", + "Schülergeräte verwalten", + "Gerätegruppe", + "DEP", + "Mobile-Device-Management für Schulen", + "Schulspezifische IT-Systeme" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/mobile-device-management-fur-schulen", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/07_projektunterstutzung.json b/services/agent/knowledge/07_projektunterstutzung.json index b22a03b..80a574a 100644 --- a/services/agent/knowledge/07_projektunterstutzung.json +++ b/services/agent/knowledge/07_projektunterstutzung.json @@ -1,25 +1,25 @@ -{ - "id": "KAT-PROJEKTUNTERSTUTZUNG-SELECT", - "title": "Projektunterstützung", - "text": "Auswahlziel: Projekte, Änderungen und neue Anforderungen \u003e Projektunterstützung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn die IT in einem fachbereichsgeführten Projekt beraten, mitarbeiten, Aufwände schätzen, technische Teilaufgaben übernehmen oder feste Ressourcen bereitstellen soll. Typische Ticketformulierungen sind: „IT-Mitarbeit im Bauprojekt“; „Technische Beratung für Fachprojekt“; „Aufwandsschätzung benötigt“; „IT-Ressource für Projekt anfragen“. Nicht auswählen, wenn die IT selbst das Projekt führt, eine normale Störung bearbeitet oder nur eine einzelne Standardleistung bestellt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Leitung priorisiert Ressourcen und benennt das zuständige Fachteam.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Projekte, Änderungen und neue Anforderungen \u003e Projektunterstützung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Projekte, Änderungen und neue Anforderungen \u003e Projektunterstützung" - ], - "keywords": [ - "Projektunterstützung", - "Mitarbeit Projekt", - "IT-Beratung", - "Ressource", - "Aufwandsschätzung", - "Teilprojekt", - "Projektanfrage", - "Projekte, Änderungen und neue Anforderungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/projekte-anderungen-und-neue-anforderungen/projektunterstutzung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-PROJEKTUNTERSTUTZUNG-SELECT", + "title": "Projektunterstützung", + "text": "Auswahlziel: Projekte, Änderungen und neue Anforderungen \u003e Projektunterstützung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn die IT in einem fachbereichsgeführten Projekt beraten, mitarbeiten, Aufwände schätzen, technische Teilaufgaben übernehmen oder feste Ressourcen bereitstellen soll. Typische Ticketformulierungen sind: „IT-Mitarbeit im Bauprojekt“; „Technische Beratung für Fachprojekt“; „Aufwandsschätzung benötigt“; „IT-Ressource für Projekt anfragen“. Nicht auswählen, wenn die IT selbst das Projekt führt, eine normale Störung bearbeitet oder nur eine einzelne Standardleistung bestellt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Leitung priorisiert Ressourcen und benennt das zuständige Fachteam.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Projekte, Änderungen und neue Anforderungen \u003e Projektunterstützung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Projekte, Änderungen und neue Anforderungen \u003e Projektunterstützung" + ], + "keywords": [ + "Projektunterstützung", + "Mitarbeit Projekt", + "IT-Beratung", + "Ressource", + "Aufwandsschätzung", + "Teilprojekt", + "Projektanfrage", + "Projekte, Änderungen und neue Anforderungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/projekte-anderungen-und-neue-anforderungen/projektunterstutzung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/07_storage.json b/services/agent/knowledge/07_storage.json index d0b00ae..6784fb9 100644 --- a/services/agent/knowledge/07_storage.json +++ b/services/agent/knowledge/07_storage.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-STORAGE-SELECT", - "title": "Storage", - "text": "Auswahlziel: Server, Backend und Plattformen \u003e Storage. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn zentrale Speicherplattformen, SAN, NAS, Volumes, LUNs, Kapazitäten, Performance oder Storage-Replikation betroffen sind. Typische Ticketformulierungen sind: „Storage-Kapazität erweitern“; „SAN meldet Fehler“; „Volume nicht verfügbar“; „Speicherperformance schlecht“. Nicht auswählen, wenn nur eine Dateifreigabe, eine lokale Festplatte oder der Speicherplatz einer einzelnen Anwendung betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Dateidienste und virtuelle Maschinen können Folgeprobleme zeigen, die Ursache bleibt jedoch Storage.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Storage“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Server, Backend und Plattformen \u003e Storage" - ], - "keywords": [ - "Storage", - "SAN", - "NAS", - "LUN", - "Volume", - "Speicherplattform", - "Kapazität", - "IOPS", - "Speicherarray", - "Server, Backend und Plattformen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/storage", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-STORAGE-SELECT", + "title": "Storage", + "text": "Auswahlziel: Server, Backend und Plattformen \u003e Storage. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn zentrale Speicherplattformen, SAN, NAS, Volumes, LUNs, Kapazitäten, Performance oder Storage-Replikation betroffen sind. Typische Ticketformulierungen sind: „Storage-Kapazität erweitern“; „SAN meldet Fehler“; „Volume nicht verfügbar“; „Speicherperformance schlecht“. Nicht auswählen, wenn nur eine Dateifreigabe, eine lokale Festplatte oder der Speicherplatz einer einzelnen Anwendung betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Dateidienste und virtuelle Maschinen können Folgeprobleme zeigen, die Ursache bleibt jedoch Storage.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Storage“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Server, Backend und Plattformen \u003e Storage" + ], + "keywords": [ + "Storage", + "SAN", + "NAS", + "LUN", + "Volume", + "Speicherplattform", + "Kapazität", + "IOPS", + "Speicherarray", + "Server, Backend und Plattformen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/storage", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/07_telefonkonferenz-und-softphone.json b/services/agent/knowledge/07_telefonkonferenz-und-softphone.json index 2b2f7be..b2cbf63 100644 --- a/services/agent/knowledge/07_telefonkonferenz-und-softphone.json +++ b/services/agent/knowledge/07_telefonkonferenz-und-softphone.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-TELEFONKONFERENZ-UND-SOFTPHONE-SELECT", - "title": "Telefonkonferenz und Softphone", - "text": "Auswahlziel: Telefonie und Kommunikation \u003e Telefonkonferenz und Softphone. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Softphone, PC-Telefonie, Headset-Telefonie oder eine Telefonkonferenz nicht funktioniert, eingerichtet oder bedient werden muss. Typische Ticketformulierungen sind: „Softphone meldet nicht an“; „Telefonkonferenz einrichten“; „Kein Ton im PC-Telefon“; „Headset im Softphone nicht auswählbar“. Nicht auswählen, wenn ein physisches Festnetztelefon, eine Rufnummernverwaltung oder eine reine Videokonferenz betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei zentralen VoIP- oder Plattformstörungen Infrastruktur und Backend beteiligen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Telefonkonferenz und Softphone“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Telefonie und Kommunikation \u003e Telefonkonferenz und Softphone" - ], - "keywords": [ - "Softphone", - "Telefonkonferenz", - "PC-Telefonie", - "VoIP-Client", - "Headset", - "Konferenznummer", - "Audioanruf", - "Telefonkonferenz und Softphone", - "Telefonie und Kommunikation" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/telefonkonferenz-und-softphone", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-TELEFONKONFERENZ-UND-SOFTPHONE-SELECT", + "title": "Telefonkonferenz und Softphone", + "text": "Auswahlziel: Telefonie und Kommunikation \u003e Telefonkonferenz und Softphone. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn ein Softphone, PC-Telefonie, Headset-Telefonie oder eine Telefonkonferenz nicht funktioniert, eingerichtet oder bedient werden muss. Typische Ticketformulierungen sind: „Softphone meldet nicht an“; „Telefonkonferenz einrichten“; „Kein Ton im PC-Telefon“; „Headset im Softphone nicht auswählbar“. Nicht auswählen, wenn ein physisches Festnetztelefon, eine Rufnummernverwaltung oder eine reine Videokonferenz betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei zentralen VoIP- oder Plattformstörungen Infrastruktur und Backend beteiligen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Telefonkonferenz und Softphone“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Telefonie und Kommunikation \u003e Telefonkonferenz und Softphone" + ], + "keywords": [ + "Softphone", + "Telefonkonferenz", + "PC-Telefonie", + "VoIP-Client", + "Headset", + "Konferenznummer", + "Audioanruf", + "Telefonkonferenz und Softphone", + "Telefonie und Kommunikation" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/telefonkonferenz-und-softphone", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/07_verbrauchsmaterial.json b/services/agent/knowledge/07_verbrauchsmaterial.json index f55b35b..1a55de3 100644 --- a/services/agent/knowledge/07_verbrauchsmaterial.json +++ b/services/agent/knowledge/07_verbrauchsmaterial.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-VERBRAUCHSMATERIAL-SELECT", - "title": "Verbrauchsmaterial", - "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Verbrauchsmaterial. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn Toner, Trommel, Resttonerbehälter, Heftklammern oder anderes Verbrauchsmaterial für Drucker und Kopierer benötigt oder als leer gemeldet wird. Typische Ticketformulierungen sind: „Toner leer“; „Neue Trommel benötigt“; „Resttonerbehälter voll“; „Verbrauchsmaterial bestellen“. Nicht auswählen, wenn das Gerät trotz vorhandenem Material defekt ist oder eine allgemeine Hardwarebeschaffung ansteht. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bestellprozess und Lagerhaltung können je nach Organisation bei Support oder Beschaffung liegen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Verbrauchsmaterial“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Drucken, Scannen und Kopieren \u003e Verbrauchsmaterial" - ], - "keywords": [ - "Toner", - "Trommel", - "Verbrauchsmaterial", - "Resttoner", - "Kartusche", - "Druckerzubehör", - "leer", - "bestellen", - "Drucken, Scannen und Kopieren" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/verbrauchsmaterial", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-VERBRAUCHSMATERIAL-SELECT", + "title": "Verbrauchsmaterial", + "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Verbrauchsmaterial. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn Toner, Trommel, Resttonerbehälter, Heftklammern oder anderes Verbrauchsmaterial für Drucker und Kopierer benötigt oder als leer gemeldet wird. Typische Ticketformulierungen sind: „Toner leer“; „Neue Trommel benötigt“; „Resttonerbehälter voll“; „Verbrauchsmaterial bestellen“. Nicht auswählen, wenn das Gerät trotz vorhandenem Material defekt ist oder eine allgemeine Hardwarebeschaffung ansteht. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bestellprozess und Lagerhaltung können je nach Organisation bei Support oder Beschaffung liegen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Verbrauchsmaterial“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Drucken, Scannen und Kopieren \u003e Verbrauchsmaterial" + ], + "keywords": [ + "Toner", + "Trommel", + "Verbrauchsmaterial", + "Resttoner", + "Kartusche", + "Druckerzubehör", + "leer", + "bestellen", + "Drucken, Scannen und Kopieren" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/verbrauchsmaterial", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/08_datenbanken-plattformbetrieb.json b/services/agent/knowledge/08_datenbanken-plattformbetrieb.json index 124c309..508e615 100644 --- a/services/agent/knowledge/08_datenbanken-plattformbetrieb.json +++ b/services/agent/knowledge/08_datenbanken-plattformbetrieb.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-DATENBANKEN-PLATTFORMBETRIEB-SELECT", - "title": "Datenbanken – Plattformbetrieb", - "text": "Auswahlziel: Server, Backend und Plattformen \u003e Datenbanken – Plattformbetrieb. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn der zentrale Datenbankdienst, die Datenbankinstanz, Verfügbarkeit, Sicherung, Performance oder technische Administration von SQL-Plattformen betroffen ist. Typische Ticketformulierungen sind: „SQL-Server nicht erreichbar“; „Datenbankinstanz langsam“; „Backup der Datenbank fehlerhaft“; „Neue Datenbank technisch bereitstellen“. Nicht auswählen, wenn fachliche Daten korrigiert, Berichte angepasst oder anwendungsspezifische Tabelleninhalte verändert werden sollen. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Fachliches Datenmodell und Datenkorrekturen liegen bei Fachanwendungen; Plattformbetrieb bei Infrastruktur.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Datenbanken – Plattformbetrieb“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Server, Backend und Plattformen \u003e Datenbanken – Plattformbetrieb" - ], - "keywords": [ - "Datenbank", - "SQL", - "SQL Server", - "PostgreSQL", - "Oracle", - "MySQL", - "Instanz", - "DB-Backup", - "Datenbankperformance", - "Datenbanken – Plattformbetrieb", - "Server, Backend und Plattformen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/datenbanken-plattformbetrieb", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-DATENBANKEN-PLATTFORMBETRIEB-SELECT", + "title": "Datenbanken – Plattformbetrieb", + "text": "Auswahlziel: Server, Backend und Plattformen \u003e Datenbanken – Plattformbetrieb. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn der zentrale Datenbankdienst, die Datenbankinstanz, Verfügbarkeit, Sicherung, Performance oder technische Administration von SQL-Plattformen betroffen ist. Typische Ticketformulierungen sind: „SQL-Server nicht erreichbar“; „Datenbankinstanz langsam“; „Backup der Datenbank fehlerhaft“; „Neue Datenbank technisch bereitstellen“. Nicht auswählen, wenn fachliche Daten korrigiert, Berichte angepasst oder anwendungsspezifische Tabelleninhalte verändert werden sollen. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Fachliches Datenmodell und Datenkorrekturen liegen bei Fachanwendungen; Plattformbetrieb bei Infrastruktur.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Datenbanken – Plattformbetrieb“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Server, Backend und Plattformen \u003e Datenbanken – Plattformbetrieb" + ], + "keywords": [ + "Datenbank", + "SQL", + "SQL Server", + "PostgreSQL", + "Oracle", + "MySQL", + "Instanz", + "DB-Backup", + "Datenbankperformance", + "Datenbanken – Plattformbetrieb", + "Server, Backend und Plattformen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/datenbanken-plattformbetrieb", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/08_datenwiederherstellung.json b/services/agent/knowledge/08_datenwiederherstellung.json index 1b35e77..e465957 100644 --- a/services/agent/knowledge/08_datenwiederherstellung.json +++ b/services/agent/knowledge/08_datenwiederherstellung.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-DATENWIEDERHERSTELLUNG-SELECT", - "title": "Datenwiederherstellung", - "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Datenwiederherstellung. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn gelöschte, beschädigte oder verlorene Dateien, Verzeichnisse, Datenbanken, virtuelle Maschinen oder Systeme aus einer vorhandenen Sicherung wiederhergestellt werden sollen. Typische Ticketformulierungen sind: „Gelöschten Ordner wiederherstellen“; „Datei aus Backup zurückholen“; „VM-Restore“; „Datenbank auf Zeitpunkt zurücksetzen“. Nicht auswählen, wenn nur geprüft werden soll, ob Sicherungen laufen, oder wenn die Daten fachlich innerhalb einer Anwendung korrigiert werden müssen. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Betroffenes Objekt, Pfad, gewünschter Zeitpunkt und Dringlichkeit müssen möglichst genau angegeben werden.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Datenwiederherstellung“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "IT-Sicherheit und Datensicherung \u003e Datenwiederherstellung" - ], - "keywords": [ - "Wiederherstellung", - "Restore", - "gelöscht", - "Datei zurückholen", - "Backup einspielen", - "Recovery", - "Point-in-Time", - "Daten verloren", - "Datenwiederherstellung", - "IT-Sicherheit und Datensicherung" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/datenwiederherstellung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-DATENWIEDERHERSTELLUNG-SELECT", + "title": "Datenwiederherstellung", + "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Datenwiederherstellung. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn gelöschte, beschädigte oder verlorene Dateien, Verzeichnisse, Datenbanken, virtuelle Maschinen oder Systeme aus einer vorhandenen Sicherung wiederhergestellt werden sollen. Typische Ticketformulierungen sind: „Gelöschten Ordner wiederherstellen“; „Datei aus Backup zurückholen“; „VM-Restore“; „Datenbank auf Zeitpunkt zurücksetzen“. Nicht auswählen, wenn nur geprüft werden soll, ob Sicherungen laufen, oder wenn die Daten fachlich innerhalb einer Anwendung korrigiert werden müssen. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Betroffenes Objekt, Pfad, gewünschter Zeitpunkt und Dringlichkeit müssen möglichst genau angegeben werden.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Datenwiederherstellung“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "IT-Sicherheit und Datensicherung \u003e Datenwiederherstellung" + ], + "keywords": [ + "Wiederherstellung", + "Restore", + "gelöscht", + "Datei zurückholen", + "Backup einspielen", + "Recovery", + "Point-in-Time", + "Daten verloren", + "Datenwiederherstellung", + "IT-Sicherheit und Datensicherung" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/datenwiederherstellung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/08_fachanwendung-neue-anforderung.json b/services/agent/knowledge/08_fachanwendung-neue-anforderung.json index 13854a6..60bc134 100644 --- a/services/agent/knowledge/08_fachanwendung-neue-anforderung.json +++ b/services/agent/knowledge/08_fachanwendung-neue-anforderung.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-FACHANWENDUNG-NEUE-ANFORDERUNG-SELECT", - "title": "Fachanwendung – Neue Anforderung", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Neue Anforderung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn eine bestehende Fachanwendung funktional erweitert, ein neuer Prozess abgebildet, ein zusätzliches Modul eingeführt oder eine bislang nicht vorhandene fachliche Funktion umgesetzt werden soll. Typische Ticketformulierungen sind: „Neues Formular im Fachverfahren“; „Zusätzliches Modul benötigt“; „Prozess soll digital abgebildet werden“; „Funktionserweiterung anfragen“. Nicht auswählen, wenn lediglich eine vorhandene Funktion gestört ist, eine kleine Parametrierung ausreicht oder eine komplett neue Anwendung beschafft und eingeführt werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Umfang, Nutzen, betroffene Organisation und Priorität sollten dokumentiert werden; größere Vorhaben können in ein Projekt überführt werden.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Neue Anforderung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Neue Anforderung" - ], - "keywords": [ - "neue Anforderung", - "Erweiterung", - "Feature", - "neue Funktion", - "zusätzliches Modul", - "Change Request", - "Anpassungswunsch", - "Prozess digitalisieren", - "Fachanwendung – Neue Anforderung", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-neue-anforderung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-FACHANWENDUNG-NEUE-ANFORDERUNG-SELECT", + "title": "Fachanwendung – Neue Anforderung", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Neue Anforderung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn eine bestehende Fachanwendung funktional erweitert, ein neuer Prozess abgebildet, ein zusätzliches Modul eingeführt oder eine bislang nicht vorhandene fachliche Funktion umgesetzt werden soll. Typische Ticketformulierungen sind: „Neues Formular im Fachverfahren“; „Zusätzliches Modul benötigt“; „Prozess soll digital abgebildet werden“; „Funktionserweiterung anfragen“. Nicht auswählen, wenn lediglich eine vorhandene Funktion gestört ist, eine kleine Parametrierung ausreicht oder eine komplett neue Anwendung beschafft und eingeführt werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Umfang, Nutzen, betroffene Organisation und Priorität sollten dokumentiert werden; größere Vorhaben können in ein Projekt überführt werden.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Neue Anforderung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Neue Anforderung" + ], + "keywords": [ + "neue Anforderung", + "Erweiterung", + "Feature", + "neue Funktion", + "zusätzliches Modul", + "Change Request", + "Anpassungswunsch", + "Prozess digitalisieren", + "Fachanwendung – Neue Anforderung", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-neue-anforderung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/08_hardware-neubeschaffung.json b/services/agent/knowledge/08_hardware-neubeschaffung.json index f3bd445..04a8dae 100644 --- a/services/agent/knowledge/08_hardware-neubeschaffung.json +++ b/services/agent/knowledge/08_hardware-neubeschaffung.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-HARDWARE-NEUBESCHAFFUNG-SELECT", - "title": "Hardware-Neubeschaffung", - "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Hardware-Neubeschaffung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn ein neuer oder zusätzlicher PC, ein Notebook, Monitor, Tablet, Zubehör oder sonstige Arbeitsplatzhardware bestellt werden soll und kein einfacher Austausch eines defekten Bestandsgeräts vorliegt. Typische Ticketformulierungen sind: „Neues Notebook für neue Stelle“; „Zusätzlichen Monitor bestellen“; „Arbeitsplatz vollständig ausstatten“; „Spezialhardware beschaffen“. Nicht auswählen, wenn ein vorhandenes Gerät nur repariert oder umgesetzt werden soll; wenn Telekommunikationshardware oder ein neues Drucksystem beschafft wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Das zuständige Fachteam prüft technische Anforderungen; Bestellung, Budget und Vergabe liegen bei Beschaffung.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Hardware-Neubeschaffung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Arbeitsplatz und Endgeräte \u003e Hardware-Neubeschaffung" - ], - "keywords": [ - "Hardware beschaffen", - "Neubeschaffung", - "Bestellung", - "neuer PC", - "neues Notebook", - "zusätzlicher Monitor", - "Arbeitsplatzausstattung", - "Kauf", - "Hardware-Neubeschaffung", - "Arbeitsplatz und Endgeräte" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/hardware-neubeschaffung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-HARDWARE-NEUBESCHAFFUNG-SELECT", + "title": "Hardware-Neubeschaffung", + "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Hardware-Neubeschaffung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn ein neuer oder zusätzlicher PC, ein Notebook, Monitor, Tablet, Zubehör oder sonstige Arbeitsplatzhardware bestellt werden soll und kein einfacher Austausch eines defekten Bestandsgeräts vorliegt. Typische Ticketformulierungen sind: „Neues Notebook für neue Stelle“; „Zusätzlichen Monitor bestellen“; „Arbeitsplatz vollständig ausstatten“; „Spezialhardware beschaffen“. Nicht auswählen, wenn ein vorhandenes Gerät nur repariert oder umgesetzt werden soll; wenn Telekommunikationshardware oder ein neues Drucksystem beschafft wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Das zuständige Fachteam prüft technische Anforderungen; Bestellung, Budget und Vergabe liegen bei Beschaffung.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Hardware-Neubeschaffung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Arbeitsplatz und Endgeräte \u003e Hardware-Neubeschaffung" + ], + "keywords": [ + "Hardware beschaffen", + "Neubeschaffung", + "Bestellung", + "neuer PC", + "neues Notebook", + "zusätzlicher Monitor", + "Arbeitsplatzausstattung", + "Kauf", + "Hardware-Neubeschaffung", + "Arbeitsplatz und Endgeräte" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/hardware-neubeschaffung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/08_netzwerksegment-und-vlan.json b/services/agent/knowledge/08_netzwerksegment-und-vlan.json index 3af3047..1801f72 100644 --- a/services/agent/knowledge/08_netzwerksegment-und-vlan.json +++ b/services/agent/knowledge/08_netzwerksegment-und-vlan.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-NETZWERKSEGMENT-UND-VLAN-SELECT", - "title": "Netzwerksegment und VLAN", - "text": "Auswahlziel: Netzwerk und Verbindungen \u003e Netzwerksegment und VLAN. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn VLANs, Netzsegmente, Subnetze, logische Trennungen oder Portzuordnungen neu eingerichtet, geändert oder analysiert werden sollen. Typische Ticketformulierungen sind: „Gerät in anderes VLAN verschieben“; „Neues Netzsegment anlegen“; „Port falschem VLAN zugeordnet“; „Subnetz für neues System“. Nicht auswählen, wenn nur eine einzelne Netzwerkdose ohne Verbindung ist oder eine Firewallregel zwischen bestehenden Segmenten fehlt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Sicherheitsrelevante Segmentierungsentscheidungen sind mit IT-Sicherheit und Leitung abzustimmen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e Netzwerksegment und VLAN“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Netzwerk und Verbindungen \u003e Netzwerksegment und VLAN" - ], - "keywords": [ - "VLAN", - "Netzsegment", - "Subnetz", - "Segmentierung", - "Switchport", - "Portzuordnung", - "Netztrennung", - "IP-Netz", - "Netzwerksegment und VLAN", - "Netzwerk und Verbindungen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/netzwerksegment-und-vlan", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-NETZWERKSEGMENT-UND-VLAN-SELECT", + "title": "Netzwerksegment und VLAN", + "text": "Auswahlziel: Netzwerk und Verbindungen \u003e Netzwerksegment und VLAN. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn VLANs, Netzsegmente, Subnetze, logische Trennungen oder Portzuordnungen neu eingerichtet, geändert oder analysiert werden sollen. Typische Ticketformulierungen sind: „Gerät in anderes VLAN verschieben“; „Neues Netzsegment anlegen“; „Port falschem VLAN zugeordnet“; „Subnetz für neues System“. Nicht auswählen, wenn nur eine einzelne Netzwerkdose ohne Verbindung ist oder eine Firewallregel zwischen bestehenden Segmenten fehlt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Sicherheitsrelevante Segmentierungsentscheidungen sind mit IT-Sicherheit und Leitung abzustimmen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Netzwerk und Verbindungen \u003e Netzwerksegment und VLAN“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Netzwerk und Verbindungen \u003e Netzwerksegment und VLAN" + ], + "keywords": [ + "VLAN", + "Netzsegment", + "Subnetz", + "Segmentierung", + "Switchport", + "Portzuordnung", + "Netztrennung", + "IP-Netz", + "Netzwerksegment und VLAN", + "Netzwerk und Verbindungen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/netzwerk-und-verbindungen/netzwerksegment-und-vlan", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/08_neues-drucksystem.json b/services/agent/knowledge/08_neues-drucksystem.json index 3fb0b7a..5f7011c 100644 --- a/services/agent/knowledge/08_neues-drucksystem.json +++ b/services/agent/knowledge/08_neues-drucksystem.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-NEUES-DRUCKSYSTEM-SELECT", - "title": "Neues Drucksystem", - "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Neues Drucksystem. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn ein neuer Drucker, Kopierer, Scannerverbund oder ein standortweites Druckkonzept beschafft, ersetzt oder geplant werden soll. Typische Ticketformulierungen sind: „Neuen Kopierer beschaffen“; „Druckerkonzept für Standort“; „Zusätzlichen Netzwerkdrucker bestellen“; „Altgerät ersetzen“. Nicht auswählen, wenn nur ein vorhandenes Gerät gestört ist, ein Treiber fehlt oder Verbrauchsmaterial benötigt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Anforderungen werden gemeinsam mit Support und Infrastruktur bewertet; Kauf und Vertrag liegen bei Beschaffung.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Neues Drucksystem“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Drucken, Scannen und Kopieren \u003e Neues Drucksystem" - ], - "keywords": [ - "neues Drucksystem", - "Drucker beschaffen", - "Kopierer beschaffen", - "Druckkonzept", - "Neugerät", - "Ausschreibung Drucker", - "MFP kaufen", - "Neues Drucksystem", - "Drucken, Scannen und Kopieren" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/neues-drucksystem", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-NEUES-DRUCKSYSTEM-SELECT", + "title": "Neues Drucksystem", + "text": "Auswahlziel: Drucken, Scannen und Kopieren \u003e Neues Drucksystem. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn ein neuer Drucker, Kopierer, Scannerverbund oder ein standortweites Druckkonzept beschafft, ersetzt oder geplant werden soll. Typische Ticketformulierungen sind: „Neuen Kopierer beschaffen“; „Druckerkonzept für Standort“; „Zusätzlichen Netzwerkdrucker bestellen“; „Altgerät ersetzen“. Nicht auswählen, wenn nur ein vorhandenes Gerät gestört ist, ein Treiber fehlt oder Verbrauchsmaterial benötigt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Anforderungen werden gemeinsam mit Support und Infrastruktur bewertet; Kauf und Vertrag liegen bei Beschaffung.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Drucken, Scannen und Kopieren \u003e Neues Drucksystem“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Drucken, Scannen und Kopieren \u003e Neues Drucksystem" + ], + "keywords": [ + "neues Drucksystem", + "Drucker beschaffen", + "Kopierer beschaffen", + "Druckkonzept", + "Neugerät", + "Ausschreibung Drucker", + "MFP kaufen", + "Neues Drucksystem", + "Drucken, Scannen und Kopieren" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/drucken-scannen-und-kopieren/neues-drucksystem", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/08_padagogische-lernplattformen.json b/services/agent/knowledge/08_padagogische-lernplattformen.json index cd47311..c594c3a 100644 --- a/services/agent/knowledge/08_padagogische-lernplattformen.json +++ b/services/agent/knowledge/08_padagogische-lernplattformen.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-PADAGOGISCHE-LERNPLATTFORMEN-SELECT", - "title": "Pädagogische Lernplattformen", - "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Pädagogische Lernplattformen. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn Lernmanagementsysteme, digitale Klassenräume, Kursräume, Unterrichtsplattformen oder deren schulische Nutzung und Zugänge betroffen sind. Typische Ticketformulierungen sind: „Kursraum nicht sichtbar“; „Lernplattform nicht erreichbar“; „Schüler kann Aufgabe nicht abgeben“; „Klasse in Plattform anlegen“. Nicht auswählen, wenn eine allgemeine Microsoft-Office-Funktion, reine Netzstörung oder Schulverwaltungsanwendung gemeint ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei zentralem Hosting- oder Netzwerkproblem wird an Infrastruktur übergeben; fachliche Nutzung bleibt bei Team Schulen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Pädagogische Lernplattformen“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Schulspezifische IT-Systeme \u003e Pädagogische Lernplattformen" - ], - "keywords": [ - "Lernplattform", - "LMS", - "Moodle", - "digitaler Klassenraum", - "Kurs", - "Aufgabe", - "Unterrichtsplattform", - "Schülerzugang", - "Pädagogische Lernplattformen", - "Schulspezifische IT-Systeme" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/padagogische-lernplattformen", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-PADAGOGISCHE-LERNPLATTFORMEN-SELECT", + "title": "Pädagogische Lernplattformen", + "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Pädagogische Lernplattformen. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn Lernmanagementsysteme, digitale Klassenräume, Kursräume, Unterrichtsplattformen oder deren schulische Nutzung und Zugänge betroffen sind. Typische Ticketformulierungen sind: „Kursraum nicht sichtbar“; „Lernplattform nicht erreichbar“; „Schüler kann Aufgabe nicht abgeben“; „Klasse in Plattform anlegen“. Nicht auswählen, wenn eine allgemeine Microsoft-Office-Funktion, reine Netzstörung oder Schulverwaltungsanwendung gemeint ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei zentralem Hosting- oder Netzwerkproblem wird an Infrastruktur übergeben; fachliche Nutzung bleibt bei Team Schulen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Pädagogische Lernplattformen“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Schulspezifische IT-Systeme \u003e Pädagogische Lernplattformen" + ], + "keywords": [ + "Lernplattform", + "LMS", + "Moodle", + "digitaler Klassenraum", + "Kurs", + "Aufgabe", + "Unterrichtsplattform", + "Schülerzugang", + "Pädagogische Lernplattformen", + "Schulspezifische IT-Systeme" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/padagogische-lernplattformen", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/08_rechnung-und-kostenstelle.json b/services/agent/knowledge/08_rechnung-und-kostenstelle.json index 2d7e6c0..878092c 100644 --- a/services/agent/knowledge/08_rechnung-und-kostenstelle.json +++ b/services/agent/knowledge/08_rechnung-und-kostenstelle.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-RECHNUNG-UND-KOSTENSTELLE-SELECT", - "title": "Rechnung und Kostenstelle", - "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Rechnung und Kostenstelle. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn eine IT-Rechnung geprüft, sachlich zugeordnet, kontiert, beanstandet oder einer Kostenstelle, Bestellung oder einem Vertrag zugeordnet werden muss. Typische Ticketformulierungen sind: „Rechnung prüfen“; „Kostenstelle korrigieren“; „Bestellbezug fehlt“; „Falscher Rechnungsbetrag“. Nicht auswählen, wenn ein Angebot vorliegt, eine Lizenz erst bestellt werden soll oder ein technisches Problem mit dem gelieferten Produkt besteht. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Leistungsbestätigung kann durch das zuständige Fachteam erforderlich sein.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Rechnung und Kostenstelle“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Beschaffung, Verträge und Lizenzen \u003e Rechnung und Kostenstelle" - ], - "keywords": [ - "Rechnung", - "Kostenstelle", - "Kontierung", - "Bestellnummer", - "Rechnungsprüfung", - "Zahlung", - "Gutschrift", - "Betrag falsch", - "Rechnung und Kostenstelle", - "Beschaffung, Verträge und Lizenzen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/rechnung-und-kostenstelle", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-RECHNUNG-UND-KOSTENSTELLE-SELECT", + "title": "Rechnung und Kostenstelle", + "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Rechnung und Kostenstelle. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn eine IT-Rechnung geprüft, sachlich zugeordnet, kontiert, beanstandet oder einer Kostenstelle, Bestellung oder einem Vertrag zugeordnet werden muss. Typische Ticketformulierungen sind: „Rechnung prüfen“; „Kostenstelle korrigieren“; „Bestellbezug fehlt“; „Falscher Rechnungsbetrag“. Nicht auswählen, wenn ein Angebot vorliegt, eine Lizenz erst bestellt werden soll oder ein technisches Problem mit dem gelieferten Produkt besteht. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Leistungsbestätigung kann durch das zuständige Fachteam erforderlich sein.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Rechnung und Kostenstelle“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Beschaffung, Verträge und Lizenzen \u003e Rechnung und Kostenstelle" + ], + "keywords": [ + "Rechnung", + "Kostenstelle", + "Kontierung", + "Bestellnummer", + "Rechnungsprüfung", + "Zahlung", + "Gutschrift", + "Betrag falsch", + "Rechnung und Kostenstelle", + "Beschaffung, Verträge und Lizenzen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/rechnung-und-kostenstelle", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/08_telekommunikationsbeschaffung.json b/services/agent/knowledge/08_telekommunikationsbeschaffung.json index 527a9e1..e29c72a 100644 --- a/services/agent/knowledge/08_telekommunikationsbeschaffung.json +++ b/services/agent/knowledge/08_telekommunikationsbeschaffung.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-TELEKOMMUNIKATIONSBESCHAFFUNG-SELECT", - "title": "Telekommunikationsbeschaffung", - "text": "Auswahlziel: Telefonie und Kommunikation \u003e Telekommunikationsbeschaffung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn neue Telefone, Smartphones, SIM-Karten, Mobilfunkverträge, Headsets oder andere Telekommunikationsleistungen beschafft, verlängert oder wirtschaftlich bewertet werden sollen. Typische Ticketformulierungen sind: „Neues Diensthandy bestellen“; „Mobilfunkvertrag abschließen“; „Telefone für neue Arbeitsplätze beschaffen“; „Headsets in größerer Stückzahl kaufen“. Nicht auswählen, wenn ein vorhandenes Gerät nur defekt ist, eine Rufumleitung geändert oder eine technische Störung behoben werden muss. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Spezifikation erfolgt gemeinsam mit Support und Telekommunikation.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Telekommunikationsbeschaffung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Telefonie und Kommunikation \u003e Telekommunikationsbeschaffung" - ], - "keywords": [ - "Telekommunikation Beschaffung", - "Telefon bestellen", - "Diensthandy bestellen", - "Mobilfunkvertrag", - "SIM bestellen", - "Headset beschaffen", - "Kauf", - "Telekommunikationsbeschaffung", - "Telefonie und Kommunikation" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/telekommunikationsbeschaffung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-TELEKOMMUNIKATIONSBESCHAFFUNG-SELECT", + "title": "Telekommunikationsbeschaffung", + "text": "Auswahlziel: Telefonie und Kommunikation \u003e Telekommunikationsbeschaffung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn neue Telefone, Smartphones, SIM-Karten, Mobilfunkverträge, Headsets oder andere Telekommunikationsleistungen beschafft, verlängert oder wirtschaftlich bewertet werden sollen. Typische Ticketformulierungen sind: „Neues Diensthandy bestellen“; „Mobilfunkvertrag abschließen“; „Telefone für neue Arbeitsplätze beschaffen“; „Headsets in größerer Stückzahl kaufen“. Nicht auswählen, wenn ein vorhandenes Gerät nur defekt ist, eine Rufumleitung geändert oder eine technische Störung behoben werden muss. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Spezifikation erfolgt gemeinsam mit Support und Telekommunikation.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Telefonie und Kommunikation \u003e Telekommunikationsbeschaffung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Telefonie und Kommunikation \u003e Telekommunikationsbeschaffung" + ], + "keywords": [ + "Telekommunikation Beschaffung", + "Telefon bestellen", + "Diensthandy bestellen", + "Mobilfunkvertrag", + "SIM bestellen", + "Headset beschaffen", + "Kauf", + "Telekommunikationsbeschaffung", + "Telefonie und Kommunikation" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/telefonie-und-kommunikation/telekommunikationsbeschaffung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/09_container-und-docker.json b/services/agent/knowledge/09_container-und-docker.json index bbe929b..315b237 100644 --- a/services/agent/knowledge/09_container-und-docker.json +++ b/services/agent/knowledge/09_container-und-docker.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-CONTAINER-UND-DOCKER-SELECT", - "title": "Container und Docker", - "text": "Auswahlziel: Server, Backend und Plattformen \u003e Container und Docker. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Docker-Hosts, Container, Images, Registries, Compose-Stacks oder containerbasierte Laufzeitumgebungen bereitgestellt, gewartet oder analysiert werden sollen. Typische Ticketformulierungen sind: „Docker-Container startet nicht“; „Image bereitstellen“; „Registry-Zugriff fehlerhaft“; „Compose-Stack deployen“. Nicht auswählen, wenn ein Kubernetes-Cluster betroffen ist, eine klassische VM benötigt wird oder nur die Fachanwendung im Container fachlich fehlerhaft ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei Sicherheitslücken in Images ist zusätzlich IT-Sicherheit relevant.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Container und Docker“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Server, Backend und Plattformen \u003e Container und Docker" - ], - "keywords": [ - "Docker", - "Container", - "Image", - "Registry", - "Docker Compose", - "Container Runtime", - "Container startet nicht", - "Containerplattform", - "Container und Docker", - "Server, Backend und Plattformen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/container-und-docker", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-CONTAINER-UND-DOCKER-SELECT", + "title": "Container und Docker", + "text": "Auswahlziel: Server, Backend und Plattformen \u003e Container und Docker. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Docker-Hosts, Container, Images, Registries, Compose-Stacks oder containerbasierte Laufzeitumgebungen bereitgestellt, gewartet oder analysiert werden sollen. Typische Ticketformulierungen sind: „Docker-Container startet nicht“; „Image bereitstellen“; „Registry-Zugriff fehlerhaft“; „Compose-Stack deployen“. Nicht auswählen, wenn ein Kubernetes-Cluster betroffen ist, eine klassische VM benötigt wird oder nur die Fachanwendung im Container fachlich fehlerhaft ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei Sicherheitslücken in Images ist zusätzlich IT-Sicherheit relevant.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Container und Docker“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Server, Backend und Plattformen \u003e Container und Docker" + ], + "keywords": [ + "Docker", + "Container", + "Image", + "Registry", + "Docker Compose", + "Container Runtime", + "Container startet nicht", + "Containerplattform", + "Container und Docker", + "Server, Backend und Plattformen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/container-und-docker", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/09_fachanwendung-einfuhrung-einer-anwendung.json b/services/agent/knowledge/09_fachanwendung-einfuhrung-einer-anwendung.json index 6fa0cb4..1baecc2 100644 --- a/services/agent/knowledge/09_fachanwendung-einfuhrung-einer-anwendung.json +++ b/services/agent/knowledge/09_fachanwendung-einfuhrung-einer-anwendung.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-FACHANWENDUNG-EINFUHRUNG-EINER-ANWENDUNG-SELECT", - "title": "Fachanwendung – Einführung einer Anwendung", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Einführung einer Anwendung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn ein neues Fachverfahren oder eine neue fachliche Software ausgewählt, beschafft, konfiguriert, migriert, getestet, geschult und produktiv eingeführt werden soll. Typische Ticketformulierungen sind: „Neues Fachverfahren einführen“; „Migration auf neue Anwendung“; „Pilotbetrieb einer Fachsoftware“; „Ablösung des Altsystems“. Nicht auswählen, wenn eine bestehende Anwendung nur aktualisiert oder erweitert wird oder lediglich Standardsoftware an einem Arbeitsplatz installiert werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Beschaffung, Datenschutz, Informationssicherheit und Infrastruktur sind je nach Umfang als Schnittstellen einzubeziehen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Einführung einer Anwendung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Einführung einer Anwendung" - ], - "keywords": [ - "Einführung", - "neue Anwendung", - "neues Fachverfahren", - "Migration", - "Ablösung", - "Rollout", - "Pilot", - "Implementierung", - "Projekt Fachsoftware", - "Fachanwendung – Einführung einer Anwendung", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-einfuhrung-einer-anwendung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-FACHANWENDUNG-EINFUHRUNG-EINER-ANWENDUNG-SELECT", + "title": "Fachanwendung – Einführung einer Anwendung", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Fachanwendung – Einführung einer Anwendung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn ein neues Fachverfahren oder eine neue fachliche Software ausgewählt, beschafft, konfiguriert, migriert, getestet, geschult und produktiv eingeführt werden soll. Typische Ticketformulierungen sind: „Neues Fachverfahren einführen“; „Migration auf neue Anwendung“; „Pilotbetrieb einer Fachsoftware“; „Ablösung des Altsystems“. Nicht auswählen, wenn eine bestehende Anwendung nur aktualisiert oder erweitert wird oder lediglich Standardsoftware an einem Arbeitsplatz installiert werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Beschaffung, Datenschutz, Informationssicherheit und Infrastruktur sind je nach Umfang als Schnittstellen einzubeziehen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Fachanwendung – Einführung einer Anwendung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Fachanwendung – Einführung einer Anwendung" + ], + "keywords": [ + "Einführung", + "neue Anwendung", + "neues Fachverfahren", + "Migration", + "Ablösung", + "Rollout", + "Pilot", + "Implementierung", + "Projekt Fachsoftware", + "Fachanwendung – Einführung einer Anwendung", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/fachanwendung-einfuhrung-einer-anwendung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/09_inventarisierung.json b/services/agent/knowledge/09_inventarisierung.json index bb0e995..f555e56 100644 --- a/services/agent/knowledge/09_inventarisierung.json +++ b/services/agent/knowledge/09_inventarisierung.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-INVENTARISIERUNG-SELECT", - "title": "Inventarisierung", - "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Inventarisierung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn ein IT-Asset neu inventarisiert, einer Person oder einem Standort zugeordnet, umgebucht, korrigiert oder im Bestand dokumentiert werden soll. Typische Ticketformulierungen sind: „Inventarnummer anlegen“; „Gerät anderem Standort zuordnen“; „Asset-Daten korrigieren“; „Bestand übernehmen“. Nicht auswählen, wenn ein Gerät physisch zurückgegeben, technisch repariert oder endgültig entsorgt werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Physische Rücknahme und Datenlöschung liegen beim Support; kaufmännischer Asset-Status bei Beschaffung.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Inventarisierung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Beschaffung, Verträge und Lizenzen \u003e Inventarisierung" - ], - "keywords": [ - "Inventarisierung", - "Inventarnummer", - "Asset", - "Bestand", - "Gerätezuordnung", - "Umbuchung", - "Standortzuordnung", - "Anlagegut", - "Beschaffung, Verträge und Lizenzen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/inventarisierung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-INVENTARISIERUNG-SELECT", + "title": "Inventarisierung", + "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Inventarisierung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn ein IT-Asset neu inventarisiert, einer Person oder einem Standort zugeordnet, umgebucht, korrigiert oder im Bestand dokumentiert werden soll. Typische Ticketformulierungen sind: „Inventarnummer anlegen“; „Gerät anderem Standort zuordnen“; „Asset-Daten korrigieren“; „Bestand übernehmen“. Nicht auswählen, wenn ein Gerät physisch zurückgegeben, technisch repariert oder endgültig entsorgt werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Physische Rücknahme und Datenlöschung liegen beim Support; kaufmännischer Asset-Status bei Beschaffung.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Inventarisierung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Beschaffung, Verträge und Lizenzen \u003e Inventarisierung" + ], + "keywords": [ + "Inventarisierung", + "Inventarnummer", + "Asset", + "Bestand", + "Gerätezuordnung", + "Umbuchung", + "Standortzuordnung", + "Anlagegut", + "Beschaffung, Verträge und Lizenzen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/inventarisierung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/09_protokollierung-und-auswertung.json b/services/agent/knowledge/09_protokollierung-und-auswertung.json index 3165588..f7dd353 100644 --- a/services/agent/knowledge/09_protokollierung-und-auswertung.json +++ b/services/agent/knowledge/09_protokollierung-und-auswertung.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-PROTOKOLLIERUNG-UND-AUSWERTUNG-SELECT", - "title": "Protokollierung und Auswertung", - "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Protokollierung und Auswertung. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn technische Logs, Auditdaten, zentrale Protokollierung, SIEM-Auswertung, Nachvollziehbarkeit oder sicherheitsbezogene Ereignisanalyse benötigt oder gestört sind. Typische Ticketformulierungen sind: „Logdaten für Analyse bereitstellen“; „Auditprotokoll fehlt“; „SIEM-Regel anpassen“; „Anmeldeereignisse auswerten“. Nicht auswählen, wenn nur eine fachliche Statistik, ein normaler Monitoring-Check oder ein konkreter aktiver Sicherheitsvorfall ohne Analyseauftrag gemeldet wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Datenschutz, Zweckbindung und Aufbewahrungsregeln sind bei personenbezogenen Protokollen zu beachten.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Protokollierung und Auswertung“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "IT-Sicherheit und Datensicherung \u003e Protokollierung und Auswertung" - ], - "keywords": [ - "Protokollierung", - "Log", - "Audit", - "SIEM", - "Ereignisprotokoll", - "Event Log", - "Nachvollziehbarkeit", - "Loganalyse", - "Security Event", - "Protokollierung und Auswertung", - "IT-Sicherheit und Datensicherung" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/protokollierung-und-auswertung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-PROTOKOLLIERUNG-UND-AUSWERTUNG-SELECT", + "title": "Protokollierung und Auswertung", + "text": "Auswahlziel: IT-Sicherheit und Datensicherung \u003e Protokollierung und Auswertung. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn technische Logs, Auditdaten, zentrale Protokollierung, SIEM-Auswertung, Nachvollziehbarkeit oder sicherheitsbezogene Ereignisanalyse benötigt oder gestört sind. Typische Ticketformulierungen sind: „Logdaten für Analyse bereitstellen“; „Auditprotokoll fehlt“; „SIEM-Regel anpassen“; „Anmeldeereignisse auswerten“. Nicht auswählen, wenn nur eine fachliche Statistik, ein normaler Monitoring-Check oder ein konkreter aktiver Sicherheitsvorfall ohne Analyseauftrag gemeldet wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Datenschutz, Zweckbindung und Aufbewahrungsregeln sind bei personenbezogenen Protokollen zu beachten.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „IT-Sicherheit und Datensicherung \u003e Protokollierung und Auswertung“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "IT-Sicherheit und Datensicherung \u003e Protokollierung und Auswertung" + ], + "keywords": [ + "Protokollierung", + "Log", + "Audit", + "SIEM", + "Ereignisprotokoll", + "Event Log", + "Nachvollziehbarkeit", + "Loganalyse", + "Security Event", + "Protokollierung und Auswertung", + "IT-Sicherheit und Datensicherung" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/it-sicherheit-und-datensicherung/protokollierung-und-auswertung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/09_prufungs-und-klausursysteme.json b/services/agent/knowledge/09_prufungs-und-klausursysteme.json index 64b32d0..a9bd528 100644 --- a/services/agent/knowledge/09_prufungs-und-klausursysteme.json +++ b/services/agent/knowledge/09_prufungs-und-klausursysteme.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-PRUFUNGS-UND-KLAUSURSYSTEME-SELECT", - "title": "Prüfungs- und Klausursysteme", - "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Prüfungs- und Klausursysteme. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn digitale Prüfungsumgebungen, Klausurclients, Prüfungsaccounts, sichere Browser, Prüfungsnetz oder technische Vorbereitung einer digitalen Prüfung betroffen sind. Typische Ticketformulierungen sind: „Prüfungsbrowser startet nicht“; „Klausuraccount fehlt“; „Digitale Prüfung vorbereiten“; „Prüfungsnetz gestört“. Nicht auswählen, wenn eine normale Lernplattformaufgabe oder allgemeine Computerraumstörung ohne Prüfungsbezug vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Tickets mit unmittelbarem Prüfungstermin sind zeitkritisch und entsprechend zu priorisieren.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Prüfungs- und Klausursysteme“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Schulspezifische IT-Systeme \u003e Prüfungs- und Klausursysteme" - ], - "keywords": [ - "Prüfungssystem", - "Klausursystem", - "Prüfungsbrowser", - "Safe Exam Browser", - "digitale Prüfung", - "Klausuraccount", - "Prüfungsnetz", - "Prüfungs- und Klausursysteme", - "Schulspezifische IT-Systeme" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/prufungs-und-klausursysteme", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-PRUFUNGS-UND-KLAUSURSYSTEME-SELECT", + "title": "Prüfungs- und Klausursysteme", + "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Prüfungs- und Klausursysteme. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn digitale Prüfungsumgebungen, Klausurclients, Prüfungsaccounts, sichere Browser, Prüfungsnetz oder technische Vorbereitung einer digitalen Prüfung betroffen sind. Typische Ticketformulierungen sind: „Prüfungsbrowser startet nicht“; „Klausuraccount fehlt“; „Digitale Prüfung vorbereiten“; „Prüfungsnetz gestört“. Nicht auswählen, wenn eine normale Lernplattformaufgabe oder allgemeine Computerraumstörung ohne Prüfungsbezug vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Tickets mit unmittelbarem Prüfungstermin sind zeitkritisch und entsprechend zu priorisieren.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Prüfungs- und Klausursysteme“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Schulspezifische IT-Systeme \u003e Prüfungs- und Klausursysteme" + ], + "keywords": [ + "Prüfungssystem", + "Klausursystem", + "Prüfungsbrowser", + "Safe Exam Browser", + "digitale Prüfung", + "Klausuraccount", + "Prüfungsnetz", + "Prüfungs- und Klausursysteme", + "Schulspezifische IT-Systeme" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/prufungs-und-klausursysteme", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/09_ruckgabe-und-aussonderung.json b/services/agent/knowledge/09_ruckgabe-und-aussonderung.json index 3762192..0d35a19 100644 --- a/services/agent/knowledge/09_ruckgabe-und-aussonderung.json +++ b/services/agent/knowledge/09_ruckgabe-und-aussonderung.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-RUCKGABE-UND-AUSSONDERUNG-SELECT", - "title": "Rückgabe und Aussonderung", - "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Rückgabe und Aussonderung. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn Geräte bei Austritt, Austausch oder Bestandsbereinigung zurückgegeben, inventarisch abgeglichen, datenschutzgerecht gelöscht, eingelagert, wiederverwendet oder ausgesondert werden sollen. Typische Ticketformulierungen sind: „Notebook bei Austritt zurückgeben“; „Altgerät aussondern“; „Datenträger vor Entsorgung löschen“; „Gerät ins Lager zurücknehmen“. Nicht auswählen, wenn ein Gerät lediglich defekt ist und weiter genutzt werden soll; wenn nur eine Rechnung oder Inventarnummer korrigiert wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Beschaffung wird beteiligt, wenn Inventarstatus, Verwertung oder kaufmännische Aussonderung zu ändern sind.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Rückgabe und Aussonderung“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Arbeitsplatz und Endgeräte \u003e Rückgabe und Aussonderung" - ], - "keywords": [ - "Rückgabe", - "Aussonderung", - "Altgerät", - "Entsorgung", - "Gerät zurückgeben", - "Austritt", - "Daten löschen", - "Wiederverwendung", - "Inventar", - "Rückgabe und Aussonderung", - "Arbeitsplatz und Endgeräte" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/ruckgabe-und-aussonderung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-RUCKGABE-UND-AUSSONDERUNG-SELECT", + "title": "Rückgabe und Aussonderung", + "text": "Auswahlziel: Arbeitsplatz und Endgeräte \u003e Rückgabe und Aussonderung. Primär zuständiges Team: Support und Telekommunikation. Diese Kategorie ist auszuwählen, wenn Geräte bei Austritt, Austausch oder Bestandsbereinigung zurückgegeben, inventarisch abgeglichen, datenschutzgerecht gelöscht, eingelagert, wiederverwendet oder ausgesondert werden sollen. Typische Ticketformulierungen sind: „Notebook bei Austritt zurückgeben“; „Altgerät aussondern“; „Datenträger vor Entsorgung löschen“; „Gerät ins Lager zurücknehmen“. Nicht auswählen, wenn ein Gerät lediglich defekt ist und weiter genutzt werden soll; wenn nur eine Rechnung oder Inventarnummer korrigiert wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Beschaffung wird beteiligt, wenn Inventarstatus, Verwertung oder kaufmännische Aussonderung zu ändern sind.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Arbeitsplatz und Endgeräte \u003e Rückgabe und Aussonderung“ zuzuordnen. Primär zuständig ist „Support und Telekommunikation“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Arbeitsplatz und Endgeräte \u003e Rückgabe und Aussonderung" + ], + "keywords": [ + "Rückgabe", + "Aussonderung", + "Altgerät", + "Entsorgung", + "Gerät zurückgeben", + "Austritt", + "Daten löschen", + "Wiederverwendung", + "Inventar", + "Rückgabe und Aussonderung", + "Arbeitsplatz und Endgeräte" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/arbeitsplatz-und-endgerate/ruckgabe-und-aussonderung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/10_haushalts-und-budgetplanung.json b/services/agent/knowledge/10_haushalts-und-budgetplanung.json index 773ccfd..d930b6e 100644 --- a/services/agent/knowledge/10_haushalts-und-budgetplanung.json +++ b/services/agent/knowledge/10_haushalts-und-budgetplanung.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-HAUSHALTS-UND-BUDGETPLANUNG-SELECT", - "title": "Haushalts- und Budgetplanung", - "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Haushalts- und Budgetplanung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn IT-Budgets, Haushaltsansätze, Mittelbedarfe, Verpflichtungsermächtigungen, Kostenprognosen oder mehrjährige Finanzplanungen erstellt und abgestimmt werden sollen. Typische Ticketformulierungen sind: „Budget für nächstes Jahr planen“; „Mittelbedarf melden“; „Kostenprognose erstellen“; „Haushaltsansatz für IT-Projekt“. Nicht auswählen, wenn es um eine einzelne Bestellung, Rechnung oder technische Projektplanung ohne Budgetbezug geht. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Fachteams liefern Bedarfe und technische Mengen; Leitung und kaufmännische Stelle konsolidieren.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Haushalts- und Budgetplanung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Beschaffung, Verträge und Lizenzen \u003e Haushalts- und Budgetplanung" - ], - "keywords": [ - "Haushalt", - "Budget", - "Mittelbedarf", - "Finanzplanung", - "Kostenprognose", - "Haushaltsansatz", - "Budgetplanung", - "Investitionsplanung", - "Haushalts- und Budgetplanung", - "Beschaffung, Verträge und Lizenzen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/haushalts-und-budgetplanung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-HAUSHALTS-UND-BUDGETPLANUNG-SELECT", + "title": "Haushalts- und Budgetplanung", + "text": "Auswahlziel: Beschaffung, Verträge und Lizenzen \u003e Haushalts- und Budgetplanung. Primär zuständiges Team: Leitung und Finanzen / Beschaffung. Diese Kategorie ist auszuwählen, wenn IT-Budgets, Haushaltsansätze, Mittelbedarfe, Verpflichtungsermächtigungen, Kostenprognosen oder mehrjährige Finanzplanungen erstellt und abgestimmt werden sollen. Typische Ticketformulierungen sind: „Budget für nächstes Jahr planen“; „Mittelbedarf melden“; „Kostenprognose erstellen“; „Haushaltsansatz für IT-Projekt“. Nicht auswählen, wenn es um eine einzelne Bestellung, Rechnung oder technische Projektplanung ohne Budgetbezug geht. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Fachteams liefern Bedarfe und technische Mengen; Leitung und kaufmännische Stelle konsolidieren.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Beschaffung, Verträge und Lizenzen \u003e Haushalts- und Budgetplanung“ zuzuordnen. Primär zuständig ist „Leitung und Finanzen / Beschaffung“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Beschaffung, Verträge und Lizenzen \u003e Haushalts- und Budgetplanung" + ], + "keywords": [ + "Haushalt", + "Budget", + "Mittelbedarf", + "Finanzplanung", + "Kostenprognose", + "Haushaltsansatz", + "Budgetplanung", + "Investitionsplanung", + "Haushalts- und Budgetplanung", + "Beschaffung, Verträge und Lizenzen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/beschaffung-vertrage-und-lizenzen/haushalts-und-budgetplanung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/10_kubernetes.json b/services/agent/knowledge/10_kubernetes.json index f9217eb..7523284 100644 --- a/services/agent/knowledge/10_kubernetes.json +++ b/services/agent/knowledge/10_kubernetes.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-KUBERNETES-SELECT", - "title": "Kubernetes", - "text": "Auswahlziel: Server, Backend und Plattformen \u003e Kubernetes. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Kubernetes-Cluster, Nodes, Namespaces, Deployments, Services, Ingress, Pods oder clusterbezogene Plattformdienste betroffen sind. Typische Ticketformulierungen sind: „Pod startet nicht“; „Deployment fehlerhaft“; „Namespace anlegen“; „Kubernetes-Cluster erweitern“. Nicht auswählen, wenn nur ein einzelner Docker-Host ohne Kubernetes, eine klassische VM oder eine fachliche Anwendung ohne Clusterbezug gemeint ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Anwendungsdeployment kann mit DevOps zusammenhängen; Clusterbetrieb bleibt in dieser Kategorie.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Kubernetes“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Server, Backend und Plattformen \u003e Kubernetes" - ], - "keywords": [ - "Kubernetes", - "K8s", - "Pod", - "Deployment", - "Namespace", - "Ingress", - "Service", - "Node", - "Cluster", - "Server, Backend und Plattformen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/kubernetes", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-KUBERNETES-SELECT", + "title": "Kubernetes", + "text": "Auswahlziel: Server, Backend und Plattformen \u003e Kubernetes. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn Kubernetes-Cluster, Nodes, Namespaces, Deployments, Services, Ingress, Pods oder clusterbezogene Plattformdienste betroffen sind. Typische Ticketformulierungen sind: „Pod startet nicht“; „Deployment fehlerhaft“; „Namespace anlegen“; „Kubernetes-Cluster erweitern“. Nicht auswählen, wenn nur ein einzelner Docker-Host ohne Kubernetes, eine klassische VM oder eine fachliche Anwendung ohne Clusterbezug gemeint ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Anwendungsdeployment kann mit DevOps zusammenhängen; Clusterbetrieb bleibt in dieser Kategorie.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Kubernetes“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Server, Backend und Plattformen \u003e Kubernetes" + ], + "keywords": [ + "Kubernetes", + "K8s", + "Pod", + "Deployment", + "Namespace", + "Ingress", + "Service", + "Node", + "Cluster", + "Server, Backend und Plattformen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/kubernetes", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/10_microsoft-word.json b/services/agent/knowledge/10_microsoft-word.json index 33d4c7f..ee8ab7b 100644 --- a/services/agent/knowledge/10_microsoft-word.json +++ b/services/agent/knowledge/10_microsoft-word.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-MICROSOFT-WORD-SELECT", - "title": "Microsoft Word", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Microsoft Word. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Microsoft Word bei Dokumentbearbeitung, Formatierung, Feldern, Inhaltsverzeichnissen, Dokumentenschutz oder programmspezifischen Funktionen fehlerhaft arbeitet oder Unterstützung benötigt wird. Typische Ticketformulierungen sind: „Word-Dokument lässt sich nicht bearbeiten“; „Formatierung springt“; „Inhaltsverzeichnis aktualisiert nicht“; „Word stürzt bei Dokument ab“. Nicht auswählen, wenn eine allgemeine Office-Installation fehlt, eine organisationsweite Vorlage geändert werden soll, ein Makro betroffen ist oder das Betriebssystem selbst fehlerhaft ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Vorlagen, Makros und Serienbriefe besitzen eigene Kategorien, wenn diese der eigentliche Kern des Tickets sind.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Microsoft Word“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Microsoft Word" - ], - "keywords": [ - "Word", - "Microsoft Word", - "DOCX", - "Dokument", - "Formatierung", - "Inhaltsverzeichnis", - "Serienbrief", - "Textverarbeitung", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/microsoft-word", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-MICROSOFT-WORD-SELECT", + "title": "Microsoft Word", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Microsoft Word. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Microsoft Word bei Dokumentbearbeitung, Formatierung, Feldern, Inhaltsverzeichnissen, Dokumentenschutz oder programmspezifischen Funktionen fehlerhaft arbeitet oder Unterstützung benötigt wird. Typische Ticketformulierungen sind: „Word-Dokument lässt sich nicht bearbeiten“; „Formatierung springt“; „Inhaltsverzeichnis aktualisiert nicht“; „Word stürzt bei Dokument ab“. Nicht auswählen, wenn eine allgemeine Office-Installation fehlt, eine organisationsweite Vorlage geändert werden soll, ein Makro betroffen ist oder das Betriebssystem selbst fehlerhaft ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Vorlagen, Makros und Serienbriefe besitzen eigene Kategorien, wenn diese der eigentliche Kern des Tickets sind.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Microsoft Word“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Microsoft Word" + ], + "keywords": [ + "Word", + "Microsoft Word", + "DOCX", + "Dokument", + "Formatierung", + "Inhaltsverzeichnis", + "Serienbrief", + "Textverarbeitung", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/microsoft-word", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/10_schulverwaltungsanwendungen.json b/services/agent/knowledge/10_schulverwaltungsanwendungen.json index 5b3d1dd..30a72ff 100644 --- a/services/agent/knowledge/10_schulverwaltungsanwendungen.json +++ b/services/agent/knowledge/10_schulverwaltungsanwendungen.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-SCHULVERWALTUNGSANWENDUNGEN-SELECT", - "title": "Schulverwaltungsanwendungen", - "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Schulverwaltungsanwendungen. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn eine speziell in Schulen eingesetzte Verwaltungsanwendung für Stundenplan, Schülerverwaltung, Zeugnisse, Vertretung oder Schulorganisation gestört ist oder Unterstützung benötigt wird. Typische Ticketformulierungen sind: „Zeugnisprogramm zeigt Fehler“; „Stundenplananwendung funktioniert nicht“; „Schülerverwaltungssoftware gestört“; „Vertretungsplan synchronisiert nicht“. Nicht auswählen, wenn eine allgemeine kommunale Fachanwendung, Lernplattform oder reine Netzwerkstörung betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Fachanwendungs-Know-how kann mittelfristig an Fachanwendungen überführt werden; zentrale Plattformursachen an Infrastruktur.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Schulverwaltungsanwendungen“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Schulspezifische IT-Systeme \u003e Schulverwaltungsanwendungen" - ], - "keywords": [ - "Schulverwaltungsanwendung", - "Schülerverwaltung", - "Zeugnisprogramm", - "Stundenplan", - "Vertretungsplan", - "Schulsoftware", - "Schulorganisation", - "Schulverwaltungsanwendungen", - "Schulspezifische IT-Systeme" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/schulverwaltungsanwendungen", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-SCHULVERWALTUNGSANWENDUNGEN-SELECT", + "title": "Schulverwaltungsanwendungen", + "text": "Auswahlziel: Schulspezifische IT-Systeme \u003e Schulverwaltungsanwendungen. Primär zuständiges Team: Schulen. Diese Kategorie ist auszuwählen, wenn eine speziell in Schulen eingesetzte Verwaltungsanwendung für Stundenplan, Schülerverwaltung, Zeugnisse, Vertretung oder Schulorganisation gestört ist oder Unterstützung benötigt wird. Typische Ticketformulierungen sind: „Zeugnisprogramm zeigt Fehler“; „Stundenplananwendung funktioniert nicht“; „Schülerverwaltungssoftware gestört“; „Vertretungsplan synchronisiert nicht“. Nicht auswählen, wenn eine allgemeine kommunale Fachanwendung, Lernplattform oder reine Netzwerkstörung betroffen ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Fachanwendungs-Know-how kann mittelfristig an Fachanwendungen überführt werden; zentrale Plattformursachen an Infrastruktur.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Schulspezifische IT-Systeme \u003e Schulverwaltungsanwendungen“ zuzuordnen. Primär zuständig ist „Schulen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Schulspezifische IT-Systeme \u003e Schulverwaltungsanwendungen" + ], + "keywords": [ + "Schulverwaltungsanwendung", + "Schülerverwaltung", + "Zeugnisprogramm", + "Stundenplan", + "Vertretungsplan", + "Schulsoftware", + "Schulorganisation", + "Schulverwaltungsanwendungen", + "Schulspezifische IT-Systeme" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/schulspezifische-it-systeme/schulverwaltungsanwendungen", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/11_devops-und-automatisierung.json b/services/agent/knowledge/11_devops-und-automatisierung.json index 6e894a6..c75b8ea 100644 --- a/services/agent/knowledge/11_devops-und-automatisierung.json +++ b/services/agent/knowledge/11_devops-und-automatisierung.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-DEVOPS-UND-AUTOMATISIERUNG-SELECT", - "title": "DevOps und Automatisierung", - "text": "Auswahlziel: Server, Backend und Plattformen \u003e DevOps und Automatisierung. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn CI/CD-Pipelines, automatisierte Deployments, Infrastrukturcode, Konfigurationsmanagement, Build-Prozesse oder technische Automatisierungen erstellt oder gestört sind. Typische Ticketformulierungen sind: „Pipeline schlägt fehl“; „Deployment automatisieren“; „Ansible-Playbook anpassen“; „Infrastructure as Code bereitstellen“. Nicht auswählen, wenn nur ein Kubernetes-Pod ausfällt, eine normale Softwareinstallation am Arbeitsplatz benötigt wird oder ein fachlicher Workflow innerhalb einer Anwendung gemeint ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Fachanwendungsbezogene Releaseentscheidungen liegen bei Fachanwendungen; technische Delivery-Plattform bei Infrastruktur.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e DevOps und Automatisierung“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Server, Backend und Plattformen \u003e DevOps und Automatisierung" - ], - "keywords": [ - "DevOps", - "CI/CD", - "Pipeline", - "Deployment", - "Ansible", - "Terraform", - "Infrastructure as Code", - "Automation", - "Build", - "DevOps und Automatisierung", - "Server, Backend und Plattformen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/devops-und-automatisierung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-DEVOPS-UND-AUTOMATISIERUNG-SELECT", + "title": "DevOps und Automatisierung", + "text": "Auswahlziel: Server, Backend und Plattformen \u003e DevOps und Automatisierung. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn CI/CD-Pipelines, automatisierte Deployments, Infrastrukturcode, Konfigurationsmanagement, Build-Prozesse oder technische Automatisierungen erstellt oder gestört sind. Typische Ticketformulierungen sind: „Pipeline schlägt fehl“; „Deployment automatisieren“; „Ansible-Playbook anpassen“; „Infrastructure as Code bereitstellen“. Nicht auswählen, wenn nur ein Kubernetes-Pod ausfällt, eine normale Softwareinstallation am Arbeitsplatz benötigt wird oder ein fachlicher Workflow innerhalb einer Anwendung gemeint ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Fachanwendungsbezogene Releaseentscheidungen liegen bei Fachanwendungen; technische Delivery-Plattform bei Infrastruktur.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e DevOps und Automatisierung“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Server, Backend und Plattformen \u003e DevOps und Automatisierung" + ], + "keywords": [ + "DevOps", + "CI/CD", + "Pipeline", + "Deployment", + "Ansible", + "Terraform", + "Infrastructure as Code", + "Automation", + "Build", + "DevOps und Automatisierung", + "Server, Backend und Plattformen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/devops-und-automatisierung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/11_microsoft-excel.json b/services/agent/knowledge/11_microsoft-excel.json index 63f02e6..3ea430b 100644 --- a/services/agent/knowledge/11_microsoft-excel.json +++ b/services/agent/knowledge/11_microsoft-excel.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-MICROSOFT-EXCEL-SELECT", - "title": "Microsoft Excel", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Microsoft Excel. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Microsoft Excel bei Tabellen, Formeln, Pivot-Auswertungen, Datenimporten oder programmspezifischen Funktionen fehlerhaft arbeitet oder fachnahe Unterstützung benötigt wird. Typische Ticketformulierungen sind: „Excel-Formel funktioniert nicht“; „Datei öffnet fehlerhaft“; „Pivot-Tabelle aktualisiert nicht“; „Excel stürzt ab“. Nicht auswählen, wenn ein Add-in oder Makro die Ursache ist, eine Fachanwendung exportiert nicht korrekt oder die Datei nur wegen fehlender Berechtigung auf einer Ablage nicht geöffnet werden kann. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Makro- und Add-in-Probleme werden in der eigenen Office-Kategorie erfasst.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Microsoft Excel“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Microsoft Excel" - ], - "keywords": [ - "Excel", - "Microsoft Excel", - "XLSX", - "Tabelle", - "Formel", - "Pivot", - "Arbeitsmappe", - "Tabellenkalkulation", - "CSV", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/microsoft-excel", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-MICROSOFT-EXCEL-SELECT", + "title": "Microsoft Excel", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Microsoft Excel. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Microsoft Excel bei Tabellen, Formeln, Pivot-Auswertungen, Datenimporten oder programmspezifischen Funktionen fehlerhaft arbeitet oder fachnahe Unterstützung benötigt wird. Typische Ticketformulierungen sind: „Excel-Formel funktioniert nicht“; „Datei öffnet fehlerhaft“; „Pivot-Tabelle aktualisiert nicht“; „Excel stürzt ab“. Nicht auswählen, wenn ein Add-in oder Makro die Ursache ist, eine Fachanwendung exportiert nicht korrekt oder die Datei nur wegen fehlender Berechtigung auf einer Ablage nicht geöffnet werden kann. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Makro- und Add-in-Probleme werden in der eigenen Office-Kategorie erfasst.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Microsoft Excel“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Microsoft Excel" + ], + "keywords": [ + "Excel", + "Microsoft Excel", + "XLSX", + "Tabelle", + "Formel", + "Pivot", + "Arbeitsmappe", + "Tabellenkalkulation", + "CSV", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/microsoft-excel", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/12_microsoft-powerpoint.json b/services/agent/knowledge/12_microsoft-powerpoint.json index 9a22ef7..0c4fbd5 100644 --- a/services/agent/knowledge/12_microsoft-powerpoint.json +++ b/services/agent/knowledge/12_microsoft-powerpoint.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-MICROSOFT-POWERPOINT-SELECT", - "title": "Microsoft PowerPoint", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Microsoft PowerPoint. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Microsoft PowerPoint bei Präsentationen, Folienlayouts, Medien, Referentenansicht oder programmspezifischen Funktionen nicht korrekt arbeitet oder Unterstützung benötigt wird. Typische Ticketformulierungen sind: „Präsentation lässt sich nicht starten“; „Video in Folie spielt nicht“; „Folienlayout fehlerhaft“; „PowerPoint stürzt ab“. Nicht auswählen, wenn Beamer, Monitor oder Videokonferenztechnik physisch nicht funktioniert oder eine zentrale Office-Installation fehlt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Physische Anzeige- und Konferenzprobleme bleiben beim Support.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Microsoft PowerPoint“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Microsoft PowerPoint" - ], - "keywords": [ - "PowerPoint", - "Microsoft PowerPoint", - "PPTX", - "Präsentation", - "Folie", - "Referentenansicht", - "Layout", - "Bildschirmpräsentation", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/microsoft-powerpoint", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-MICROSOFT-POWERPOINT-SELECT", + "title": "Microsoft PowerPoint", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Microsoft PowerPoint. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Microsoft PowerPoint bei Präsentationen, Folienlayouts, Medien, Referentenansicht oder programmspezifischen Funktionen nicht korrekt arbeitet oder Unterstützung benötigt wird. Typische Ticketformulierungen sind: „Präsentation lässt sich nicht starten“; „Video in Folie spielt nicht“; „Folienlayout fehlerhaft“; „PowerPoint stürzt ab“. Nicht auswählen, wenn Beamer, Monitor oder Videokonferenztechnik physisch nicht funktioniert oder eine zentrale Office-Installation fehlt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Physische Anzeige- und Konferenzprobleme bleiben beim Support.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Microsoft PowerPoint“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Microsoft PowerPoint" + ], + "keywords": [ + "PowerPoint", + "Microsoft PowerPoint", + "PPTX", + "Präsentation", + "Folie", + "Referentenansicht", + "Layout", + "Bildschirmpräsentation", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/microsoft-powerpoint", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/12_monitoring.json b/services/agent/knowledge/12_monitoring.json index 65fe22d..73e5c44 100644 --- a/services/agent/knowledge/12_monitoring.json +++ b/services/agent/knowledge/12_monitoring.json @@ -1,28 +1,28 @@ -{ - "id": "KAT-MONITORING-SELECT", - "title": "Monitoring", - "text": "Auswahlziel: Server, Backend und Plattformen \u003e Monitoring. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn technische Überwachung, Checks, Alarmierung, Dashboards, Schwellwerte oder Benachrichtigungen für Infrastruktur und Plattformen eingerichtet oder fehlerhaft sind. Typische Ticketformulierungen sind: „Monitoring-Check hinzufügen“; „Alarm wird nicht ausgelöst“; „Schwellwert anpassen“; „Infrastruktur-Dashboard erstellen“. Nicht auswählen, wenn ein konkreter Dienst bereits ausgefallen ist und die Behebung im Vordergrund steht oder eine fachliche Statistik benötigt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Ein Alarm ist ein Hinweis; die eigentliche Störung kann zusätzlich in ihrer fachlich passenden Kategorie erfasst werden.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Monitoring“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Server, Backend und Plattformen \u003e Monitoring" - ], - "keywords": [ - "Monitoring", - "Überwachung", - "Alarmierung", - "Check", - "Schwellwert", - "Dashboard", - "Alert", - "Metrik", - "Nagios", - "Zabbix", - "Server, Backend und Plattformen" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/monitoring", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-MONITORING-SELECT", + "title": "Monitoring", + "text": "Auswahlziel: Server, Backend und Plattformen \u003e Monitoring. Primär zuständiges Team: Infrastruktur und Backend. Diese Kategorie ist auszuwählen, wenn technische Überwachung, Checks, Alarmierung, Dashboards, Schwellwerte oder Benachrichtigungen für Infrastruktur und Plattformen eingerichtet oder fehlerhaft sind. Typische Ticketformulierungen sind: „Monitoring-Check hinzufügen“; „Alarm wird nicht ausgelöst“; „Schwellwert anpassen“; „Infrastruktur-Dashboard erstellen“. Nicht auswählen, wenn ein konkreter Dienst bereits ausgefallen ist und die Behebung im Vordergrund steht oder eine fachliche Statistik benötigt wird. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Ein Alarm ist ein Hinweis; die eigentliche Störung kann zusätzlich in ihrer fachlich passenden Kategorie erfasst werden.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Server, Backend und Plattformen \u003e Monitoring“ zuzuordnen. Primär zuständig ist „Infrastruktur und Backend“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Server, Backend und Plattformen \u003e Monitoring" + ], + "keywords": [ + "Monitoring", + "Überwachung", + "Alarmierung", + "Check", + "Schwellwert", + "Dashboard", + "Alert", + "Metrik", + "Nagios", + "Zabbix", + "Server, Backend und Plattformen" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/server-backend-und-plattformen/monitoring", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/13_outlook-client.json b/services/agent/knowledge/13_outlook-client.json index 47ca578..adbdeac 100644 --- a/services/agent/knowledge/13_outlook-client.json +++ b/services/agent/knowledge/13_outlook-client.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-OUTLOOK-CLIENT-SELECT", - "title": "Outlook-Client", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Outlook-Client. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn die lokale Outlook-Anwendung nicht startet, Profile oder Ansichten fehlerhaft sind, Suche, Kalenderdarstellung, Signatur oder lokale Outlook-Funktionen nicht korrekt arbeiten. Typische Ticketformulierungen sind: „Outlook startet nicht“; „Outlook-Profil defekt“; „Suche findet nichts“; „Kalenderansicht fehlerhaft“; „Signatur fehlt“. Nicht auswählen, wenn das Postfach serverseitig nicht erreichbar ist, E-Mails organisationsweit nicht zugestellt werden, ein Funktionspostfach beantragt wird oder nur das Kennwort gesperrt ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Serverseitige Mail- und Postfachstörungen werden an Infrastruktur und Backend übergeben.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Outlook-Client“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Outlook-Client" - ], - "keywords": [ - "Outlook", - "Outlook-Client", - "Profil", - "Outlook Suche", - "Kalenderansicht", - "Signatur", - "OST", - "PST", - "lokales Outlook", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/outlook-client", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-OUTLOOK-CLIENT-SELECT", + "title": "Outlook-Client", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Outlook-Client. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn die lokale Outlook-Anwendung nicht startet, Profile oder Ansichten fehlerhaft sind, Suche, Kalenderdarstellung, Signatur oder lokale Outlook-Funktionen nicht korrekt arbeiten. Typische Ticketformulierungen sind: „Outlook startet nicht“; „Outlook-Profil defekt“; „Suche findet nichts“; „Kalenderansicht fehlerhaft“; „Signatur fehlt“. Nicht auswählen, wenn das Postfach serverseitig nicht erreichbar ist, E-Mails organisationsweit nicht zugestellt werden, ein Funktionspostfach beantragt wird oder nur das Kennwort gesperrt ist. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Serverseitige Mail- und Postfachstörungen werden an Infrastruktur und Backend übergeben.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Outlook-Client“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Outlook-Client" + ], + "keywords": [ + "Outlook", + "Outlook-Client", + "Profil", + "Outlook Suche", + "Kalenderansicht", + "Signatur", + "OST", + "PST", + "lokales Outlook", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/outlook-client", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/14_office-vorlagen.json b/services/agent/knowledge/14_office-vorlagen.json index d2673c5..20c16f8 100644 --- a/services/agent/knowledge/14_office-vorlagen.json +++ b/services/agent/knowledge/14_office-vorlagen.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-OFFICE-VORLAGEN-SELECT", - "title": "Office-Vorlagen", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Office-Vorlagen. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn zentrale Word-, Excel- oder PowerPoint-Vorlagen erstellt, geändert, verteilt oder korrigiert werden sollen, einschließlich Briefkopf, Layout, Textbausteinen und Organisationsvorgaben. Typische Ticketformulierungen sind: „Briefvorlage anpassen“; „Neues Corporate-Design-Layout“; „Vorlage wird nicht geladen“; „Textbaustein zentral ändern“. Nicht auswählen, wenn nur ein einzelnes Dokument formatiert werden soll, ein Makro fehlerhaft ist oder ein Drucker die Vorlage nicht ausgibt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Inhaltliche Freigaben durch zuständige Organisationseinheiten bleiben erforderlich.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Office-Vorlagen“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Office-Vorlagen" - ], - "keywords": [ - "Office-Vorlage", - "Word-Vorlage", - "Excel-Vorlage", - "PowerPoint-Vorlage", - "Briefkopf", - "Template", - "Textbaustein", - "Corporate Design", - "Office-Vorlagen", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/office-vorlagen", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-OFFICE-VORLAGEN-SELECT", + "title": "Office-Vorlagen", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Office-Vorlagen. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn zentrale Word-, Excel- oder PowerPoint-Vorlagen erstellt, geändert, verteilt oder korrigiert werden sollen, einschließlich Briefkopf, Layout, Textbausteinen und Organisationsvorgaben. Typische Ticketformulierungen sind: „Briefvorlage anpassen“; „Neues Corporate-Design-Layout“; „Vorlage wird nicht geladen“; „Textbaustein zentral ändern“. Nicht auswählen, wenn nur ein einzelnes Dokument formatiert werden soll, ein Makro fehlerhaft ist oder ein Drucker die Vorlage nicht ausgibt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Inhaltliche Freigaben durch zuständige Organisationseinheiten bleiben erforderlich.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Office-Vorlagen“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Office-Vorlagen" + ], + "keywords": [ + "Office-Vorlage", + "Word-Vorlage", + "Excel-Vorlage", + "PowerPoint-Vorlage", + "Briefkopf", + "Template", + "Textbaustein", + "Corporate Design", + "Office-Vorlagen", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/office-vorlagen", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/15_office-add-ins-und-makros.json b/services/agent/knowledge/15_office-add-ins-und-makros.json index d40f08f..51f1197 100644 --- a/services/agent/knowledge/15_office-add-ins-und-makros.json +++ b/services/agent/knowledge/15_office-add-ins-und-makros.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-OFFICE-ADD-INS-UND-MAKROS-SELECT", - "title": "Office-Add-ins und Makros", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Office-Add-ins und Makros. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn ein Office-Add-in, COM-Add-in, VBA-Makro oder automatisierter Office-Ablauf installiert, freigegeben, repariert oder angepasst werden soll. Typische Ticketformulierungen sind: „Excel-Makro läuft nicht“; „Outlook-Add-in fehlt“; „VBA-Fehler“; „COM-Add-in deaktiviert“. Nicht auswählen, wenn die Basisanwendung Word, Excel, PowerPoint oder Outlook ohne Add-in-Bezug fehlerhaft ist oder neue allgemeine Software beschafft werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Sicherheitsbewertung und Signierung können Infrastruktur und Backend beziehungsweise IT-Sicherheit einbeziehen.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Office-Add-ins und Makros“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Office-Add-ins und Makros" - ], - "keywords": [ - "Add-in", - "Plugin", - "Makro", - "VBA", - "COM-Add-in", - "Office-Erweiterung", - "Automatisierung", - "Makrofehler", - "Office-Add-ins und Makros", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/office-add-ins-und-makros", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-OFFICE-ADD-INS-UND-MAKROS-SELECT", + "title": "Office-Add-ins und Makros", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Office-Add-ins und Makros. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn ein Office-Add-in, COM-Add-in, VBA-Makro oder automatisierter Office-Ablauf installiert, freigegeben, repariert oder angepasst werden soll. Typische Ticketformulierungen sind: „Excel-Makro läuft nicht“; „Outlook-Add-in fehlt“; „VBA-Fehler“; „COM-Add-in deaktiviert“. Nicht auswählen, wenn die Basisanwendung Word, Excel, PowerPoint oder Outlook ohne Add-in-Bezug fehlerhaft ist oder neue allgemeine Software beschafft werden soll. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Sicherheitsbewertung und Signierung können Infrastruktur und Backend beziehungsweise IT-Sicherheit einbeziehen.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Office-Add-ins und Makros“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Office-Add-ins und Makros" + ], + "keywords": [ + "Add-in", + "Plugin", + "Makro", + "VBA", + "COM-Add-in", + "Office-Erweiterung", + "Automatisierung", + "Makrofehler", + "Office-Add-ins und Makros", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/office-add-ins-und-makros", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/16_office-aktivierung-und-lizenzierung.json b/services/agent/knowledge/16_office-aktivierung-und-lizenzierung.json index ab73dfd..fbd3556 100644 --- a/services/agent/knowledge/16_office-aktivierung-und-lizenzierung.json +++ b/services/agent/knowledge/16_office-aktivierung-und-lizenzierung.json @@ -1,26 +1,26 @@ -{ - "id": "KAT-OFFICE-AKTIVIERUNG-UND-LIZENZIERUNG-SELECT", - "title": "Office-Aktivierung und Lizenzierung", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Office-Aktivierung und Lizenzierung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Microsoft Office nicht aktiviert ist, eine Lizenz technisch nicht erkannt wird, ein Lizenzstatus fehlerhaft ist oder die korrekte Office-Edition zugeordnet werden muss. Typische Ticketformulierungen sind: „Office nicht aktiviert“; „Lizenz kann nicht überprüft werden“; „Produkt nicht lizenziert“; „Falsche Office-Edition“. Nicht auswählen, wenn neue Lizenzen gekauft, Verträge verlängert oder Rechnungen bearbeitet werden sollen; diese Vorgänge gehören zu Beschaffung, Verträge und Lizenzen. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Lizenzzuordnung liegt bei Fachanwendungen; Einkauf und Vertragsverwaltung bei Leitung und Finanzen / Beschaffung.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Office-Aktivierung und Lizenzierung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Office-Aktivierung und Lizenzierung" - ], - "keywords": [ - "Office Aktivierung", - "Lizenzfehler", - "Produkt nicht lizenziert", - "Microsoft 365 Lizenz", - "Office-Lizenz", - "Aktivierung", - "Lizenzstatus", - "Office-Aktivierung und Lizenzierung", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/office-aktivierung-und-lizenzierung", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-OFFICE-AKTIVIERUNG-UND-LIZENZIERUNG-SELECT", + "title": "Office-Aktivierung und Lizenzierung", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Office-Aktivierung und Lizenzierung. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Microsoft Office nicht aktiviert ist, eine Lizenz technisch nicht erkannt wird, ein Lizenzstatus fehlerhaft ist oder die korrekte Office-Edition zugeordnet werden muss. Typische Ticketformulierungen sind: „Office nicht aktiviert“; „Lizenz kann nicht überprüft werden“; „Produkt nicht lizenziert“; „Falsche Office-Edition“. Nicht auswählen, wenn neue Lizenzen gekauft, Verträge verlängert oder Rechnungen bearbeitet werden sollen; diese Vorgänge gehören zu Beschaffung, Verträge und Lizenzen. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Technische Lizenzzuordnung liegt bei Fachanwendungen; Einkauf und Vertragsverwaltung bei Leitung und Finanzen / Beschaffung.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Office-Aktivierung und Lizenzierung“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Office-Aktivierung und Lizenzierung" + ], + "keywords": [ + "Office Aktivierung", + "Lizenzfehler", + "Produkt nicht lizenziert", + "Microsoft 365 Lizenz", + "Office-Lizenz", + "Aktivierung", + "Lizenzstatus", + "Office-Aktivierung und Lizenzierung", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/office-aktivierung-und-lizenzierung", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/17_serienbriefe-und-dokumentfunktionen.json b/services/agent/knowledge/17_serienbriefe-und-dokumentfunktionen.json index f3dc83d..08dac02 100644 --- a/services/agent/knowledge/17_serienbriefe-und-dokumentfunktionen.json +++ b/services/agent/knowledge/17_serienbriefe-und-dokumentfunktionen.json @@ -1,27 +1,27 @@ -{ - "id": "KAT-SERIENBRIEFE-UND-DOKUMENTFUNKTIONEN-SELECT", - "title": "Serienbriefe und Dokumentfunktionen", - "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Serienbriefe und Dokumentfunktionen. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Serienbriefe, Datenquellen, Feldfunktionen, Dokumentverknüpfungen, Etiketten oder automatisierte Dokumenterstellung in Office fehlerhaft sind oder eingerichtet werden sollen. Typische Ticketformulierungen sind: „Serienbrief verbindet Datenquelle nicht“; „Feldfunktion zeigt Fehler“; „Etikettendruck aus Word vorbereiten“; „Dokument automatisch befüllen“. Nicht auswählen, wenn ein allgemeines Word-Formatierungsproblem, ein Fachverfahrensbericht oder ein Druckerdefekt vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei Datenübergabe aus einer Fachanwendung ist gegebenenfalls zusätzlich die Schnittstellenkategorie relevant.", - "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Serienbriefe und Dokumentfunktionen“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", - "auto_reply": false, - "min_score": 0.7, - "categories": [ - "Fachanwendungen und Microsoft Office \u003e Serienbriefe und Dokumentfunktionen" - ], - "keywords": [ - "Serienbrief", - "Datenquelle", - "Feldfunktion", - "Etiketten", - "Dokumentfunktion", - "Mail Merge", - "Seriendruck", - "Dokumentautomatisierung", - "Serienbriefe und Dokumentfunktionen", - "Fachanwendungen und Microsoft Office" - ], - "source": "internal-category", - "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/serienbriefe-und-dokumentfunktionen", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KAT-SERIENBRIEFE-UND-DOKUMENTFUNKTIONEN-SELECT", + "title": "Serienbriefe und Dokumentfunktionen", + "text": "Auswahlziel: Fachanwendungen und Microsoft Office \u003e Serienbriefe und Dokumentfunktionen. Primär zuständiges Team: Fachanwendungen. Diese Kategorie ist auszuwählen, wenn Serienbriefe, Datenquellen, Feldfunktionen, Dokumentverknüpfungen, Etiketten oder automatisierte Dokumenterstellung in Office fehlerhaft sind oder eingerichtet werden sollen. Typische Ticketformulierungen sind: „Serienbrief verbindet Datenquelle nicht“; „Feldfunktion zeigt Fehler“; „Etikettendruck aus Word vorbereiten“; „Dokument automatisch befüllen“. Nicht auswählen, wenn ein allgemeines Word-Formatierungsproblem, ein Fachverfahrensbericht oder ein Druckerdefekt vorliegt. Für die Klassifizierung sind der konkret betroffene Dienst oder Gegenstand, die Fehlermeldung, die Anzahl betroffener Personen, der Standort sowie die Unterscheidung zwischen Störung, Berechtigung, Bestellung und Änderungswunsch besonders wichtig. Einzelne unspezifische Wörter wie „geht nicht“, „Problem“ oder „dringend“ reichen ohne Sachbezug nicht für diese Zuordnung aus. Abgrenzungsregel: Bei Datenübergabe aus einer Fachanwendung ist gegebenenfalls zusätzlich die Schnittstellenkategorie relevant.", + "answer": "Interne Klassifizierung: Das Ticket ist der Kategorie „Fachanwendungen und Microsoft Office \u003e Serienbriefe und Dokumentfunktionen“ zuzuordnen. Primär zuständig ist „Fachanwendungen“. Vor der endgültigen Zuweisung sind die genannten Ausschlusskriterien und Schnittstellen zu prüfen.", + "auto_reply": false, + "min_score": 0.7, + "categories": [ + "Fachanwendungen und Microsoft Office \u003e Serienbriefe und Dokumentfunktionen" + ], + "keywords": [ + "Serienbrief", + "Datenquelle", + "Feldfunktion", + "Etiketten", + "Dokumentfunktion", + "Mail Merge", + "Seriendruck", + "Dokumentautomatisierung", + "Serienbriefe und Dokumentfunktionen", + "Fachanwendungen und Microsoft Office" + ], + "source": "internal-category", + "source_uri": "kb://internal-kb/glpi-kategorien/fachanwendungen-und-microsoft-office/serienbriefe-und-dokumentfunktionen", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/agent/knowledge/example-vpn.json b/services/agent/knowledge/example-vpn.json index 0d813fd..d0acddb 100644 --- a/services/agent/knowledge/example-vpn.json +++ b/services/agent/knowledge/example-vpn.json @@ -1,20 +1,20 @@ -{ - "id": "KB-EXAMPLE-VPN", - "title": "Beispiel: VPN Gateway nicht erreichbar", - "text": "Beispieldokument. Aktivieren oder ersetzen Sie diesen Eintrag erst nach fachlicher Prüfung. Typisches Symptom: VPN meldet, dass das Gateway nicht erreichbar ist.", - "answer": "Bitte trennen Sie die bestehende VPN-Verbindung vollständig und starten Sie den VPN-Client anschließend neu. Sollte die Meldung weiterhin auftreten, antworten Sie bitte auf dieses Ticket mit dem genauen Fehlertext.", - "auto_reply": false, - "min_score": 0.92, - "categories": [ - - ], - "keywords": [ - "VPN", - "Gateway", - "nicht erreichbar" - ], - "source": "internal-category", - "source_uri": "kb://examples/vpn-gateway", - "language": "de-DE", - "communication_style": "formal" +{ + "id": "KB-EXAMPLE-VPN", + "title": "Beispiel: VPN Gateway nicht erreichbar", + "text": "Beispieldokument. Aktivieren oder ersetzen Sie diesen Eintrag erst nach fachlicher Prüfung. Typisches Symptom: VPN meldet, dass das Gateway nicht erreichbar ist.", + "answer": "Bitte trennen Sie die bestehende VPN-Verbindung vollständig und starten Sie den VPN-Client anschließend neu. Sollte die Meldung weiterhin auftreten, antworten Sie bitte auf dieses Ticket mit dem genauen Fehlertext.", + "auto_reply": false, + "min_score": 0.92, + "categories": [ + + ], + "keywords": [ + "VPN", + "Gateway", + "nicht erreichbar" + ], + "source": "internal-category", + "source_uri": "kb://examples/vpn-gateway", + "language": "de-DE", + "communication_style": "formal" } \ No newline at end of file diff --git a/services/control/engineering-graph.json b/services/control/engineering-graph.json index 858e6cd..3665df0 100644 --- a/services/control/engineering-graph.json +++ b/services/control/engineering-graph.json @@ -82,6 +82,16 @@ "path": "platform/neuroforge/internal/brain/brain.go" } }, + { + "id": "file:platform/neuroforge/internal/brain/distributed.go", + "kind": "file", + "label": "distributed.go", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "path": "platform/neuroforge/internal/brain/distributed.go" + } + }, { "id": "file:platform/neuroforge/internal/brain/goal_progress.go", "kind": "file", @@ -92,6 +102,16 @@ "path": "platform/neuroforge/internal/brain/goal_progress.go" } }, + { + "id": "file:platform/neuroforge/internal/brain/orchestrator.go", + "kind": "file", + "label": "orchestrator.go", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "path": "platform/neuroforge/internal/brain/orchestrator.go" + } + }, { "id": "file:platform/neuroforge/internal/brain/policy.go", "kind": "file", @@ -102,6 +122,16 @@ "path": "platform/neuroforge/internal/brain/policy.go" } }, + { + "id": "file:platform/neuroforge/internal/brain/research_quality.go", + "kind": "file", + "label": "research_quality.go", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "path": "platform/neuroforge/internal/brain/research_quality.go" + } + }, { "id": "file:platform/neuroforge/internal/brain/research_trace.go", "kind": "file", @@ -122,6 +152,16 @@ "path": "platform/neuroforge/internal/brain/staging.go" } }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go", + "kind": "file", + "label": "staging_quality.go", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "path": "platform/neuroforge/internal/brain/staging_quality.go" + } + }, { "id": "file:platform/neuroforge/internal/brain/v3.go", "kind": "file", @@ -372,6 +412,16 @@ "path": "platform/neuroforge/internal/store/diskann.go" } }, + { + "id": "file:platform/neuroforge/internal/store/graph_index.go", + "kind": "file", + "label": "graph_index.go", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "path": "platform/neuroforge/internal/store/graph_index.go" + } + }, { "id": "file:platform/neuroforge/internal/store/index_segments.go", "kind": "file", @@ -422,6 +472,16 @@ "path": "platform/neuroforge/internal/store/observability.go" } }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go", + "kind": "file", + "label": "orchestrator.go", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "path": "platform/neuroforge/internal/store/orchestrator.go" + } + }, { "id": "file:platform/neuroforge/internal/store/pagecache.go", "kind": "file", @@ -9306,7 +9366,7 @@ "community": "mega-control", "meta": { "exported": false, - "line": 178, + "line": 188, "path": "services/control/main.go" } }, @@ -9402,7 +9462,7 @@ "community": "mega-control", "meta": { "exported": false, - "line": 201, + "line": 211, "path": "services/control/main.go" } }, @@ -9414,7 +9474,7 @@ "community": "mega-control", "meta": { "exported": false, - "line": 244, + "line": 254, "path": "services/control/main.go" } }, @@ -9438,7 +9498,7 @@ "community": "mega-control", "meta": { "exported": false, - "line": 211, + "line": 221, "path": "services/control/main.go" } }, @@ -9490,6 +9550,30 @@ "path": "services/control/graph.go" } }, + { + "id": "function:mega-control:server.handleNeuroForgeGraphStatus", + "kind": "function", + "label": "server.handleNeuroForgeGraphStatus", + "group": "engineering", + "community": "mega-control", + "meta": { + "exported": false, + "line": 153, + "path": "services/control/main.go" + } + }, + { + "id": "function:mega-control:server.handleNeuroForgeOrchestrator", + "kind": "function", + "label": "server.handleNeuroForgeOrchestrator", + "group": "engineering", + "community": "mega-control", + "meta": { + "exported": false, + "line": 149, + "path": "services/control/main.go" + } + }, { "id": "function:mega-control:server.handleResearchGraph", "kind": "function", @@ -9522,7 +9606,7 @@ "community": "mega-control", "meta": { "exported": false, - "line": 215, + "line": 225, "path": "services/control/main.go" } }, @@ -9558,7 +9642,7 @@ "community": "mega-control", "meta": { "exported": false, - "line": 232, + "line": 242, "path": "services/control/main.go" } }, @@ -9594,7 +9678,7 @@ "community": "mega-control", "meta": { "exported": false, - "line": 147, + "line": 157, "path": "services/control/main.go" } }, @@ -9606,7 +9690,7 @@ "community": "mega-control", "meta": { "exported": false, - "line": 279, + "line": 289, "path": "services/control/main.go" } }, @@ -9670,6 +9754,18 @@ "path": "platform/neuroforge/cmd/server/main.go" } }, + { + "id": "function:neuroforge/cmd/server:envFloat", + "kind": "function", + "label": "envFloat", + "group": "engineering", + "community": "neuroforge/cmd/server", + "meta": { + "exported": false, + "line": 50, + "path": "platform/neuroforge/cmd/server/main.go" + } + }, { "id": "function:neuroforge/cmd/server:envInt", "kind": "function", @@ -9682,6 +9778,18 @@ "path": "platform/neuroforge/cmd/server/main.go" } }, + { + "id": "function:neuroforge/cmd/server:firstNonEmptyMain", + "kind": "function", + "label": "firstNonEmptyMain", + "group": "engineering", + "community": "neuroforge/cmd/server", + "meta": { + "exported": false, + "line": 514, + "path": "platform/neuroforge/cmd/server/main.go" + } + }, { "id": "function:neuroforge/cmd/server:main", "kind": "function", @@ -9690,7 +9798,7 @@ "community": "neuroforge/cmd/server", "meta": { "exported": false, - "line": 92, + "line": 104, "path": "platform/neuroforge/cmd/server/main.go" } }, @@ -9702,7 +9810,7 @@ "community": "neuroforge/cmd/server", "meta": { "exported": false, - "line": 85, + "line": 97, "path": "platform/neuroforge/cmd/server/main.go" } }, @@ -9714,7 +9822,7 @@ "community": "neuroforge/cmd/server", "meta": { "exported": false, - "line": 99, + "line": 111, "path": "platform/neuroforge/cmd/server/main.go" } }, @@ -9726,7 +9834,7 @@ "community": "neuroforge/cmd/server", "meta": { "exported": false, - "line": 50, + "line": 62, "path": "platform/neuroforge/cmd/server/main.go" } }, @@ -9738,7 +9846,7 @@ "community": "neuroforge/cmd/server", "meta": { "exported": false, - "line": 65, + "line": 77, "path": "platform/neuroforge/cmd/server/main.go" } }, @@ -9750,7 +9858,7 @@ "community": "neuroforge/cmd/worker", "meta": { "exported": false, - "line": 76, + "line": 255, "path": "platform/neuroforge/cmd/worker/main.go" } }, @@ -9762,7 +9870,79 @@ "community": "neuroforge/cmd/worker", "meta": { "exported": false, - "line": 123, + "line": 416, + "path": "platform/neuroforge/cmd/worker/main.go" + } + }, + { + "id": "function:neuroforge/cmd/worker:csv", + "kind": "function", + "label": "csv", + "group": "engineering", + "community": "neuroforge/cmd/worker", + "meta": { + "exported": false, + "line": 454, + "path": "platform/neuroforge/cmd/worker/main.go" + } + }, + { + "id": "function:neuroforge/cmd/worker:envDuration", + "kind": "function", + "label": "envDuration", + "group": "engineering", + "community": "neuroforge/cmd/worker", + "meta": { + "exported": false, + "line": 507, + "path": "platform/neuroforge/cmd/worker/main.go" + } + }, + { + "id": "function:neuroforge/cmd/worker:envInt", + "kind": "function", + "label": "envInt", + "group": "engineering", + "community": "neuroforge/cmd/worker", + "meta": { + "exported": false, + "line": 495, + "path": "platform/neuroforge/cmd/worker/main.go" + } + }, + { + "id": "function:neuroforge/cmd/worker:firstNonEmpty", + "kind": "function", + "label": "firstNonEmpty", + "group": "engineering", + "community": "neuroforge/cmd/worker", + "meta": { + "exported": false, + "line": 486, + "path": "platform/neuroforge/cmd/worker/main.go" + } + }, + { + "id": "function:neuroforge/cmd/worker:hasCap", + "kind": "function", + "label": "hasCap", + "group": "engineering", + "community": "neuroforge/cmd/worker", + "meta": { + "exported": false, + "line": 468, + "path": "platform/neuroforge/cmd/worker/main.go" + } + }, + { + "id": "function:neuroforge/cmd/worker:heartbeatLoop", + "kind": "function", + "label": "heartbeatLoop", + "group": "engineering", + "community": "neuroforge/cmd/worker", + "meta": { + "exported": false, + "line": 239, "path": "platform/neuroforge/cmd/worker/main.go" } }, @@ -9774,7 +9954,55 @@ "community": "neuroforge/cmd/worker", "meta": { "exported": false, - "line": 69, + "line": 446, + "path": "platform/neuroforge/cmd/worker/main.go" + } + }, + { + "id": "function:neuroforge/cmd/worker:leaseSet.add", + "kind": "function", + "label": "leaseSet.add", + "group": "engineering", + "community": "neuroforge/cmd/worker", + "meta": { + "exported": false, + "line": 100, + "path": "platform/neuroforge/cmd/worker/main.go" + } + }, + { + "id": "function:neuroforge/cmd/worker:leaseSet.del", + "kind": "function", + "label": "leaseSet.del", + "group": "engineering", + "community": "neuroforge/cmd/worker", + "meta": { + "exported": false, + "line": 105, + "path": "platform/neuroforge/cmd/worker/main.go" + } + }, + { + "id": "function:neuroforge/cmd/worker:leaseSet.snapshot", + "kind": "function", + "label": "leaseSet.snapshot", + "group": "engineering", + "community": "neuroforge/cmd/worker", + "meta": { + "exported": false, + "line": 110, + "path": "platform/neuroforge/cmd/worker/main.go" + } + }, + { + "id": "function:neuroforge/cmd/worker:lowerDefault", + "kind": "function", + "label": "lowerDefault", + "group": "engineering", + "community": "neuroforge/cmd/worker", + "meta": { + "exported": false, + "line": 478, "path": "platform/neuroforge/cmd/worker/main.go" } }, @@ -9786,7 +10014,79 @@ "community": "neuroforge/cmd/worker", "meta": { "exported": false, - "line": 39, + "line": 120, + "path": "platform/neuroforge/cmd/worker/main.go" + } + }, + { + "id": "function:neuroforge/cmd/worker:ollamaChat", + "kind": "function", + "label": "ollamaChat", + "group": "engineering", + "community": "neuroforge/cmd/worker", + "meta": { + "exported": false, + "line": 354, + "path": "platform/neuroforge/cmd/worker/main.go" + } + }, + { + "id": "function:neuroforge/cmd/worker:ollamaEmbed", + "kind": "function", + "label": "ollamaEmbed", + "group": "engineering", + "community": "neuroforge/cmd/worker", + "meta": { + "exported": false, + "line": 332, + "path": "platform/neuroforge/cmd/worker/main.go" + } + }, + { + "id": "function:neuroforge/cmd/worker:pollLoop", + "kind": "function", + "label": "pollLoop", + "group": "engineering", + "community": "neuroforge/cmd/worker", + "meta": { + "exported": false, + "line": 189, + "path": "platform/neuroforge/cmd/worker/main.go" + } + }, + { + "id": "function:neuroforge/cmd/worker:postJSON", + "kind": "function", + "label": "postJSON", + "group": "engineering", + "community": "neuroforge/cmd/worker", + "meta": { + "exported": false, + "line": 421, + "path": "platform/neuroforge/cmd/worker/main.go" + } + }, + { + "id": "function:neuroforge/cmd/worker:postOllama", + "kind": "function", + "label": "postOllama", + "group": "engineering", + "community": "neuroforge/cmd/worker", + "meta": { + "exported": false, + "line": 394, + "path": "platform/neuroforge/cmd/worker/main.go" + } + }, + { + "id": "function:neuroforge/cmd/worker:register", + "kind": "function", + "label": "register", + "group": "engineering", + "community": "neuroforge/cmd/worker", + "meta": { + "exported": false, + "line": 235, "path": "platform/neuroforge/cmd/worker/main.go" } }, @@ -9798,10 +10098,46 @@ "community": "neuroforge/cmd/worker", "meta": { "exported": false, - "line": 99, + "line": 279, "path": "platform/neuroforge/cmd/worker/main.go" } }, + { + "id": "function:neuroforge/cmd/worker:waitOrDone", + "kind": "function", + "label": "waitOrDone", + "group": "engineering", + "community": "neuroforge/cmd/worker", + "meta": { + "exported": false, + "line": 178, + "path": "platform/neuroforge/cmd/worker/main.go" + } + }, + { + "id": "function:neuroforge/cmd/worker:workerBody", + "kind": "function", + "label": "workerBody", + "group": "engineering", + "community": "neuroforge/cmd/worker", + "meta": { + "exported": false, + "line": 227, + "path": "platform/neuroforge/cmd/worker/main.go" + } + }, + { + "id": "function:neuroforge/internal/brain:Engine.ApplyAndFinalizeJob", + "kind": "function", + "label": "Engine.ApplyAndFinalizeJob", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": true, + "line": 59, + "path": "platform/neuroforge/internal/brain/orchestrator.go" + } + }, { "id": "function:neuroforge/internal/brain:Engine.ApplyJobResult", "kind": "function", @@ -9810,10 +10146,22 @@ "community": "neuroforge/internal/brain", "meta": { "exported": true, - "line": 1008, + "line": 1081, "path": "platform/neuroforge/internal/brain/brain.go" } }, + { + "id": "function:neuroforge/internal/brain:Engine.ApplyPendingJobResults", + "kind": "function", + "label": "Engine.ApplyPendingJobResults", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": true, + "line": 76, + "path": "platform/neuroforge/internal/brain/orchestrator.go" + } + }, { "id": "function:neuroforge/internal/brain:Engine.Chat", "kind": "function", @@ -9822,7 +10170,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": true, - "line": 203, + "line": 239, "path": "platform/neuroforge/internal/brain/brain.go" } }, @@ -9834,7 +10182,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": true, - "line": 232, + "line": 487, "path": "platform/neuroforge/internal/brain/staging.go" } }, @@ -9918,7 +10266,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": true, - "line": 29, + "line": 48, "path": "platform/neuroforge/internal/brain/staging.go" } }, @@ -9930,7 +10278,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": true, - "line": 749, + "line": 785, "path": "platform/neuroforge/internal/brain/brain.go" } }, @@ -9942,7 +10290,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": true, - "line": 533, + "line": 569, "path": "platform/neuroforge/internal/brain/brain.go" } }, @@ -9954,7 +10302,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": true, - "line": 457, + "line": 493, "path": "platform/neuroforge/internal/brain/brain.go" } }, @@ -9990,10 +10338,22 @@ "community": "neuroforge/internal/brain", "meta": { "exported": true, - "line": 378, + "line": 414, "path": "platform/neuroforge/internal/brain/brain.go" } }, + { + "id": "function:neuroforge/internal/brain:Engine.PlanGraphBackfill", + "kind": "function", + "label": "Engine.PlanGraphBackfill", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": true, + "line": 14, + "path": "platform/neuroforge/internal/brain/orchestrator.go" + } + }, { "id": "function:neuroforge/internal/brain:Engine.RebalanceShards", "kind": "function", @@ -10014,7 +10374,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": true, - "line": 100, + "line": 159, "path": "platform/neuroforge/internal/brain/goal_progress.go" } }, @@ -10038,7 +10398,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": true, - "line": 298, + "line": 310, "path": "platform/neuroforge/internal/brain/v8.go" } }, @@ -10074,10 +10434,22 @@ "community": "neuroforge/internal/brain", "meta": { "exported": true, - "line": 935, + "line": 971, "path": "platform/neuroforge/internal/brain/brain.go" } }, + { + "id": "function:neuroforge/internal/brain:Engine.RunOrchestrator", + "kind": "function", + "label": "Engine.RunOrchestrator", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": true, + "line": 88, + "path": "platform/neuroforge/internal/brain/orchestrator.go" + } + }, { "id": "function:neuroforge/internal/brain:Engine.RunV3Maintenance", "kind": "function", @@ -10134,7 +10506,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": true, - "line": 510, + "line": 546, "path": "platform/neuroforge/internal/brain/brain.go" } }, @@ -10146,7 +10518,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": true, - "line": 1029, + "line": 1135, "path": "platform/neuroforge/internal/brain/brain.go" } }, @@ -10158,7 +10530,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": true, - "line": 523, + "line": 559, "path": "platform/neuroforge/internal/brain/brain.go" } }, @@ -10194,7 +10566,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 47, + "line": 51, "path": "platform/neuroforge/internal/brain/brain.go" } }, @@ -10206,7 +10578,19 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 127, + "line": 137, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, + { + "id": "function:neuroforge/internal/brain:Engine.chatModelJSONLimitOn", + "kind": "function", + "label": "Engine.chatModelJSONLimitOn", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 157, "path": "platform/neuroforge/internal/brain/brain.go" } }, @@ -10218,7 +10602,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 131, + "line": 141, "path": "platform/neuroforge/internal/brain/brain.go" } }, @@ -10230,7 +10614,19 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 143, + "line": 153, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, + { + "id": "function:neuroforge/internal/brain:Engine.chatModelLimitOnMode", + "kind": "function", + "label": "Engine.chatModelLimitOnMode", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 161, "path": "platform/neuroforge/internal/brain/brain.go" } }, @@ -10266,10 +10662,34 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 168, + "line": 363, "path": "platform/neuroforge/internal/brain/staging.go" } }, + { + "id": "function:neuroforge/internal/brain:Engine.distributedChat", + "kind": "function", + "label": "Engine.distributedChat", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 83, + "path": "platform/neuroforge/internal/brain/distributed.go" + } + }, + { + "id": "function:neuroforge/internal/brain:Engine.distributedEmbed", + "kind": "function", + "label": "Engine.distributedEmbed", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 54, + "path": "platform/neuroforge/internal/brain/distributed.go" + } + }, { "id": "function:neuroforge/internal/brain:Engine.duplicateMemory", "kind": "function", @@ -10314,7 +10734,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 82, + "line": 86, "path": "platform/neuroforge/internal/brain/brain.go" } }, @@ -10326,7 +10746,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 57, + "line": 61, "path": "platform/neuroforge/internal/brain/brain.go" } }, @@ -10338,10 +10758,22 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 987, + "line": 1040, "path": "platform/neuroforge/internal/brain/brain.go" } }, + { + "id": "function:neuroforge/internal/brain:Engine.ensureGoalDraftArticleDepth", + "kind": "function", + "label": "Engine.ensureGoalDraftArticleDepth", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 585, + "path": "platform/neuroforge/internal/brain/staging.go" + } + }, { "id": "function:neuroforge/internal/brain:Engine.evaluateReward", "kind": "function", @@ -10350,7 +10782,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 552, + "line": 588, "path": "platform/neuroforge/internal/brain/brain.go" } }, @@ -10374,7 +10806,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 456, + "line": 524, "path": "platform/neuroforge/internal/brain/v8.go" } }, @@ -10422,7 +10854,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 998, + "line": 1071, "path": "platform/neuroforge/internal/brain/brain.go" } }, @@ -10434,7 +10866,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 78, + "line": 157, "path": "platform/neuroforge/internal/brain/staging.go" } }, @@ -10482,7 +10914,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 351, + "line": 387, "path": "platform/neuroforge/internal/brain/brain.go" } }, @@ -10494,10 +10926,22 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 663, + "line": 699, "path": "platform/neuroforge/internal/brain/brain.go" } }, + { + "id": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", + "kind": "function", + "label": "Engine.repairDraftGrounding", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 662, + "path": "platform/neuroforge/internal/brain/staging_quality.go" + } + }, { "id": "function:neuroforge/internal/brain:Engine.replicateMemory", "kind": "function", @@ -10506,7 +10950,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 696, + "line": 732, "path": "platform/neuroforge/internal/brain/brain.go" } }, @@ -10530,7 +10974,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 544, + "line": 700, "path": "platform/neuroforge/internal/brain/v8.go" } }, @@ -10546,6 +10990,18 @@ "path": "platform/neuroforge/internal/brain/v5.go" } }, + { + "id": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", + "kind": "function", + "label": "Engine.reshapeGoalDraftArticle", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 549, + "path": "platform/neuroforge/internal/brain/staging.go" + } + }, { "id": "function:neuroforge/internal/brain:Engine.searchVectorFederated", "kind": "function", @@ -10554,7 +11010,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 594, + "line": 630, "path": "platform/neuroforge/internal/brain/brain.go" } }, @@ -10578,7 +11034,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 47, + "line": 116, "path": "platform/neuroforge/internal/brain/staging.go" } }, @@ -10590,7 +11046,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 871, + "line": 907, "path": "platform/neuroforge/internal/brain/brain.go" } }, @@ -10602,10 +11058,34 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 258, + "line": 597, "path": "platform/neuroforge/internal/brain/staging.go" } }, + { + "id": "function:neuroforge/internal/brain:Engine.verifyDraftClaims", + "kind": "function", + "label": "Engine.verifyDraftClaims", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 520, + "path": "platform/neuroforge/internal/brain/staging_quality.go" + } + }, + { + "id": "function:neuroforge/internal/brain:Engine.waitDistributedJob", + "kind": "function", + "label": "Engine.waitDistributedJob", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 27, + "path": "platform/neuroforge/internal/brain/distributed.go" + } + }, { "id": "function:neuroforge/internal/brain:New", "kind": "function", @@ -10614,7 +11094,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": true, - "line": 43, + "line": 47, "path": "platform/neuroforge/internal/brain/brain.go" } }, @@ -10626,7 +11106,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": true, - "line": 619, + "line": 775, "path": "platform/neuroforge/internal/brain/v8.go" } }, @@ -10638,10 +11118,22 @@ "community": "neuroforge/internal/brain", "meta": { "exported": true, - "line": 627, + "line": 783, "path": "platform/neuroforge/internal/brain/v8.go" } }, + { + "id": "function:neuroforge/internal/brain:allDigits", + "kind": "function", + "label": "allDigits", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 136, + "path": "platform/neuroforge/internal/brain/research_quality.go" + } + }, { "id": "function:neuroforge/internal/brain:appendUniqueTags", "kind": "function", @@ -10666,6 +11158,30 @@ "path": "platform/neuroforge/internal/brain/v3.go" } }, + { + "id": "function:neuroforge/internal/brain:articleQualityForDraft", + "kind": "function", + "label": "articleQualityForDraft", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 535, + "path": "platform/neuroforge/internal/brain/staging.go" + } + }, + { + "id": "function:neuroforge/internal/brain:articleRuneCount", + "kind": "function", + "label": "articleRuneCount", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 513, + "path": "platform/neuroforge/internal/brain/staging.go" + } + }, { "id": "function:neuroforge/internal/brain:buildContext", "kind": "function", @@ -10674,7 +11190,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 337, + "line": 373, "path": "platform/neuroforge/internal/brain/brain.go" } }, @@ -10702,6 +11218,54 @@ "path": "platform/neuroforge/internal/brain/v4.go" } }, + { + "id": "function:neuroforge/internal/brain:countDraftIndependentCorroborations", + "kind": "function", + "label": "countDraftIndependentCorroborations", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 697, + "path": "platform/neuroforge/internal/brain/staging_quality.go" + } + }, + { + "id": "function:neuroforge/internal/brain:criticalIdentifiers", + "kind": "function", + "label": "criticalIdentifiers", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 394, + "path": "platform/neuroforge/internal/brain/staging_quality.go" + } + }, + { + "id": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON", + "kind": "function", + "label": "decodeStagingSynthesisJSON", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 814, + "path": "platform/neuroforge/internal/brain/staging.go" + } + }, + { + "id": "function:neuroforge/internal/brain:decodeVerifierJSON", + "kind": "function", + "label": "decodeVerifierJSON", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 477, + "path": "platform/neuroforge/internal/brain/staging_quality.go" + } + }, { "id": "function:neuroforge/internal/brain:dedupeStrings", "kind": "function", @@ -10710,7 +11274,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 605, + "line": 761, "path": "platform/neuroforge/internal/brain/v8.go" } }, @@ -10722,7 +11286,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 449, + "line": 517, "path": "platform/neuroforge/internal/brain/v8.go" } }, @@ -10734,7 +11298,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 890, + "line": 926, "path": "platform/neuroforge/internal/brain/brain.go" } }, @@ -10746,7 +11310,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 299, + "line": 865, "path": "platform/neuroforge/internal/brain/staging.go" } }, @@ -10774,6 +11338,54 @@ "path": "platform/neuroforge/internal/brain/v3.go" } }, + { + "id": "function:neuroforge/internal/brain:deterministicResearchQueries", + "kind": "function", + "label": "deterministicResearchQueries", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 592, + "path": "platform/neuroforge/internal/brain/v8.go" + } + }, + { + "id": "function:neuroforge/internal/brain:deterministicStagingFailure", + "kind": "function", + "label": "deterministicStagingFailure", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 339, + "path": "platform/neuroforge/internal/brain/staging.go" + } + }, + { + "id": "function:neuroforge/internal/brain:domainMatches", + "kind": "function", + "label": "domainMatches", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 122, + "path": "platform/neuroforge/internal/brain/staging_quality.go" + } + }, + { + "id": "function:neuroforge/internal/brain:draftEvidenceAuthorityScore", + "kind": "function", + "label": "draftEvidenceAuthorityScore", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 261, + "path": "platform/neuroforge/internal/brain/staging_quality.go" + } + }, { "id": "function:neuroforge/internal/brain:due", "kind": "function", @@ -10810,6 +11422,30 @@ "path": "platform/neuroforge/internal/brain/v3.go" } }, + { + "id": "function:neuroforge/internal/brain:evidencePackForPrompt", + "kind": "function", + "label": "evidencePackForPrompt", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 318, + "path": "platform/neuroforge/internal/brain/staging_quality.go" + } + }, + { + "id": "function:neuroforge/internal/brain:extractDraftStatements", + "kind": "function", + "label": "extractDraftStatements", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 452, + "path": "platform/neuroforge/internal/brain/staging_quality.go" + } + }, { "id": "function:neuroforge/internal/brain:filterGoalEvidenceHits", "kind": "function", @@ -10818,7 +11454,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 86, + "line": 145, "path": "platform/neuroforge/internal/brain/goal_progress.go" } }, @@ -10830,10 +11466,46 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 440, + "line": 508, "path": "platform/neuroforge/internal/brain/v8.go" } }, + { + "id": "function:neuroforge/internal/brain:goalAnchorTokens", + "kind": "function", + "label": "goalAnchorTokens", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 16, + "path": "platform/neuroforge/internal/brain/research_quality.go" + } + }, + { + "id": "function:neuroforge/internal/brain:goalEvidenceRelevant", + "kind": "function", + "label": "goalEvidenceRelevant", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 174, + "path": "platform/neuroforge/internal/brain/research_quality.go" + } + }, + { + "id": "function:neuroforge/internal/brain:goalIDFromTags", + "kind": "function", + "label": "goalIDFromTags", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 148, + "path": "platform/neuroforge/internal/brain/research_quality.go" + } + }, { "id": "function:neuroforge/internal/brain:goalKeywords", "kind": "function", @@ -10842,10 +11514,22 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 317, + "line": 883, "path": "platform/neuroforge/internal/brain/staging.go" } }, + { + "id": "function:neuroforge/internal/brain:goalPreferredAuthorityDomains", + "kind": "function", + "label": "goalPreferredAuthorityDomains", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 722, + "path": "platform/neuroforge/internal/brain/staging_quality.go" + } + }, { "id": "function:neuroforge/internal/brain:hashText", "kind": "function", @@ -10858,6 +11542,30 @@ "path": "platform/neuroforge/internal/brain/v8.go" } }, + { + "id": "function:neuroforge/internal/brain:isActionableDraftStatement", + "kind": "function", + "label": "isActionableDraftStatement", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 442, + "path": "platform/neuroforge/internal/brain/staging_quality.go" + } + }, + { + "id": "function:neuroforge/internal/brain:isJSONHex4", + "kind": "function", + "label": "isJSONHex4", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 801, + "path": "platform/neuroforge/internal/brain/staging.go" + } + }, { "id": "function:neuroforge/internal/brain:maxIntV3", "kind": "function", @@ -10870,6 +11578,18 @@ "path": "platform/neuroforge/internal/brain/v3.go" } }, + { + "id": "function:neuroforge/internal/brain:memoryBelongsToGoal", + "kind": "function", + "label": "memoryBelongsToGoal", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 159, + "path": "platform/neuroforge/internal/brain/research_quality.go" + } + }, { "id": "function:neuroforge/internal/brain:memoryTypeForKind", "kind": "function", @@ -10878,7 +11598,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 440, + "line": 476, "path": "platform/neuroforge/internal/brain/brain.go" } }, @@ -10890,7 +11610,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 961, + "line": 997, "path": "platform/neuroforge/internal/brain/brain.go" } }, @@ -10906,6 +11626,42 @@ "path": "platform/neuroforge/internal/brain/v3.go" } }, + { + "id": "function:neuroforge/internal/brain:normalizeAuthoritativeDomain", + "kind": "function", + "label": "normalizeAuthoritativeDomain", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 101, + "path": "platform/neuroforge/internal/brain/staging_quality.go" + } + }, + { + "id": "function:neuroforge/internal/brain:normalizeStatementText", + "kind": "function", + "label": "normalizeStatementText", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 436, + "path": "platform/neuroforge/internal/brain/staging_quality.go" + } + }, + { + "id": "function:neuroforge/internal/brain:normalizedResearchWords", + "kind": "function", + "label": "normalizedResearchWords", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 126, + "path": "platform/neuroforge/internal/brain/research_quality.go" + } + }, { "id": "function:neuroforge/internal/brain:parsePrediction", "kind": "function", @@ -10954,6 +11710,18 @@ "path": "platform/neuroforge/internal/brain/policy.go" } }, + { + "id": "function:neuroforge/internal/brain:promptEvidenceTexts", + "kind": "function", + "label": "promptEvidenceTexts", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 285, + "path": "platform/neuroforge/internal/brain/staging_quality.go" + } + }, { "id": "function:neuroforge/internal/brain:rendezvousScore", "kind": "function", @@ -10978,6 +11746,30 @@ "path": "platform/neuroforge/internal/brain/v3.go" } }, + { + "id": "function:neuroforge/internal/brain:repairInvalidJSONStringEscapes", + "kind": "function", + "label": "repairInvalidJSONStringEscapes", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 754, + "path": "platform/neuroforge/internal/brain/staging.go" + } + }, + { + "id": "function:neuroforge/internal/brain:researchMaterialRelevant", + "kind": "function", + "label": "researchMaterialRelevant", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 64, + "path": "platform/neuroforge/internal/brain/research_quality.go" + } + }, { "id": "function:neuroforge/internal/brain:researchQueryUseful", "kind": "function", @@ -10986,7 +11778,19 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 519, + "line": 675, + "path": "platform/neuroforge/internal/brain/v8.go" + } + }, + { + "id": "function:neuroforge/internal/brain:researchResultsContainRelevant", + "kind": "function", + "label": "researchResultsContainRelevant", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 287, "path": "platform/neuroforge/internal/brain/v8.go" } }, @@ -11022,7 +11826,7 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 193, + "line": 229, "path": "platform/neuroforge/internal/brain/brain.go" } }, @@ -11050,6 +11854,30 @@ "path": "platform/neuroforge/internal/brain/research_trace.go" } }, + { + "id": "function:neuroforge/internal/brain:slashSwitchIdentifiers", + "kind": "function", + "label": "slashSwitchIdentifiers", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 370, + "path": "platform/neuroforge/internal/brain/staging_quality.go" + } + }, + { + "id": "function:neuroforge/internal/brain:sortDraftEvidenceByAuthority", + "kind": "function", + "label": "sortDraftEvidenceByAuthority", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 271, + "path": "platform/neuroforge/internal/brain/staging_quality.go" + } + }, { "id": "function:neuroforge/internal/brain:sortedGoalIDs", "kind": "function", @@ -11062,6 +11890,42 @@ "path": "platform/neuroforge/internal/brain/v3.go" } }, + { + "id": "function:neuroforge/internal/brain:sourceAuditForEvidence", + "kind": "function", + "label": "sourceAuditForEvidence", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 209, + "path": "platform/neuroforge/internal/brain/staging_quality.go" + } + }, + { + "id": "function:neuroforge/internal/brain:sourceAuthorityFor", + "kind": "function", + "label": "sourceAuthorityFor", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 155, + "path": "platform/neuroforge/internal/brain/staging_quality.go" + } + }, + { + "id": "function:neuroforge/internal/brain:sourceOriginKey", + "kind": "function", + "label": "sourceOriginKey", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 132, + "path": "platform/neuroforge/internal/brain/staging_quality.go" + } + }, { "id": "function:neuroforge/internal/brain:sourcePolicyKey", "kind": "function", @@ -11086,6 +11950,42 @@ "path": "platform/neuroforge/internal/brain/v8.go" } }, + { + "id": "function:neuroforge/internal/brain:stagingEvidenceSignature", + "kind": "function", + "label": "stagingEvidenceSignature", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 323, + "path": "platform/neuroforge/internal/brain/staging.go" + } + }, + { + "id": "function:neuroforge/internal/brain:strictUnmarshalJSONObject", + "kind": "function", + "label": "strictUnmarshalJSONObject", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 732, + "path": "platform/neuroforge/internal/brain/staging.go" + } + }, + { + "id": "function:neuroforge/internal/brain:summarizeEvidenceAuthority", + "kind": "function", + "label": "summarizeEvidenceAuthority", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 242, + "path": "platform/neuroforge/internal/brain/staging_quality.go" + } + }, { "id": "function:neuroforge/internal/brain:summarizeObservation", "kind": "function", @@ -11106,10 +12006,34 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 453, + "line": 489, "path": "platform/neuroforge/internal/brain/brain.go" } }, + { + "id": "function:neuroforge/internal/brain:validateDraftArticleBounds", + "kind": "function", + "label": "validateDraftArticleBounds", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 517, + "path": "platform/neuroforge/internal/brain/staging.go" + } + }, + { + "id": "function:neuroforge/internal/brain:validateDraftCriticalIdentifiers", + "kind": "function", + "label": "validateDraftCriticalIdentifiers", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 415, + "path": "platform/neuroforge/internal/brain/staging_quality.go" + } + }, { "id": "function:neuroforge/internal/brain:vectorCentroid", "kind": "function", @@ -11118,7 +12042,19 @@ "community": "neuroforge/internal/brain", "meta": { "exported": false, - "line": 910, + "line": 946, + "path": "platform/neuroforge/internal/brain/brain.go" + } + }, + { + "id": "function:neuroforge/internal/brain:vectorFingerprint", + "kind": "function", + "label": "vectorFingerprint", + "group": "engineering", + "community": "neuroforge/internal/brain", + "meta": { + "exported": false, + "line": 1027, "path": "platform/neuroforge/internal/brain/brain.go" } }, @@ -11130,7 +12066,7 @@ "community": "neuroforge/internal/core", "meta": { "exported": true, - "line": 675, + "line": 740, "path": "platform/neuroforge/internal/core/types.go" } }, @@ -11286,7 +12222,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 276, + "line": 286, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -11298,7 +12234,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 126, + "line": 131, "path": "platform/neuroforge/internal/httpapi/v3.go" } }, @@ -11310,7 +12246,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 145, + "line": 150, "path": "platform/neuroforge/internal/httpapi/v3.go" } }, @@ -11346,7 +12282,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 737, + "line": 827, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -11358,7 +12294,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 711, + "line": 801, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -11394,7 +12330,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 733, + "line": 823, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -11406,7 +12342,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 508, + "line": 598, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -11430,7 +12366,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 563, + "line": 653, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -11442,10 +12378,34 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 617, + "line": 707, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, + { + "id": "function:neuroforge/internal/httpapi:Server.adminGraphBackfill", + "kind": "function", + "label": "Server.adminGraphBackfill", + "group": "engineering", + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, + "line": 135, + "path": "platform/neuroforge/internal/httpapi/knowledge.go" + } + }, + { + "id": "function:neuroforge/internal/httpapi:Server.adminGraphStatus", + "kind": "function", + "label": "Server.adminGraphStatus", + "group": "engineering", + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, + "line": 119, + "path": "platform/neuroforge/internal/httpapi/knowledge.go" + } + }, { "id": "function:neuroforge/internal/httpapi:Server.adminKnowledgeEvents", "kind": "function", @@ -11526,7 +12486,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 700, + "line": 790, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -11542,6 +12502,54 @@ "path": "platform/neuroforge/internal/httpapi/v5.go" } }, + { + "id": "function:neuroforge/internal/httpapi:Server.adminOrchestratorCancel", + "kind": "function", + "label": "Server.adminOrchestratorCancel", + "group": "engineering", + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, + "line": 162, + "path": "platform/neuroforge/internal/httpapi/knowledge.go" + } + }, + { + "id": "function:neuroforge/internal/httpapi:Server.adminOrchestratorJobs", + "kind": "function", + "label": "Server.adminOrchestratorJobs", + "group": "engineering", + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, + "line": 148, + "path": "platform/neuroforge/internal/httpapi/knowledge.go" + } + }, + { + "id": "function:neuroforge/internal/httpapi:Server.adminOrchestratorRetry", + "kind": "function", + "label": "Server.adminOrchestratorRetry", + "group": "engineering", + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, + "line": 153, + "path": "platform/neuroforge/internal/httpapi/knowledge.go" + } + }, + { + "id": "function:neuroforge/internal/httpapi:Server.adminOrchestratorStatus", + "kind": "function", + "label": "Server.adminOrchestratorStatus", + "group": "engineering", + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, + "line": 144, + "path": "platform/neuroforge/internal/httpapi/knowledge.go" + } + }, { "id": "function:neuroforge/internal/httpapi:Server.adminProviderHealth", "kind": "function", @@ -11550,7 +12558,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 695, + "line": 785, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -11562,7 +12570,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 511, + "line": 601, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -11586,7 +12594,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 567, + "line": 657, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -11598,7 +12606,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 630, + "line": 720, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -11610,7 +12618,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 130, + "line": 135, "path": "platform/neuroforge/internal/httpapi/v3.go" } }, @@ -11658,7 +12666,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 157, + "line": 162, "path": "platform/neuroforge/internal/httpapi/v3.go" } }, @@ -11670,7 +12678,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 117, + "line": 122, "path": "platform/neuroforge/internal/httpapi/v3.go" } }, @@ -11682,7 +12690,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 604, + "line": 694, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -11694,7 +12702,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 471, + "line": 561, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -11718,7 +12726,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 723, + "line": 813, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -11742,7 +12750,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 726, + "line": 816, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -11754,7 +12762,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 153, + "line": 158, "path": "platform/neuroforge/internal/httpapi/v3.go" } }, @@ -11766,7 +12774,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 221, + "line": 231, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -11778,7 +12786,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 312, + "line": 322, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -11802,7 +12810,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 286, + "line": 296, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -11898,7 +12906,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 113, + "line": 118, "path": "platform/neuroforge/internal/httpapi/v3.go" } }, @@ -11910,7 +12918,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 253, + "line": 263, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -11922,7 +12930,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 308, + "line": 318, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -11934,7 +12942,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 405, + "line": 415, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -11946,7 +12954,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 92, + "line": 97, "path": "platform/neuroforge/internal/httpapi/v3.go" } }, @@ -11958,7 +12966,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 72, + "line": 77, "path": "platform/neuroforge/internal/httpapi/v3.go" } }, @@ -11994,7 +13002,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 82, + "line": 87, "path": "platform/neuroforge/internal/httpapi/v3.go" } }, @@ -12006,7 +13014,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 18, + "line": 19, "path": "platform/neuroforge/internal/httpapi/v3.go" } }, @@ -12018,7 +13026,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 57, + "line": 62, "path": "platform/neuroforge/internal/httpapi/v3.go" } }, @@ -12030,7 +13038,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 31, + "line": 36, "path": "platform/neuroforge/internal/httpapi/v3.go" } }, @@ -12042,7 +13050,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 14, + "line": 15, "path": "platform/neuroforge/internal/httpapi/v3.go" } }, @@ -12054,7 +13062,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 40, + "line": 45, "path": "platform/neuroforge/internal/httpapi/v3.go" } }, @@ -12066,7 +13074,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 391, + "line": 401, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -12078,7 +13086,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 150, + "line": 160, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -12114,7 +13122,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 240, + "line": 250, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -12142,6 +13150,18 @@ "path": "platform/neuroforge/internal/httpapi/integration.go" } }, + { + "id": "function:neuroforge/internal/httpapi:Server.integrationGraphStatus", + "kind": "function", + "label": "Server.integrationGraphStatus", + "group": "engineering", + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, + "line": 431, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, { "id": "function:neuroforge/internal/httpapi:Server.integrationKnowledgeDelete", "kind": "function", @@ -12178,6 +13198,18 @@ "path": "platform/neuroforge/internal/httpapi/integration.go" } }, + { + "id": "function:neuroforge/internal/httpapi:Server.integrationOrchestratorStatus", + "kind": "function", + "label": "Server.integrationOrchestratorStatus", + "group": "engineering", + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, + "line": 443, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, { "id": "function:neuroforge/internal/httpapi:Server.integrationResearchGraph", "kind": "function", @@ -12222,7 +13254,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 303, + "line": 313, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -12234,7 +13266,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 325, + "line": 335, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -12246,7 +13278,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 105, + "line": 110, "path": "platform/neuroforge/internal/httpapi/v3.go" } }, @@ -12258,7 +13290,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 794, + "line": 884, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -12270,7 +13302,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 191, + "line": 201, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -12294,7 +13326,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 859, + "line": 949, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -12306,7 +13338,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 769, + "line": 859, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -12342,7 +13374,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 338, + "line": 348, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -12354,7 +13386,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 354, + "line": 364, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -12366,7 +13398,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 747, + "line": 837, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -12402,7 +13434,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 417, + "line": 427, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -12414,7 +13446,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 267, + "line": 277, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -12426,7 +13458,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 421, + "line": 507, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -12438,7 +13470,31 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 448, + "line": 533, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, + { + "id": "function:neuroforge/internal/httpapi:Server.workerHeartbeat", + "kind": "function", + "label": "Server.workerHeartbeat", + "group": "engineering", + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, + "line": 489, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, + { + "id": "function:neuroforge/internal/httpapi:Server.workerRegister", + "kind": "function", + "label": "Server.workerRegister", + "group": "engineering", + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, + "line": 475, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -12462,7 +13518,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 214, + "line": 224, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -12486,7 +13542,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 812, + "line": 902, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -12498,7 +13554,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 798, + "line": 888, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -12522,7 +13578,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 297, + "line": 307, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -12618,7 +13674,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 608, + "line": 698, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -12690,7 +13746,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 550, + "line": 640, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -12750,7 +13806,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": false, - "line": 207, + "line": 217, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -12786,7 +13842,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": true, - "line": 172, + "line": 182, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -12798,7 +13854,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": true, - "line": 182, + "line": 192, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -12810,7 +13866,7 @@ "community": "neuroforge/internal/httpapi", "meta": { "exported": true, - "line": 174, + "line": 184, "path": "platform/neuroforge/internal/httpapi/httpapi.go" } }, @@ -12826,6 +13882,18 @@ "path": "platform/neuroforge/internal/httpapi/integration.go" } }, + { + "id": "function:neuroforge/internal/httpapi:workerHeartbeatFromRequest", + "kind": "function", + "label": "workerHeartbeatFromRequest", + "group": "engineering", + "community": "neuroforge/internal/httpapi", + "meta": { + "exported": false, + "line": 467, + "path": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, { "id": "function:neuroforge/internal/ingest:ChunkText", "kind": "function", @@ -12970,6 +14038,18 @@ "path": "platform/neuroforge/internal/provider/provider.go" } }, + { + "id": "function:neuroforge/internal/provider:Router.ChatJSONOn", + "kind": "function", + "label": "Router.ChatJSONOn", + "group": "engineering", + "community": "neuroforge/internal/provider", + "meta": { + "exported": true, + "line": 133, + "path": "platform/neuroforge/internal/provider/provider.go" + } + }, { "id": "function:neuroforge/internal/provider:Router.ChatOn", "kind": "function", @@ -12990,7 +14070,7 @@ "community": "neuroforge/internal/provider", "meta": { "exported": true, - "line": 173, + "line": 186, "path": "platform/neuroforge/internal/provider/provider.go" } }, @@ -13002,7 +14082,7 @@ "community": "neuroforge/internal/provider", "meta": { "exported": true, - "line": 187, + "line": 200, "path": "platform/neuroforge/internal/provider/provider.go" } }, @@ -13014,7 +14094,7 @@ "community": "neuroforge/internal/provider", "meta": { "exported": true, - "line": 433, + "line": 449, "path": "platform/neuroforge/internal/provider/provider.go" } }, @@ -13026,7 +14106,19 @@ "community": "neuroforge/internal/provider", "meta": { "exported": false, - "line": 258, + "line": 271, + "path": "platform/neuroforge/internal/provider/provider.go" + } + }, + { + "id": "function:neuroforge/internal/provider:Router.chatOn", + "kind": "function", + "label": "Router.chatOn", + "group": "engineering", + "community": "neuroforge/internal/provider", + "meta": { + "exported": false, + "line": 137, "path": "platform/neuroforge/internal/provider/provider.go" } }, @@ -13038,7 +14130,7 @@ "community": "neuroforge/internal/provider", "meta": { "exported": false, - "line": 323, + "line": 339, "path": "platform/neuroforge/internal/provider/provider.go" } }, @@ -13050,7 +14142,7 @@ "community": "neuroforge/internal/provider", "meta": { "exported": false, - "line": 402, + "line": 418, "path": "platform/neuroforge/internal/provider/provider.go" } }, @@ -13062,7 +14154,7 @@ "community": "neuroforge/internal/provider", "meta": { "exported": false, - "line": 303, + "line": 319, "path": "platform/neuroforge/internal/provider/provider.go" } }, @@ -13074,7 +14166,7 @@ "community": "neuroforge/internal/provider", "meta": { "exported": false, - "line": 373, + "line": 389, "path": "platform/neuroforge/internal/provider/provider.go" } }, @@ -13134,7 +14226,7 @@ "community": "neuroforge/internal/provider", "meta": { "exported": false, - "line": 243, + "line": 256, "path": "platform/neuroforge/internal/provider/provider.go" } }, @@ -13146,7 +14238,7 @@ "community": "neuroforge/internal/provider", "meta": { "exported": false, - "line": 236, + "line": 249, "path": "platform/neuroforge/internal/provider/provider.go" } }, @@ -13470,7 +14562,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 50, + "line": 52, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -13482,7 +14574,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 576, + "line": 650, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -13758,7 +14850,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 100, + "line": 101, "path": "platform/neuroforge/internal/store/knowledge.go" } }, @@ -13770,7 +14862,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 292, + "line": 315, "path": "platform/neuroforge/internal/store/v3.go" } }, @@ -13794,7 +14886,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 789, + "line": 863, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -13818,7 +14910,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 1306, + "line": 1312, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -13834,6 +14926,18 @@ "path": "platform/neuroforge/internal/store/raftstate.go" } }, + { + "id": "function:neuroforge/internal/store:Store.CancelJob", + "kind": "function", + "label": "Store.CancelJob", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": true, + "line": 735, + "path": "platform/neuroforge/internal/store/orchestrator.go" + } + }, { "id": "function:neuroforge/internal/store:Store.ClaimJob", "kind": "function", @@ -13842,8 +14946,20 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 1364, - "path": "platform/neuroforge/internal/store/store.go" + "line": 389, + "path": "platform/neuroforge/internal/store/orchestrator.go" + } + }, + { + "id": "function:neuroforge/internal/store:Store.ClaimJobForWorker", + "kind": "function", + "label": "Store.ClaimJobForWorker", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": true, + "line": 393, + "path": "platform/neuroforge/internal/store/orchestrator.go" } }, { @@ -13854,7 +14970,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 578, + "line": 652, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -13938,7 +15054,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 594, + "line": 668, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -13950,8 +15066,20 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 1389, - "path": "platform/neuroforge/internal/store/store.go" + "line": 436, + "path": "platform/neuroforge/internal/store/orchestrator.go" + } + }, + { + "id": "function:neuroforge/internal/store:Store.CompleteJobLease", + "kind": "function", + "label": "Store.CompleteJobLease", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": true, + "line": 440, + "path": "platform/neuroforge/internal/store/orchestrator.go" } }, { @@ -13962,7 +15090,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 615, + "line": 689, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -13974,7 +15102,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 136, + "line": 138, "path": "platform/neuroforge/internal/store/v3.go" } }, @@ -13986,7 +15114,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 1151, + "line": 1157, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -13998,7 +15126,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 1104, + "line": 1109, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -14010,7 +15138,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 282, + "line": 305, "path": "platform/neuroforge/internal/store/v3.go" } }, @@ -14034,7 +15162,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 1411, + "line": 1357, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -14082,8 +15210,20 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 1351, - "path": "platform/neuroforge/internal/store/store.go" + "line": 91, + "path": "platform/neuroforge/internal/store/orchestrator.go" + } + }, + { + "id": "function:neuroforge/internal/store:Store.EnqueueJobSpec", + "kind": "function", + "label": "Store.EnqueueJobSpec", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": true, + "line": 95, + "path": "platform/neuroforge/internal/store/orchestrator.go" } }, { @@ -14094,10 +15234,22 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 1431, + "line": 1378, "path": "platform/neuroforge/internal/store/store.go" } }, + { + "id": "function:neuroforge/internal/store:Store.FinishMasterApply", + "kind": "function", + "label": "Store.FinishMasterApply", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": true, + "line": 686, + "path": "platform/neuroforge/internal/store/orchestrator.go" + } + }, { "id": "function:neuroforge/internal/store:Store.FinishResearchRun", "kind": "function", @@ -14118,7 +15270,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 387, + "line": 397, "path": "platform/neuroforge/internal/store/wal.go" } }, @@ -14130,7 +15282,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 209, + "line": 232, "path": "platform/neuroforge/internal/store/v3.go" } }, @@ -14142,7 +15294,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 855, + "line": 929, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -14178,7 +15330,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 220, + "line": 243, "path": "platform/neuroforge/internal/store/v3.go" } }, @@ -14194,6 +15346,66 @@ "path": "platform/neuroforge/internal/store/raftstate.go" } }, + { + "id": "function:neuroforge/internal/store:Store.GraphBackfillCandidates", + "kind": "function", + "label": "Store.GraphBackfillCandidates", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": true, + "line": 285, + "path": "platform/neuroforge/internal/store/graph_index.go" + } + }, + { + "id": "function:neuroforge/internal/store:Store.GraphDegree", + "kind": "function", + "label": "Store.GraphDegree", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": true, + "line": 117, + "path": "platform/neuroforge/internal/store/graph_index.go" + } + }, + { + "id": "function:neuroforge/internal/store:Store.GraphStats", + "kind": "function", + "label": "Store.GraphStats", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": true, + "line": 123, + "path": "platform/neuroforge/internal/store/graph_index.go" + } + }, + { + "id": "function:neuroforge/internal/store:Store.HasLiveWorker", + "kind": "function", + "label": "Store.HasLiveWorker", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": true, + "line": 585, + "path": "platform/neuroforge/internal/store/orchestrator.go" + } + }, + { + "id": "function:neuroforge/internal/store:Store.HeartbeatWorker", + "kind": "function", + "label": "Store.HeartbeatWorker", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": true, + "line": 294, + "path": "platform/neuroforge/internal/store/orchestrator.go" + } + }, { "id": "function:neuroforge/internal/store:Store.IndexSnapshotStatus", "kind": "function", @@ -14218,6 +15430,42 @@ "path": "platform/neuroforge/internal/store/index_segments.go" } }, + { + "id": "function:neuroforge/internal/store:Store.IsOrchestratorLeader", + "kind": "function", + "label": "Store.IsOrchestratorLeader", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": true, + "line": 829, + "path": "platform/neuroforge/internal/store/orchestrator.go" + } + }, + { + "id": "function:neuroforge/internal/store:Store.Job", + "kind": "function", + "label": "Store.Job", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": true, + "line": 499, + "path": "platform/neuroforge/internal/store/orchestrator.go" + } + }, + { + "id": "function:neuroforge/internal/store:Store.JobsSnapshot", + "kind": "function", + "label": "Store.JobsSnapshot", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": true, + "line": 512, + "path": "platform/neuroforge/internal/store/orchestrator.go" + } + }, { "id": "function:neuroforge/internal/store:Store.KnowledgeGraph", "kind": "function", @@ -14226,7 +15474,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 274, + "line": 275, "path": "platform/neuroforge/internal/store/knowledge.go" } }, @@ -14238,7 +15486,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 212, + "line": 213, "path": "platform/neuroforge/internal/store/knowledge.go" } }, @@ -14250,7 +15498,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 382, + "line": 383, "path": "platform/neuroforge/internal/store/knowledge.go" } }, @@ -14262,7 +15510,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 136, + "line": 137, "path": "platform/neuroforge/internal/store/knowledge.go" } }, @@ -14286,7 +15534,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 1246, + "line": 1252, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -14298,10 +15546,22 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 1204, + "line": 1210, "path": "platform/neuroforge/internal/store/store.go" } }, + { + "id": "function:neuroforge/internal/store:Store.MarkGraphLinked", + "kind": "function", + "label": "Store.MarkGraphLinked", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": true, + "line": 350, + "path": "platform/neuroforge/internal/store/graph_index.go" + } + }, { "id": "function:neuroforge/internal/store:Store.MemoriesSnapshot", "kind": "function", @@ -14310,7 +15570,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 1223, + "line": 1229, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -14350,6 +15610,18 @@ "path": "platform/neuroforge/internal/store/observability.go" } }, + { + "id": "function:neuroforge/internal/store:Store.OrchestratorStatus", + "kind": "function", + "label": "Store.OrchestratorStatus", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": true, + "line": 598, + "path": "platform/neuroforge/internal/store/orchestrator.go" + } + }, { "id": "function:neuroforge/internal/store:Store.PauseGoal", "kind": "function", @@ -14358,7 +15630,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 236, + "line": 259, "path": "platform/neuroforge/internal/store/v3.go" } }, @@ -14374,6 +15646,30 @@ "path": "platform/neuroforge/internal/store/cluster.go" } }, + { + "id": "function:neuroforge/internal/store:Store.PendingJobCount", + "kind": "function", + "label": "Store.PendingJobCount", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": true, + "line": 636, + "path": "platform/neuroforge/internal/store/orchestrator.go" + } + }, + { + "id": "function:neuroforge/internal/store:Store.PendingMasterApplyJobs", + "kind": "function", + "label": "Store.PendingMasterApplyJobs", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": true, + "line": 654, + "path": "platform/neuroforge/internal/store/orchestrator.go" + } + }, { "id": "function:neuroforge/internal/store:Store.PrepareClusterEntry", "kind": "function", @@ -14386,6 +15682,18 @@ "path": "platform/neuroforge/internal/store/cluster.go" } }, + { + "id": "function:neuroforge/internal/store:Store.PruneTerminalJobs", + "kind": "function", + "label": "Store.PruneTerminalJobs", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": true, + "line": 759, + "path": "platform/neuroforge/internal/store/orchestrator.go" + } + }, { "id": "function:neuroforge/internal/store:Store.RebuildDiskANN", "kind": "function", @@ -14406,7 +15714,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 116, + "line": 117, "path": "platform/neuroforge/internal/store/knowledge.go" } }, @@ -14418,7 +15726,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 308, + "line": 331, "path": "platform/neuroforge/internal/store/v3.go" } }, @@ -14430,7 +15738,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 1338, + "line": 1344, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -14446,6 +15754,18 @@ "path": "platform/neuroforge/internal/store/cluster.go" } }, + { + "id": "function:neuroforge/internal/store:Store.RegisterWorker", + "kind": "function", + "label": "Store.RegisterWorker", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": true, + "line": 284, + "path": "platform/neuroforge/internal/store/orchestrator.go" + } + }, { "id": "function:neuroforge/internal/store:Store.Reinforce", "kind": "function", @@ -14454,10 +15774,22 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 1068, + "line": 1098, "path": "platform/neuroforge/internal/store/store.go" } }, + { + "id": "function:neuroforge/internal/store:Store.ReinforceRelation", + "kind": "function", + "label": "Store.ReinforceRelation", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": true, + "line": 83, + "path": "platform/neuroforge/internal/store/graph_index.go" + } + }, { "id": "function:neuroforge/internal/store:Store.ResearchRunsSnapshot", "kind": "function", @@ -14478,7 +15810,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 99, + "line": 101, "path": "platform/neuroforge/internal/store/v3.go" } }, @@ -14490,10 +15822,22 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 256, + "line": 279, "path": "platform/neuroforge/internal/store/v3.go" } }, + { + "id": "function:neuroforge/internal/store:Store.RetryJob", + "kind": "function", + "label": "Store.RetryJob", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": true, + "line": 538, + "path": "platform/neuroforge/internal/store/orchestrator.go" + } + }, { "id": "function:neuroforge/internal/store:Store.RunRetention", "kind": "function", @@ -14502,7 +15846,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 348, + "line": 371, "path": "platform/neuroforge/internal/store/v3.go" } }, @@ -14526,7 +15870,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 912, + "line": 986, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -14538,7 +15882,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 1747, + "line": 1709, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -14550,7 +15894,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 1756, + "line": 1718, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -14562,7 +15906,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 672, + "line": 746, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -14574,7 +15918,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 606, + "line": 680, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -14586,7 +15930,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 151, + "line": 153, "path": "platform/neuroforge/internal/store/v3.go" } }, @@ -14598,7 +15942,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 1179, + "line": 1185, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -14610,7 +15954,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 1190, + "line": 1196, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -14658,7 +16002,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 1259, + "line": 1265, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -14694,7 +16038,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 1236, + "line": 1242, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -14730,7 +16074,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 1127, + "line": 1133, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -14754,7 +16098,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 616, + "line": 690, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -14766,7 +16110,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 1252, + "line": 1258, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -14778,7 +16122,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 679, + "line": 753, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -14802,10 +16146,22 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 168, + "line": 170, "path": "platform/neuroforge/internal/store/v3.go" } }, + { + "id": "function:neuroforge/internal/store:Store.UpsertRelationEvidence", + "kind": "function", + "label": "Store.UpsertRelationEvidence", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": true, + "line": 367, + "path": "platform/neuroforge/internal/store/graph_index.go" + } + }, { "id": "function:neuroforge/internal/store:Store.UpsertSource", "kind": "function", @@ -14826,7 +16182,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 1322, + "line": 1328, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -14838,7 +16194,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 1449, + "line": 1396, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -14862,10 +16218,22 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 393, + "line": 403, "path": "platform/neuroforge/internal/store/wal.go" } }, + { + "id": "function:neuroforge/internal/store:Store.WorkersSnapshot", + "kind": "function", + "label": "Store.WorkersSnapshot", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": true, + "line": 559, + "path": "platform/neuroforge/internal/store/orchestrator.go" + } + }, { "id": "function:neuroforge/internal/store:Store.appendClusterLogDecision", "kind": "function", @@ -14934,7 +16302,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 313, + "line": 323, "path": "platform/neuroforge/internal/store/wal.go" } }, @@ -14998,6 +16366,18 @@ "path": "platform/neuroforge/internal/store/cluster.go" } }, + { + "id": "function:neuroforge/internal/store:Store.dependencyStateLocked", + "kind": "function", + "label": "Store.dependencyStateLocked", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": false, + "line": 214, + "path": "platform/neuroforge/internal/store/orchestrator.go" + } + }, { "id": "function:neuroforge/internal/store:Store.ensureClusterLog", "kind": "function", @@ -15030,10 +16410,22 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 866, + "line": 940, "path": "platform/neuroforge/internal/store/store.go" } }, + { + "id": "function:neuroforge/internal/store:Store.graphExpandLocked", + "kind": "function", + "label": "Store.graphExpandLocked", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": false, + "line": 175, + "path": "platform/neuroforge/internal/store/graph_index.go" + } + }, { "id": "function:neuroforge/internal/store:Store.indexCountMatchesLocked", "kind": "function", @@ -15058,6 +16450,18 @@ "path": "platform/neuroforge/internal/store/source_index.go" } }, + { + "id": "function:neuroforge/internal/store:Store.indexSynapseLocked", + "kind": "function", + "label": "Store.indexSynapseLocked", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": false, + "line": 37, + "path": "platform/neuroforge/internal/store/graph_index.go" + } + }, { "id": "function:neuroforge/internal/store:Store.initHotTrackerLocked", "kind": "function", @@ -15102,7 +16506,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 373, + "line": 383, "path": "platform/neuroforge/internal/store/wal.go" } }, @@ -15114,7 +16518,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 541, + "line": 615, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -15162,10 +16566,22 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 697, + "line": 771, "path": "platform/neuroforge/internal/store/store.go" } }, + { + "id": "function:neuroforge/internal/store:Store.normalizeJobForClaimLocked", + "kind": "function", + "label": "Store.normalizeJobForClaimLocked", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": false, + "line": 325, + "path": "platform/neuroforge/internal/store/orchestrator.go" + } + }, { "id": "function:neuroforge/internal/store:Store.oldestHotLocked", "kind": "function", @@ -15198,7 +16614,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 561, + "line": 635, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -15210,7 +16626,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 564, + "line": 638, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -15222,7 +16638,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 344, + "line": 354, "path": "platform/neuroforge/internal/store/wal.go" } }, @@ -15234,7 +16650,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 702, + "line": 776, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -15246,7 +16662,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 739, + "line": 813, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -15262,6 +16678,30 @@ "path": "platform/neuroforge/internal/store/source_index.go" } }, + { + "id": "function:neuroforge/internal/store:Store.rebuildSynapseAdjLocked", + "kind": "function", + "label": "Store.rebuildSynapseAdjLocked", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": false, + "line": 27, + "path": "platform/neuroforge/internal/store/graph_index.go" + } + }, + { + "id": "function:neuroforge/internal/store:Store.registerWorkerLocked", + "kind": "function", + "label": "Store.registerWorkerLocked", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": false, + "line": 244, + "path": "platform/neuroforge/internal/store/orchestrator.go" + } + }, { "id": "function:neuroforge/internal/store:Store.replayWAL", "kind": "function", @@ -15294,7 +16734,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 15, + "line": 17, "path": "platform/neuroforge/internal/store/v3.go" } }, @@ -15306,7 +16746,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 918, + "line": 992, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -15358,6 +16798,18 @@ "path": "platform/neuroforge/internal/store/source_index.go" } }, + { + "id": "function:neuroforge/internal/store:Store.unindexSynapseLocked", + "kind": "function", + "label": "Store.unindexSynapseLocked", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": false, + "line": 54, + "path": "platform/neuroforge/internal/store/graph_index.go" + } + }, { "id": "function:neuroforge/internal/store:Store.untrackHotMemoryLocked", "kind": "function", @@ -15378,7 +16830,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 1455, + "line": 1402, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -15426,7 +16878,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 366, + "line": 376, "path": "platform/neuroforge/internal/store/wal.go" } }, @@ -15586,6 +17038,18 @@ "path": "platform/neuroforge/internal/store/sqar_vector.go" } }, + { + "id": "function:neuroforge/internal/store:appendUniqueRelation", + "kind": "function", + "label": "appendUniqueRelation", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": false, + "line": 72, + "path": "platform/neuroforge/internal/store/graph_index.go" + } + }, { "id": "function:neuroforge/internal/store:appendUniqueString", "kind": "function", @@ -15594,7 +17058,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 90, + "line": 92, "path": "platform/neuroforge/internal/store/v3.go" } }, @@ -15618,7 +17082,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 214, + "line": 217, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -15658,6 +17122,18 @@ "path": "platform/neuroforge/internal/store/vector_journal.go" } }, + { + "id": "function:neuroforge/internal/store:capabilitiesContain", + "kind": "function", + "label": "capabilitiesContain", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": false, + "line": 75, + "path": "platform/neuroforge/internal/store/orchestrator.go" + } + }, { "id": "function:neuroforge/internal/store:cleanupOldIndexBases", "kind": "function", @@ -15678,7 +17154,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 162, + "line": 164, "path": "platform/neuroforge/internal/store/v3.go" } }, @@ -15690,7 +17166,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 891, + "line": 965, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -15726,7 +17202,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 689, + "line": 763, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -15786,7 +17262,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 1061, + "line": 1091, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -15802,6 +17278,18 @@ "path": "platform/neuroforge/internal/store/sqar_vector.go" } }, + { + "id": "function:neuroforge/internal/store:goalFingerprint", + "kind": "function", + "label": "goalFingerprint", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": false, + "line": 222, + "path": "platform/neuroforge/internal/store/v3.go" + } + }, { "id": "function:neuroforge/internal/store:hashSnapshotNode", "kind": "function", @@ -15894,7 +17382,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 528, + "line": 602, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -15918,7 +17406,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 78, + "line": 80, "path": "platform/neuroforge/internal/store/v3.go" } }, @@ -15958,6 +17446,18 @@ "path": "platform/neuroforge/internal/store/mmap_linux.go" } }, + { + "id": "function:neuroforge/internal/store:maxInt", + "kind": "function", + "label": "maxInt", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": false, + "line": 380, + "path": "platform/neuroforge/internal/store/orchestrator.go" + } + }, { "id": "function:neuroforge/internal/store:maxIntStore", "kind": "function", @@ -16002,7 +17502,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 92, + "line": 93, "path": "platform/neuroforge/internal/store/knowledge.go" } }, @@ -16014,7 +17514,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 383, + "line": 393, "path": "platform/neuroforge/internal/store/wal.go" } }, @@ -16026,7 +17526,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 333, + "line": 356, "path": "platform/neuroforge/internal/store/v3.go" } }, @@ -16038,7 +17538,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 489, + "line": 563, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -16066,6 +17566,18 @@ "path": "platform/neuroforge/internal/store/pagecache.go" } }, + { + "id": "function:neuroforge/internal/store:normalizeCapabilities", + "kind": "function", + "label": "normalizeCapabilities", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": false, + "line": 60, + "path": "platform/neuroforge/internal/store/orchestrator.go" + } + }, { "id": "function:neuroforge/internal/store:normalizeVectorJournalOptions", "kind": "function", @@ -16158,7 +17670,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 1097, + "line": 1102, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -16182,7 +17694,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 206, + "line": 207, "path": "platform/neuroforge/internal/store/knowledge.go" } }, @@ -16194,7 +17706,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 207, + "line": 208, "path": "platform/neuroforge/internal/store/knowledge.go" } }, @@ -16206,7 +17718,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 210, + "line": 211, "path": "platform/neuroforge/internal/store/knowledge.go" } }, @@ -16218,7 +17730,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 209, + "line": 210, "path": "platform/neuroforge/internal/store/knowledge.go" } }, @@ -16230,7 +17742,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": true, - "line": 208, + "line": 209, "path": "platform/neuroforge/internal/store/knowledge.go" } }, @@ -16242,7 +17754,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 568, + "line": 642, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -16366,6 +17878,18 @@ "path": "platform/neuroforge/internal/store/sqar_vector.go" } }, + { + "id": "function:neuroforge/internal/store:workerMatchesJob", + "kind": "function", + "label": "workerMatchesJob", + "group": "engineering", + "community": "neuroforge/internal/store", + "meta": { + "exported": false, + "line": 235, + "path": "platform/neuroforge/internal/store/orchestrator.go" + } + }, { "id": "function:neuroforge/internal/store:writeAtomic", "kind": "function", @@ -16374,7 +17898,7 @@ "community": "neuroforge/internal/store", "meta": { "exported": false, - "line": 549, + "line": 623, "path": "platform/neuroforge/internal/store/store.go" } }, @@ -17942,6 +19466,16 @@ "file": "platform/neuroforge/internal/httpapi/httpapi.go" } }, + { + "id": "route:GET /admin/api/graph/status", + "kind": "route", + "label": "GET /admin/api/graph/status", + "group": "engineering", + "community": "platform/neuroforge", + "meta": { + "file": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, { "id": "route:GET /admin/api/index/disk", "kind": "route", @@ -18032,6 +19566,26 @@ "file": "platform/neuroforge/internal/httpapi/httpapi.go" } }, + { + "id": "route:GET /admin/api/orchestrator/jobs", + "kind": "route", + "label": "GET /admin/api/orchestrator/jobs", + "group": "engineering", + "community": "platform/neuroforge", + "meta": { + "file": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, + { + "id": "route:GET /admin/api/orchestrator/status", + "kind": "route", + "label": "GET /admin/api/orchestrator/status", + "group": "engineering", + "community": "platform/neuroforge", + "meta": { + "file": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, { "id": "route:GET /admin/api/research", "kind": "route", @@ -18392,6 +19946,26 @@ "file": "services/agent/internal/web/server.go" } }, + { + "id": "route:GET /api/neuroforge/graph-status", + "kind": "route", + "label": "GET /api/neuroforge/graph-status", + "group": "engineering", + "community": "services/control", + "meta": { + "file": "services/control/main.go" + } + }, + { + "id": "route:GET /api/neuroforge/orchestrator", + "kind": "route", + "label": "GET /api/neuroforge/orchestrator", + "group": "engineering", + "community": "services/control", + "meta": { + "file": "services/control/main.go" + } + }, { "id": "route:GET /api/outcomes", "kind": "route", @@ -18522,6 +20096,26 @@ "file": "platform/neuroforge/internal/httpapi/httpapi.go" } }, + { + "id": "route:GET /api/v1/integrations/graph/status", + "kind": "route", + "label": "GET /api/v1/integrations/graph/status", + "group": "engineering", + "community": "platform/neuroforge", + "meta": { + "file": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, + { + "id": "route:GET /api/v1/integrations/orchestrator/status", + "kind": "route", + "label": "GET /api/v1/integrations/orchestrator/status", + "group": "engineering", + "community": "platform/neuroforge", + "meta": { + "file": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, { "id": "route:GET /api/v1/learning-cycles", "kind": "route", @@ -18702,6 +20296,16 @@ "file": "platform/neuroforge/internal/httpapi/httpapi.go" } }, + { + "id": "route:POST /admin/api/graph/backfill", + "kind": "route", + "label": "POST /admin/api/graph/backfill", + "group": "engineering", + "community": "platform/neuroforge", + "meta": { + "file": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, { "id": "route:POST /admin/api/index/disk/rebuild", "kind": "route", @@ -18732,6 +20336,26 @@ "file": "platform/neuroforge/internal/httpapi/httpapi.go" } }, + { + "id": "route:POST /admin/api/orchestrator/jobs/{id}/cancel", + "kind": "route", + "label": "POST /admin/api/orchestrator/jobs/{id}/cancel", + "group": "engineering", + "community": "platform/neuroforge", + "meta": { + "file": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, + { + "id": "route:POST /admin/api/orchestrator/jobs/{id}/retry", + "kind": "route", + "label": "POST /admin/api/orchestrator/jobs/{id}/retry", + "group": "engineering", + "community": "platform/neuroforge", + "meta": { + "file": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, { "id": "route:POST /admin/api/provider-health", "kind": "route", @@ -19102,6 +20726,26 @@ "file": "platform/neuroforge/internal/httpapi/httpapi.go" } }, + { + "id": "route:POST /api/v1/worker/heartbeat", + "kind": "route", + "label": "POST /api/v1/worker/heartbeat", + "group": "engineering", + "community": "platform/neuroforge", + "meta": { + "file": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, + { + "id": "route:POST /api/v1/worker/register", + "kind": "route", + "label": "POST /api/v1/worker/register", + "group": "engineering", + "community": "platform/neuroforge", + "meta": { + "file": "platform/neuroforge/internal/httpapi/httpapi.go" + } + }, { "id": "route:POST /internal/v1/cluster/abort", "kind": "route", @@ -19280,7 +20924,7 @@ "community": "compose", "status": "configured", "meta": { - "image": "git.send.nrw/sendnrw/glpi-neuroforge-mega-agent:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.5.0}" + "image": "git.send.nrw/sendnrw/glpi-neuroforge-mega-agent:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.6.0}" } }, { @@ -19291,7 +20935,7 @@ "community": "compose", "status": "configured", "meta": { - "image": "git.send.nrw/sendnrw/glpi-neuroforge-mega-agent-data-init:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.5.0}" + "image": "git.send.nrw/sendnrw/glpi-neuroforge-mega-agent-data-init:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.6.0}" } }, { @@ -19302,7 +20946,7 @@ "community": "compose", "status": "configured", "meta": { - "image": "git.send.nrw/sendnrw/glpi-neuroforge-mega-control:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.5.0}" + "image": "git.send.nrw/sendnrw/glpi-neuroforge-mega-control:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.6.0}" } }, { @@ -19313,7 +20957,7 @@ "community": "compose", "status": "configured", "meta": { - "image": "git.send.nrw/sendnrw/glpi-neuroforge-mega-knowledge:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.5.0}" + "image": "git.send.nrw/sendnrw/glpi-neuroforge-mega-knowledge:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.6.0}" } }, { @@ -19324,18 +20968,29 @@ "community": "compose", "status": "configured", "meta": { - "image": "git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.5.0}" + "image": "git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.6.0}" } }, { - "id": "service:neuroforge-worker", + "id": "service:neuroforge-worker-cpu", "kind": "service", - "label": "neuroforge-worker", + "label": "neuroforge-worker-cpu", "group": "runtime", "community": "compose", "status": "configured", "meta": { - "image": "git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge-worker:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.5.0}" + "image": "git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge-worker:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.6.0}" + } + }, + { + "id": "service:neuroforge-worker-gpu", + "kind": "service", + "label": "neuroforge-worker-gpu", + "group": "runtime", + "community": "compose", + "status": "configured", + "meta": { + "image": "git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge-worker:${IMAGE_TAG:?Set IMAGE_TAG to an immutable release tag, for example 1.6.0}" } }, { @@ -19680,12 +21335,24 @@ "to": "function:neuroforge/cmd/server:envBool", "kind": "defines" }, + { + "id": "file:platform/neuroforge/cmd/server/main.go-\u003efunction:neuroforge/cmd/server:envFloat:defines", + "from": "file:platform/neuroforge/cmd/server/main.go", + "to": "function:neuroforge/cmd/server:envFloat", + "kind": "defines" + }, { "id": "file:platform/neuroforge/cmd/server/main.go-\u003efunction:neuroforge/cmd/server:envInt:defines", "from": "file:platform/neuroforge/cmd/server/main.go", "to": "function:neuroforge/cmd/server:envInt", "kind": "defines" }, + { + "id": "file:platform/neuroforge/cmd/server/main.go-\u003efunction:neuroforge/cmd/server:firstNonEmptyMain:defines", + "from": "file:platform/neuroforge/cmd/server/main.go", + "to": "function:neuroforge/cmd/server:firstNonEmptyMain", + "kind": "defines" + }, { "id": "file:platform/neuroforge/cmd/server/main.go-\u003efunction:neuroforge/cmd/server:main:defines", "from": "file:platform/neuroforge/cmd/server/main.go", @@ -19842,30 +21509,144 @@ "to": "function:neuroforge/cmd/worker:complete", "kind": "defines" }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:csv:defines", + "from": "file:platform/neuroforge/cmd/worker/main.go", + "to": "function:neuroforge/cmd/worker:csv", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:envDuration:defines", + "from": "file:platform/neuroforge/cmd/worker/main.go", + "to": "function:neuroforge/cmd/worker:envDuration", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:envInt:defines", + "from": "file:platform/neuroforge/cmd/worker/main.go", + "to": "function:neuroforge/cmd/worker:envInt", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:firstNonEmpty:defines", + "from": "file:platform/neuroforge/cmd/worker/main.go", + "to": "function:neuroforge/cmd/worker:firstNonEmpty", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:hasCap:defines", + "from": "file:platform/neuroforge/cmd/worker/main.go", + "to": "function:neuroforge/cmd/worker:hasCap", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:heartbeatLoop:defines", + "from": "file:platform/neuroforge/cmd/worker/main.go", + "to": "function:neuroforge/cmd/worker:heartbeatLoop", + "kind": "defines" + }, { "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:hostname:defines", "from": "file:platform/neuroforge/cmd/worker/main.go", "to": "function:neuroforge/cmd/worker:hostname", "kind": "defines" }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:leaseSet.add:defines", + "from": "file:platform/neuroforge/cmd/worker/main.go", + "to": "function:neuroforge/cmd/worker:leaseSet.add", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:leaseSet.del:defines", + "from": "file:platform/neuroforge/cmd/worker/main.go", + "to": "function:neuroforge/cmd/worker:leaseSet.del", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:leaseSet.snapshot:defines", + "from": "file:platform/neuroforge/cmd/worker/main.go", + "to": "function:neuroforge/cmd/worker:leaseSet.snapshot", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:lowerDefault:defines", + "from": "file:platform/neuroforge/cmd/worker/main.go", + "to": "function:neuroforge/cmd/worker:lowerDefault", + "kind": "defines" + }, { "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:main:defines", "from": "file:platform/neuroforge/cmd/worker/main.go", "to": "function:neuroforge/cmd/worker:main", "kind": "defines" }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:ollamaChat:defines", + "from": "file:platform/neuroforge/cmd/worker/main.go", + "to": "function:neuroforge/cmd/worker:ollamaChat", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:ollamaEmbed:defines", + "from": "file:platform/neuroforge/cmd/worker/main.go", + "to": "function:neuroforge/cmd/worker:ollamaEmbed", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:pollLoop:defines", + "from": "file:platform/neuroforge/cmd/worker/main.go", + "to": "function:neuroforge/cmd/worker:pollLoop", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:postJSON:defines", + "from": "file:platform/neuroforge/cmd/worker/main.go", + "to": "function:neuroforge/cmd/worker:postJSON", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:postOllama:defines", + "from": "file:platform/neuroforge/cmd/worker/main.go", + "to": "function:neuroforge/cmd/worker:postOllama", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:register:defines", + "from": "file:platform/neuroforge/cmd/worker/main.go", + "to": "function:neuroforge/cmd/worker:register", + "kind": "defines" + }, { "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:run:defines", "from": "file:platform/neuroforge/cmd/worker/main.go", "to": "function:neuroforge/cmd/worker:run", "kind": "defines" }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:waitOrDone:defines", + "from": "file:platform/neuroforge/cmd/worker/main.go", + "to": "function:neuroforge/cmd/worker:waitOrDone", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003efunction:neuroforge/cmd/worker:workerBody:defines", + "from": "file:platform/neuroforge/cmd/worker/main.go", + "to": "function:neuroforge/cmd/worker:workerBody", + "kind": "defines" + }, { "id": "file:platform/neuroforge/cmd/worker/main.go-\u003epackage:bytes:imports", "from": "file:platform/neuroforge/cmd/worker/main.go", "to": "package:bytes", "kind": "imports" }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003epackage:context:imports", + "from": "file:platform/neuroforge/cmd/worker/main.go", + "to": "package:context", + "kind": "imports" + }, { "id": "file:platform/neuroforge/cmd/worker/main.go-\u003epackage:encoding/json:imports", "from": "file:platform/neuroforge/cmd/worker/main.go", @@ -19914,6 +21695,12 @@ "to": "package:neuroforge/internal/vector", "kind": "imports" }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003epackage:os/signal:imports", + "from": "file:platform/neuroforge/cmd/worker/main.go", + "to": "package:os/signal", + "kind": "imports" + }, { "id": "file:platform/neuroforge/cmd/worker/main.go-\u003epackage:os:imports", "from": "file:platform/neuroforge/cmd/worker/main.go", @@ -19926,12 +21713,30 @@ "to": "package:sort", "kind": "imports" }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003epackage:strconv:imports", + "from": "file:platform/neuroforge/cmd/worker/main.go", + "to": "package:strconv", + "kind": "imports" + }, { "id": "file:platform/neuroforge/cmd/worker/main.go-\u003epackage:strings:imports", "from": "file:platform/neuroforge/cmd/worker/main.go", "to": "package:strings", "kind": "imports" }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003epackage:sync:imports", + "from": "file:platform/neuroforge/cmd/worker/main.go", + "to": "package:sync", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/cmd/worker/main.go-\u003epackage:syscall:imports", + "from": "file:platform/neuroforge/cmd/worker/main.go", + "to": "package:syscall", + "kind": "imports" + }, { "id": "file:platform/neuroforge/cmd/worker/main.go-\u003epackage:time:imports", "from": "file:platform/neuroforge/cmd/worker/main.go", @@ -20010,6 +21815,12 @@ "to": "function:neuroforge/internal/brain:Engine.chatModel", "kind": "defines" }, + { + "id": "file:platform/neuroforge/internal/brain/brain.go-\u003efunction:neuroforge/internal/brain:Engine.chatModelJSONLimitOn:defines", + "from": "file:platform/neuroforge/internal/brain/brain.go", + "to": "function:neuroforge/internal/brain:Engine.chatModelJSONLimitOn", + "kind": "defines" + }, { "id": "file:platform/neuroforge/internal/brain/brain.go-\u003efunction:neuroforge/internal/brain:Engine.chatModelLimit:defines", "from": "file:platform/neuroforge/internal/brain/brain.go", @@ -20022,6 +21833,12 @@ "to": "function:neuroforge/internal/brain:Engine.chatModelLimitOn", "kind": "defines" }, + { + "id": "file:platform/neuroforge/internal/brain/brain.go-\u003efunction:neuroforge/internal/brain:Engine.chatModelLimitOnMode:defines", + "from": "file:platform/neuroforge/internal/brain/brain.go", + "to": "function:neuroforge/internal/brain:Engine.chatModelLimitOnMode", + "kind": "defines" + }, { "id": "file:platform/neuroforge/internal/brain/brain.go-\u003efunction:neuroforge/internal/brain:Engine.embed:defines", "from": "file:platform/neuroforge/internal/brain/brain.go", @@ -20130,6 +21947,12 @@ "to": "function:neuroforge/internal/brain:vectorCentroid", "kind": "defines" }, + { + "id": "file:platform/neuroforge/internal/brain/brain.go-\u003efunction:neuroforge/internal/brain:vectorFingerprint:defines", + "from": "file:platform/neuroforge/internal/brain/brain.go", + "to": "function:neuroforge/internal/brain:vectorFingerprint", + "kind": "defines" + }, { "id": "file:platform/neuroforge/internal/brain/brain.go-\u003epackage:bytes:imports", "from": "file:platform/neuroforge/internal/brain/brain.go", @@ -20142,6 +21965,24 @@ "to": "package:context", "kind": "imports" }, + { + "id": "file:platform/neuroforge/internal/brain/brain.go-\u003epackage:crypto/sha256:imports", + "from": "file:platform/neuroforge/internal/brain/brain.go", + "to": "package:crypto/sha256", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/brain/brain.go-\u003epackage:encoding/binary:imports", + "from": "file:platform/neuroforge/internal/brain/brain.go", + "to": "package:encoding/binary", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/brain/brain.go-\u003epackage:encoding/hex:imports", + "from": "file:platform/neuroforge/internal/brain/brain.go", + "to": "package:encoding/hex", + "kind": "imports" + }, { "id": "file:platform/neuroforge/internal/brain/brain.go-\u003epackage:encoding/json:imports", "from": "file:platform/neuroforge/internal/brain/brain.go", @@ -20166,6 +22007,12 @@ "to": "package:io", "kind": "imports" }, + { + "id": "file:platform/neuroforge/internal/brain/brain.go-\u003epackage:math:imports", + "from": "file:platform/neuroforge/internal/brain/brain.go", + "to": "package:math", + "kind": "imports" + }, { "id": "file:platform/neuroforge/internal/brain/brain.go-\u003epackage:net/http:imports", "from": "file:platform/neuroforge/internal/brain/brain.go", @@ -20238,6 +22085,66 @@ "to": "package:time", "kind": "imports" }, + { + "id": "file:platform/neuroforge/internal/brain/distributed.go-\u003efunction:neuroforge/internal/brain:Engine.distributedChat:defines", + "from": "file:platform/neuroforge/internal/brain/distributed.go", + "to": "function:neuroforge/internal/brain:Engine.distributedChat", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/distributed.go-\u003efunction:neuroforge/internal/brain:Engine.distributedEmbed:defines", + "from": "file:platform/neuroforge/internal/brain/distributed.go", + "to": "function:neuroforge/internal/brain:Engine.distributedEmbed", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/distributed.go-\u003efunction:neuroforge/internal/brain:Engine.waitDistributedJob:defines", + "from": "file:platform/neuroforge/internal/brain/distributed.go", + "to": "function:neuroforge/internal/brain:Engine.waitDistributedJob", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/distributed.go-\u003epackage:context:imports", + "from": "file:platform/neuroforge/internal/brain/distributed.go", + "to": "package:context", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/brain/distributed.go-\u003epackage:encoding/json:imports", + "from": "file:platform/neuroforge/internal/brain/distributed.go", + "to": "package:encoding/json", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/brain/distributed.go-\u003epackage:errors:imports", + "from": "file:platform/neuroforge/internal/brain/distributed.go", + "to": "package:errors", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/brain/distributed.go-\u003epackage:fmt:imports", + "from": "file:platform/neuroforge/internal/brain/distributed.go", + "to": "package:fmt", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/brain/distributed.go-\u003epackage:neuroforge/internal/provider:imports", + "from": "file:platform/neuroforge/internal/brain/distributed.go", + "to": "package:neuroforge/internal/provider", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/brain/distributed.go-\u003epackage:neuroforge/internal/store:imports", + "from": "file:platform/neuroforge/internal/brain/distributed.go", + "to": "package:neuroforge/internal/store", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/brain/distributed.go-\u003epackage:time:imports", + "from": "file:platform/neuroforge/internal/brain/distributed.go", + "to": "package:time", + "kind": "imports" + }, { "id": "file:platform/neuroforge/internal/brain/goal_progress.go-\u003efunction:neuroforge/internal/brain:Engine.ReconcileGoalProgress:defines", "from": "file:platform/neuroforge/internal/brain/goal_progress.go", @@ -20298,6 +22205,54 @@ "to": "package:strings", "kind": "imports" }, + { + "id": "file:platform/neuroforge/internal/brain/orchestrator.go-\u003efunction:neuroforge/internal/brain:Engine.ApplyAndFinalizeJob:defines", + "from": "file:platform/neuroforge/internal/brain/orchestrator.go", + "to": "function:neuroforge/internal/brain:Engine.ApplyAndFinalizeJob", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/orchestrator.go-\u003efunction:neuroforge/internal/brain:Engine.ApplyPendingJobResults:defines", + "from": "file:platform/neuroforge/internal/brain/orchestrator.go", + "to": "function:neuroforge/internal/brain:Engine.ApplyPendingJobResults", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/orchestrator.go-\u003efunction:neuroforge/internal/brain:Engine.PlanGraphBackfill:defines", + "from": "file:platform/neuroforge/internal/brain/orchestrator.go", + "to": "function:neuroforge/internal/brain:Engine.PlanGraphBackfill", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/orchestrator.go-\u003efunction:neuroforge/internal/brain:Engine.RunOrchestrator:defines", + "from": "file:platform/neuroforge/internal/brain/orchestrator.go", + "to": "function:neuroforge/internal/brain:Engine.RunOrchestrator", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/orchestrator.go-\u003epackage:context:imports", + "from": "file:platform/neuroforge/internal/brain/orchestrator.go", + "to": "package:context", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/brain/orchestrator.go-\u003epackage:errors:imports", + "from": "file:platform/neuroforge/internal/brain/orchestrator.go", + "to": "package:errors", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/brain/orchestrator.go-\u003epackage:neuroforge/internal/core:imports", + "from": "file:platform/neuroforge/internal/brain/orchestrator.go", + "to": "package:neuroforge/internal/core", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/brain/orchestrator.go-\u003epackage:time:imports", + "from": "file:platform/neuroforge/internal/brain/orchestrator.go", + "to": "package:time", + "kind": "imports" + }, { "id": "file:platform/neuroforge/internal/brain/policy.go-\u003efunction:neuroforge/internal/brain:Engine.duplicateMemory:defines", "from": "file:platform/neuroforge/internal/brain/policy.go", @@ -20346,6 +22301,72 @@ "to": "package:unicode/utf8", "kind": "imports" }, + { + "id": "file:platform/neuroforge/internal/brain/research_quality.go-\u003efunction:neuroforge/internal/brain:allDigits:defines", + "from": "file:platform/neuroforge/internal/brain/research_quality.go", + "to": "function:neuroforge/internal/brain:allDigits", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/research_quality.go-\u003efunction:neuroforge/internal/brain:goalAnchorTokens:defines", + "from": "file:platform/neuroforge/internal/brain/research_quality.go", + "to": "function:neuroforge/internal/brain:goalAnchorTokens", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/research_quality.go-\u003efunction:neuroforge/internal/brain:goalEvidenceRelevant:defines", + "from": "file:platform/neuroforge/internal/brain/research_quality.go", + "to": "function:neuroforge/internal/brain:goalEvidenceRelevant", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/research_quality.go-\u003efunction:neuroforge/internal/brain:goalIDFromTags:defines", + "from": "file:platform/neuroforge/internal/brain/research_quality.go", + "to": "function:neuroforge/internal/brain:goalIDFromTags", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/research_quality.go-\u003efunction:neuroforge/internal/brain:memoryBelongsToGoal:defines", + "from": "file:platform/neuroforge/internal/brain/research_quality.go", + "to": "function:neuroforge/internal/brain:memoryBelongsToGoal", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/research_quality.go-\u003efunction:neuroforge/internal/brain:normalizedResearchWords:defines", + "from": "file:platform/neuroforge/internal/brain/research_quality.go", + "to": "function:neuroforge/internal/brain:normalizedResearchWords", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/research_quality.go-\u003efunction:neuroforge/internal/brain:researchMaterialRelevant:defines", + "from": "file:platform/neuroforge/internal/brain/research_quality.go", + "to": "function:neuroforge/internal/brain:researchMaterialRelevant", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/research_quality.go-\u003epackage:neuroforge/internal/core:imports", + "from": "file:platform/neuroforge/internal/brain/research_quality.go", + "to": "package:neuroforge/internal/core", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/brain/research_quality.go-\u003epackage:sort:imports", + "from": "file:platform/neuroforge/internal/brain/research_quality.go", + "to": "package:sort", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/brain/research_quality.go-\u003epackage:strings:imports", + "from": "file:platform/neuroforge/internal/brain/research_quality.go", + "to": "package:strings", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/brain/research_quality.go-\u003epackage:unicode:imports", + "from": "file:platform/neuroforge/internal/brain/research_quality.go", + "to": "package:unicode", + "kind": "imports" + }, { "id": "file:platform/neuroforge/internal/brain/research_trace.go-\u003efunction:neuroforge/internal/brain:Engine.newResearchTrace:defines", "from": "file:platform/neuroforge/internal/brain/research_trace.go", @@ -20412,12 +22433,24 @@ "to": "function:neuroforge/internal/brain:Engine.collectGoalDraftEvidence", "kind": "defines" }, + { + "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:Engine.ensureGoalDraftArticleDepth:defines", + "from": "file:platform/neuroforge/internal/brain/staging.go", + "to": "function:neuroforge/internal/brain:Engine.ensureGoalDraftArticleDepth", + "kind": "defines" + }, { "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:Engine.maybePublishGoalDraft:defines", "from": "file:platform/neuroforge/internal/brain/staging.go", "to": "function:neuroforge/internal/brain:Engine.maybePublishGoalDraft", "kind": "defines" }, + { + "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle:defines", + "from": "file:platform/neuroforge/internal/brain/staging.go", + "to": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", + "kind": "defines" + }, { "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:Engine.stagingConfig:defines", "from": "file:platform/neuroforge/internal/brain/staging.go", @@ -20430,18 +22463,72 @@ "to": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", "kind": "defines" }, + { + "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:articleQualityForDraft:defines", + "from": "file:platform/neuroforge/internal/brain/staging.go", + "to": "function:neuroforge/internal/brain:articleQualityForDraft", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:articleRuneCount:defines", + "from": "file:platform/neuroforge/internal/brain/staging.go", + "to": "function:neuroforge/internal/brain:articleRuneCount", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:decodeStagingSynthesisJSON:defines", + "from": "file:platform/neuroforge/internal/brain/staging.go", + "to": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON", + "kind": "defines" + }, { "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:deterministicDraftAnswer:defines", "from": "file:platform/neuroforge/internal/brain/staging.go", "to": "function:neuroforge/internal/brain:deterministicDraftAnswer", "kind": "defines" }, + { + "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:deterministicStagingFailure:defines", + "from": "file:platform/neuroforge/internal/brain/staging.go", + "to": "function:neuroforge/internal/brain:deterministicStagingFailure", + "kind": "defines" + }, { "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:goalKeywords:defines", "from": "file:platform/neuroforge/internal/brain/staging.go", "to": "function:neuroforge/internal/brain:goalKeywords", "kind": "defines" }, + { + "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:isJSONHex4:defines", + "from": "file:platform/neuroforge/internal/brain/staging.go", + "to": "function:neuroforge/internal/brain:isJSONHex4", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:repairInvalidJSONStringEscapes:defines", + "from": "file:platform/neuroforge/internal/brain/staging.go", + "to": "function:neuroforge/internal/brain:repairInvalidJSONStringEscapes", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:stagingEvidenceSignature:defines", + "from": "file:platform/neuroforge/internal/brain/staging.go", + "to": "function:neuroforge/internal/brain:stagingEvidenceSignature", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:strictUnmarshalJSONObject:defines", + "from": "file:platform/neuroforge/internal/brain/staging.go", + "to": "function:neuroforge/internal/brain:strictUnmarshalJSONObject", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging.go-\u003efunction:neuroforge/internal/brain:validateDraftArticleBounds:defines", + "from": "file:platform/neuroforge/internal/brain/staging.go", + "to": "function:neuroforge/internal/brain:validateDraftArticleBounds", + "kind": "defines" + }, { "id": "file:platform/neuroforge/internal/brain/staging.go-\u003epackage:bytes:imports", "from": "file:platform/neuroforge/internal/brain/staging.go", @@ -20454,6 +22541,12 @@ "to": "package:context", "kind": "imports" }, + { + "id": "file:platform/neuroforge/internal/brain/staging.go-\u003epackage:crypto/sha256:imports", + "from": "file:platform/neuroforge/internal/brain/staging.go", + "to": "package:crypto/sha256", + "kind": "imports" + }, { "id": "file:platform/neuroforge/internal/brain/staging.go-\u003epackage:encoding/json:imports", "from": "file:platform/neuroforge/internal/brain/staging.go", @@ -20502,6 +22595,198 @@ "to": "package:strings", "kind": "imports" }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:Engine.repairDraftGrounding:defines", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:Engine.verifyDraftClaims:defines", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "function:neuroforge/internal/brain:Engine.verifyDraftClaims", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:countDraftIndependentCorroborations:defines", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "function:neuroforge/internal/brain:countDraftIndependentCorroborations", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:criticalIdentifiers:defines", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "function:neuroforge/internal/brain:criticalIdentifiers", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:decodeVerifierJSON:defines", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "function:neuroforge/internal/brain:decodeVerifierJSON", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:domainMatches:defines", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "function:neuroforge/internal/brain:domainMatches", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:draftEvidenceAuthorityScore:defines", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "function:neuroforge/internal/brain:draftEvidenceAuthorityScore", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:evidencePackForPrompt:defines", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "function:neuroforge/internal/brain:evidencePackForPrompt", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:extractDraftStatements:defines", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "function:neuroforge/internal/brain:extractDraftStatements", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:goalPreferredAuthorityDomains:defines", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "function:neuroforge/internal/brain:goalPreferredAuthorityDomains", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:isActionableDraftStatement:defines", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "function:neuroforge/internal/brain:isActionableDraftStatement", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:normalizeAuthoritativeDomain:defines", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "function:neuroforge/internal/brain:normalizeAuthoritativeDomain", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:normalizeStatementText:defines", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "function:neuroforge/internal/brain:normalizeStatementText", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:promptEvidenceTexts:defines", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "function:neuroforge/internal/brain:promptEvidenceTexts", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:slashSwitchIdentifiers:defines", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "function:neuroforge/internal/brain:slashSwitchIdentifiers", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:sortDraftEvidenceByAuthority:defines", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "function:neuroforge/internal/brain:sortDraftEvidenceByAuthority", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:sourceAuditForEvidence:defines", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "function:neuroforge/internal/brain:sourceAuditForEvidence", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:sourceAuthorityFor:defines", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "function:neuroforge/internal/brain:sourceAuthorityFor", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:sourceOriginKey:defines", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "function:neuroforge/internal/brain:sourceOriginKey", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:summarizeEvidenceAuthority:defines", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "function:neuroforge/internal/brain:summarizeEvidenceAuthority", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003efunction:neuroforge/internal/brain:validateDraftCriticalIdentifiers:defines", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "function:neuroforge/internal/brain:validateDraftCriticalIdentifiers", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003epackage:context:imports", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "package:context", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003epackage:encoding/json:imports", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "package:encoding/json", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003epackage:errors:imports", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "package:errors", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003epackage:fmt:imports", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "package:fmt", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003epackage:net/url:imports", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "package:net/url", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003epackage:neuroforge/internal/core:imports", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "package:neuroforge/internal/core", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003epackage:regexp:imports", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "package:regexp", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003epackage:sort:imports", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "package:sort", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003epackage:strconv:imports", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "package:strconv", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003epackage:strings:imports", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "package:strings", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/brain/staging_quality.go-\u003epackage:unicode:imports", + "from": "file:platform/neuroforge/internal/brain/staging_quality.go", + "to": "package:unicode", + "kind": "imports" + }, { "id": "file:platform/neuroforge/internal/brain/v3.go-\u003efunction:neuroforge/internal/brain:Engine.RebalanceShards:defines", "from": "file:platform/neuroforge/internal/brain/v3.go", @@ -21036,6 +23321,12 @@ "to": "function:neuroforge/internal/brain:defaultResearchTrust", "kind": "defines" }, + { + "id": "file:platform/neuroforge/internal/brain/v8.go-\u003efunction:neuroforge/internal/brain:deterministicResearchQueries:defines", + "from": "file:platform/neuroforge/internal/brain/v8.go", + "to": "function:neuroforge/internal/brain:deterministicResearchQueries", + "kind": "defines" + }, { "id": "file:platform/neuroforge/internal/brain/v8.go-\u003efunction:neuroforge/internal/brain:firstNonEmpty:defines", "from": "file:platform/neuroforge/internal/brain/v8.go", @@ -21054,6 +23345,12 @@ "to": "function:neuroforge/internal/brain:researchQueryUseful", "kind": "defines" }, + { + "id": "file:platform/neuroforge/internal/brain/v8.go-\u003efunction:neuroforge/internal/brain:researchResultsContainRelevant:defines", + "from": "file:platform/neuroforge/internal/brain/v8.go", + "to": "function:neuroforge/internal/brain:researchResultsContainRelevant", + "kind": "defines" + }, { "id": "file:platform/neuroforge/internal/brain/v8.go-\u003efunction:neuroforge/internal/brain:sourcePolicyKey:defines", "from": "file:platform/neuroforge/internal/brain/v8.go", @@ -21426,6 +23723,18 @@ "to": "function:neuroforge/internal/httpapi:Server.integrationAuth", "kind": "defines" }, + { + "id": "file:platform/neuroforge/internal/httpapi/httpapi.go-\u003efunction:neuroforge/internal/httpapi:Server.integrationGraphStatus:defines", + "from": "file:platform/neuroforge/internal/httpapi/httpapi.go", + "to": "function:neuroforge/internal/httpapi:Server.integrationGraphStatus", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/httpapi/httpapi.go-\u003efunction:neuroforge/internal/httpapi:Server.integrationOrchestratorStatus:defines", + "from": "file:platform/neuroforge/internal/httpapi/httpapi.go", + "to": "function:neuroforge/internal/httpapi:Server.integrationOrchestratorStatus", + "kind": "defines" + }, { "id": "file:platform/neuroforge/internal/httpapi/httpapi.go-\u003efunction:neuroforge/internal/httpapi:Server.json:defines", "from": "file:platform/neuroforge/internal/httpapi/httpapi.go", @@ -21510,6 +23819,18 @@ "to": "function:neuroforge/internal/httpapi:Server.workerComplete", "kind": "defines" }, + { + "id": "file:platform/neuroforge/internal/httpapi/httpapi.go-\u003efunction:neuroforge/internal/httpapi:Server.workerHeartbeat:defines", + "from": "file:platform/neuroforge/internal/httpapi/httpapi.go", + "to": "function:neuroforge/internal/httpapi:Server.workerHeartbeat", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/httpapi/httpapi.go-\u003efunction:neuroforge/internal/httpapi:Server.workerRegister:defines", + "from": "file:platform/neuroforge/internal/httpapi/httpapi.go", + "to": "function:neuroforge/internal/httpapi:Server.workerRegister", + "kind": "defines" + }, { "id": "file:platform/neuroforge/internal/httpapi/httpapi.go-\u003efunction:neuroforge/internal/httpapi:bearer:defines", "from": "file:platform/neuroforge/internal/httpapi/httpapi.go", @@ -21570,6 +23891,12 @@ "to": "function:neuroforge/internal/httpapi:statusWriter.WriteHeader", "kind": "defines" }, + { + "id": "file:platform/neuroforge/internal/httpapi/httpapi.go-\u003efunction:neuroforge/internal/httpapi:workerHeartbeatFromRequest:defines", + "from": "file:platform/neuroforge/internal/httpapi/httpapi.go", + "to": "function:neuroforge/internal/httpapi:workerHeartbeatFromRequest", + "kind": "defines" + }, { "id": "file:platform/neuroforge/internal/httpapi/httpapi.go-\u003epackage:context:imports", "from": "file:platform/neuroforge/internal/httpapi/httpapi.go", @@ -21870,6 +24197,18 @@ "to": "function:neuroforge/internal/httpapi:Server.adminGetLearningPolicy", "kind": "defines" }, + { + "id": "file:platform/neuroforge/internal/httpapi/knowledge.go-\u003efunction:neuroforge/internal/httpapi:Server.adminGraphBackfill:defines", + "from": "file:platform/neuroforge/internal/httpapi/knowledge.go", + "to": "function:neuroforge/internal/httpapi:Server.adminGraphBackfill", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/httpapi/knowledge.go-\u003efunction:neuroforge/internal/httpapi:Server.adminGraphStatus:defines", + "from": "file:platform/neuroforge/internal/httpapi/knowledge.go", + "to": "function:neuroforge/internal/httpapi:Server.adminGraphStatus", + "kind": "defines" + }, { "id": "file:platform/neuroforge/internal/httpapi/knowledge.go-\u003efunction:neuroforge/internal/httpapi:Server.adminKnowledgeEvents:defines", "from": "file:platform/neuroforge/internal/httpapi/knowledge.go", @@ -21906,6 +24245,30 @@ "to": "function:neuroforge/internal/httpapi:Server.adminKnowledgeSummary", "kind": "defines" }, + { + "id": "file:platform/neuroforge/internal/httpapi/knowledge.go-\u003efunction:neuroforge/internal/httpapi:Server.adminOrchestratorCancel:defines", + "from": "file:platform/neuroforge/internal/httpapi/knowledge.go", + "to": "function:neuroforge/internal/httpapi:Server.adminOrchestratorCancel", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/httpapi/knowledge.go-\u003efunction:neuroforge/internal/httpapi:Server.adminOrchestratorJobs:defines", + "from": "file:platform/neuroforge/internal/httpapi/knowledge.go", + "to": "function:neuroforge/internal/httpapi:Server.adminOrchestratorJobs", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/httpapi/knowledge.go-\u003efunction:neuroforge/internal/httpapi:Server.adminOrchestratorRetry:defines", + "from": "file:platform/neuroforge/internal/httpapi/knowledge.go", + "to": "function:neuroforge/internal/httpapi:Server.adminOrchestratorRetry", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/httpapi/knowledge.go-\u003efunction:neuroforge/internal/httpapi:Server.adminOrchestratorStatus:defines", + "from": "file:platform/neuroforge/internal/httpapi/knowledge.go", + "to": "function:neuroforge/internal/httpapi:Server.adminOrchestratorStatus", + "kind": "defines" + }, { "id": "file:platform/neuroforge/internal/httpapi/knowledge.go-\u003efunction:neuroforge/internal/httpapi:Server.adminPutLearningPolicy:defines", "from": "file:platform/neuroforge/internal/httpapi/knowledge.go", @@ -22278,6 +24641,12 @@ "to": "package:neuroforge/internal/core", "kind": "imports" }, + { + "id": "file:platform/neuroforge/internal/httpapi/v3.go-\u003epackage:neuroforge/internal/store:imports", + "from": "file:platform/neuroforge/internal/httpapi/v3.go", + "to": "package:neuroforge/internal/store", + "kind": "imports" + }, { "id": "file:platform/neuroforge/internal/httpapi/v3.go-\u003epackage:strconv:imports", "from": "file:platform/neuroforge/internal/httpapi/v3.go", @@ -22698,6 +25067,12 @@ "to": "function:neuroforge/internal/provider:Router.Chat", "kind": "defines" }, + { + "id": "file:platform/neuroforge/internal/provider/provider.go-\u003efunction:neuroforge/internal/provider:Router.ChatJSONOn:defines", + "from": "file:platform/neuroforge/internal/provider/provider.go", + "to": "function:neuroforge/internal/provider:Router.ChatJSONOn", + "kind": "defines" + }, { "id": "file:platform/neuroforge/internal/provider/provider.go-\u003efunction:neuroforge/internal/provider:Router.ChatOn:defines", "from": "file:platform/neuroforge/internal/provider/provider.go", @@ -22728,6 +25103,12 @@ "to": "function:neuroforge/internal/provider:Router.chatOllama", "kind": "defines" }, + { + "id": "file:platform/neuroforge/internal/provider/provider.go-\u003efunction:neuroforge/internal/provider:Router.chatOn:defines", + "from": "file:platform/neuroforge/internal/provider/provider.go", + "to": "function:neuroforge/internal/provider:Router.chatOn", + "kind": "defines" + }, { "id": "file:platform/neuroforge/internal/provider/provider.go-\u003efunction:neuroforge/internal/provider:Router.chatOpenAI:defines", "from": "file:platform/neuroforge/internal/provider/provider.go", @@ -23340,6 +25721,102 @@ "to": "package:time", "kind": "imports" }, + { + "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003efunction:neuroforge/internal/store:Store.GraphBackfillCandidates:defines", + "from": "file:platform/neuroforge/internal/store/graph_index.go", + "to": "function:neuroforge/internal/store:Store.GraphBackfillCandidates", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003efunction:neuroforge/internal/store:Store.GraphDegree:defines", + "from": "file:platform/neuroforge/internal/store/graph_index.go", + "to": "function:neuroforge/internal/store:Store.GraphDegree", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003efunction:neuroforge/internal/store:Store.GraphStats:defines", + "from": "file:platform/neuroforge/internal/store/graph_index.go", + "to": "function:neuroforge/internal/store:Store.GraphStats", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003efunction:neuroforge/internal/store:Store.MarkGraphLinked:defines", + "from": "file:platform/neuroforge/internal/store/graph_index.go", + "to": "function:neuroforge/internal/store:Store.MarkGraphLinked", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003efunction:neuroforge/internal/store:Store.ReinforceRelation:defines", + "from": "file:platform/neuroforge/internal/store/graph_index.go", + "to": "function:neuroforge/internal/store:Store.ReinforceRelation", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003efunction:neuroforge/internal/store:Store.UpsertRelationEvidence:defines", + "from": "file:platform/neuroforge/internal/store/graph_index.go", + "to": "function:neuroforge/internal/store:Store.UpsertRelationEvidence", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003efunction:neuroforge/internal/store:Store.graphExpandLocked:defines", + "from": "file:platform/neuroforge/internal/store/graph_index.go", + "to": "function:neuroforge/internal/store:Store.graphExpandLocked", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003efunction:neuroforge/internal/store:Store.indexSynapseLocked:defines", + "from": "file:platform/neuroforge/internal/store/graph_index.go", + "to": "function:neuroforge/internal/store:Store.indexSynapseLocked", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003efunction:neuroforge/internal/store:Store.rebuildSynapseAdjLocked:defines", + "from": "file:platform/neuroforge/internal/store/graph_index.go", + "to": "function:neuroforge/internal/store:Store.rebuildSynapseAdjLocked", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003efunction:neuroforge/internal/store:Store.unindexSynapseLocked:defines", + "from": "file:platform/neuroforge/internal/store/graph_index.go", + "to": "function:neuroforge/internal/store:Store.unindexSynapseLocked", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003efunction:neuroforge/internal/store:appendUniqueRelation:defines", + "from": "file:platform/neuroforge/internal/store/graph_index.go", + "to": "function:neuroforge/internal/store:appendUniqueRelation", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003epackage:neuroforge/internal/core:imports", + "from": "file:platform/neuroforge/internal/store/graph_index.go", + "to": "package:neuroforge/internal/core", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003epackage:neuroforge/internal/vector:imports", + "from": "file:platform/neuroforge/internal/store/graph_index.go", + "to": "package:neuroforge/internal/vector", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003epackage:sort:imports", + "from": "file:platform/neuroforge/internal/store/graph_index.go", + "to": "package:sort", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003epackage:strings:imports", + "from": "file:platform/neuroforge/internal/store/graph_index.go", + "to": "package:strings", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/store/graph_index.go-\u003epackage:time:imports", + "from": "file:platform/neuroforge/internal/store/graph_index.go", + "to": "package:time", + "kind": "imports" + }, { "id": "file:platform/neuroforge/internal/store/index_segments.go-\u003efunction:neuroforge/internal/store:Store.CompactIndexSegments:defines", "from": "file:platform/neuroforge/internal/store/index_segments.go", @@ -23640,6 +26117,210 @@ "to": "package:time", "kind": "imports" }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.CancelJob:defines", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "function:neuroforge/internal/store:Store.CancelJob", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.ClaimJob:defines", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "function:neuroforge/internal/store:Store.ClaimJob", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.ClaimJobForWorker:defines", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "function:neuroforge/internal/store:Store.ClaimJobForWorker", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.CompleteJob:defines", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "function:neuroforge/internal/store:Store.CompleteJob", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.CompleteJobLease:defines", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "function:neuroforge/internal/store:Store.CompleteJobLease", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.EnqueueJob:defines", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "function:neuroforge/internal/store:Store.EnqueueJob", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.EnqueueJobSpec:defines", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "function:neuroforge/internal/store:Store.EnqueueJobSpec", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.FinishMasterApply:defines", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "function:neuroforge/internal/store:Store.FinishMasterApply", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.HasLiveWorker:defines", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "function:neuroforge/internal/store:Store.HasLiveWorker", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.HeartbeatWorker:defines", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "function:neuroforge/internal/store:Store.HeartbeatWorker", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.IsOrchestratorLeader:defines", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "function:neuroforge/internal/store:Store.IsOrchestratorLeader", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.Job:defines", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "function:neuroforge/internal/store:Store.Job", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.JobsSnapshot:defines", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "function:neuroforge/internal/store:Store.JobsSnapshot", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.OrchestratorStatus:defines", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "function:neuroforge/internal/store:Store.OrchestratorStatus", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.PendingJobCount:defines", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "function:neuroforge/internal/store:Store.PendingJobCount", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.PendingMasterApplyJobs:defines", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "function:neuroforge/internal/store:Store.PendingMasterApplyJobs", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.PruneTerminalJobs:defines", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "function:neuroforge/internal/store:Store.PruneTerminalJobs", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.RegisterWorker:defines", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "function:neuroforge/internal/store:Store.RegisterWorker", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.RetryJob:defines", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "function:neuroforge/internal/store:Store.RetryJob", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.WorkersSnapshot:defines", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "function:neuroforge/internal/store:Store.WorkersSnapshot", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.dependencyStateLocked:defines", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "function:neuroforge/internal/store:Store.dependencyStateLocked", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.normalizeJobForClaimLocked:defines", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "function:neuroforge/internal/store:Store.normalizeJobForClaimLocked", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:Store.registerWorkerLocked:defines", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "function:neuroforge/internal/store:Store.registerWorkerLocked", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:capabilitiesContain:defines", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "function:neuroforge/internal/store:capabilitiesContain", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:maxInt:defines", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "function:neuroforge/internal/store:maxInt", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:normalizeCapabilities:defines", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "function:neuroforge/internal/store:normalizeCapabilities", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003efunction:neuroforge/internal/store:workerMatchesJob:defines", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "function:neuroforge/internal/store:workerMatchesJob", + "kind": "defines" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003epackage:encoding/json:imports", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "package:encoding/json", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003epackage:errors:imports", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "package:errors", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003epackage:fmt:imports", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "package:fmt", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003epackage:neuroforge/internal/core:imports", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "package:neuroforge/internal/core", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003epackage:sort:imports", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "package:sort", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003epackage:strings:imports", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "package:strings", + "kind": "imports" + }, + { + "id": "file:platform/neuroforge/internal/store/orchestrator.go-\u003epackage:time:imports", + "from": "file:platform/neuroforge/internal/store/orchestrator.go", + "to": "package:time", + "kind": "imports" + }, { "id": "file:platform/neuroforge/internal/store/pagecache.go-\u003efunction:neuroforge/internal/store:MemoryPageCache.Delete:defines", "from": "file:platform/neuroforge/internal/store/pagecache.go", @@ -24468,12 +27149,6 @@ "to": "function:neuroforge/internal/store:Store.AddUsage", "kind": "defines" }, - { - "id": "file:platform/neuroforge/internal/store/store.go-\u003efunction:neuroforge/internal/store:Store.ClaimJob:defines", - "from": "file:platform/neuroforge/internal/store/store.go", - "to": "function:neuroforge/internal/store:Store.ClaimJob", - "kind": "defines" - }, { "id": "file:platform/neuroforge/internal/store/store.go-\u003efunction:neuroforge/internal/store:Store.Close:defines", "from": "file:platform/neuroforge/internal/store/store.go", @@ -24486,12 +27161,6 @@ "to": "function:neuroforge/internal/store:Store.CompactMemorySegments", "kind": "defines" }, - { - "id": "file:platform/neuroforge/internal/store/store.go-\u003efunction:neuroforge/internal/store:Store.CompleteJob:defines", - "from": "file:platform/neuroforge/internal/store/store.go", - "to": "function:neuroforge/internal/store:Store.CompleteJob", - "kind": "defines" - }, { "id": "file:platform/neuroforge/internal/store/store.go-\u003efunction:neuroforge/internal/store:Store.Config:defines", "from": "file:platform/neuroforge/internal/store/store.go", @@ -24516,12 +27185,6 @@ "to": "function:neuroforge/internal/store:Store.DeleteMemory", "kind": "defines" }, - { - "id": "file:platform/neuroforge/internal/store/store.go-\u003efunction:neuroforge/internal/store:Store.EnqueueJob:defines", - "from": "file:platform/neuroforge/internal/store/store.go", - "to": "function:neuroforge/internal/store:Store.EnqueueJob", - "kind": "defines" - }, { "id": "file:platform/neuroforge/internal/store/store.go-\u003efunction:neuroforge/internal/store:Store.ExportSafe:defines", "from": "file:platform/neuroforge/internal/store/store.go", @@ -25056,6 +27719,12 @@ "to": "function:neuroforge/internal/store:cloneGoal", "kind": "defines" }, + { + "id": "file:platform/neuroforge/internal/store/v3.go-\u003efunction:neuroforge/internal/store:goalFingerprint:defines", + "from": "file:platform/neuroforge/internal/store/v3.go", + "to": "function:neuroforge/internal/store:goalFingerprint", + "kind": "defines" + }, { "id": "file:platform/neuroforge/internal/store/v3.go-\u003efunction:neuroforge/internal/store:knowledgeScore:defines", "from": "file:platform/neuroforge/internal/store/v3.go", @@ -31446,6 +34115,18 @@ "to": "function:mega-control:server.handleConfig", "kind": "defines" }, + { + "id": "file:services/control/main.go-\u003efunction:mega-control:server.handleNeuroForgeGraphStatus:defines", + "from": "file:services/control/main.go", + "to": "function:mega-control:server.handleNeuroForgeGraphStatus", + "kind": "defines" + }, + { + "id": "file:services/control/main.go-\u003efunction:mega-control:server.handleNeuroForgeOrchestrator:defines", + "from": "file:services/control/main.go", + "to": "function:mega-control:server.handleNeuroForgeOrchestrator", + "kind": "defines" + }, { "id": "file:services/control/main.go-\u003efunction:mega-control:server.handleStatus:defines", "from": "file:services/control/main.go", @@ -46291,6 +48972,30 @@ "kind": "calls_package", "label": "Itoa" }, + { + "id": "function:mega-control:server.handleNeuroForgeGraphStatus-\u003efunction:mega-control:bearerHeader:calls", + "from": "function:mega-control:server.handleNeuroForgeGraphStatus", + "to": "function:mega-control:bearerHeader", + "kind": "calls" + }, + { + "id": "function:mega-control:server.handleNeuroForgeGraphStatus-\u003efunction:mega-control:server.proxyJSON:calls", + "from": "function:mega-control:server.handleNeuroForgeGraphStatus", + "to": "function:mega-control:server.proxyJSON", + "kind": "calls" + }, + { + "id": "function:mega-control:server.handleNeuroForgeOrchestrator-\u003efunction:mega-control:bearerHeader:calls", + "from": "function:mega-control:server.handleNeuroForgeOrchestrator", + "to": "function:mega-control:bearerHeader", + "kind": "calls" + }, + { + "id": "function:mega-control:server.handleNeuroForgeOrchestrator-\u003efunction:mega-control:server.proxyJSON:calls", + "from": "function:mega-control:server.handleNeuroForgeOrchestrator", + "to": "function:mega-control:server.proxyJSON", + "kind": "calls" + }, { "id": "function:mega-control:server.handleResearchGraph-\u003efunction:mega-control:bearerHeader:calls", "from": "function:mega-control:server.handleResearchGraph", @@ -46590,6 +49295,27 @@ "kind": "calls_package", "label": "TrimSpace" }, + { + "id": "function:neuroforge/cmd/server:envFloat-\u003epackage:os:calls_package", + "from": "function:neuroforge/cmd/server:envFloat", + "to": "package:os", + "kind": "calls_package", + "label": "LookupEnv" + }, + { + "id": "function:neuroforge/cmd/server:envFloat-\u003epackage:strconv:calls_package", + "from": "function:neuroforge/cmd/server:envFloat", + "to": "package:strconv", + "kind": "calls_package", + "label": "ParseFloat" + }, + { + "id": "function:neuroforge/cmd/server:envFloat-\u003epackage:strings:calls_package", + "from": "function:neuroforge/cmd/server:envFloat", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, { "id": "function:neuroforge/cmd/server:envInt-\u003epackage:os:calls_package", "from": "function:neuroforge/cmd/server:envInt", @@ -46611,6 +49337,13 @@ "kind": "calls_package", "label": "TrimSpace" }, + { + "id": "function:neuroforge/cmd/server:firstNonEmptyMain-\u003epackage:strings:calls_package", + "from": "function:neuroforge/cmd/server:firstNonEmptyMain", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, { "id": "function:neuroforge/cmd/server:main-\u003efunction:neuroforge/cmd/server:run:calls", "from": "function:neuroforge/cmd/server:main", @@ -46637,12 +49370,24 @@ "to": "function:neuroforge/cmd/server:envBool", "kind": "calls" }, + { + "id": "function:neuroforge/cmd/server:run-\u003efunction:neuroforge/cmd/server:envFloat:calls", + "from": "function:neuroforge/cmd/server:run", + "to": "function:neuroforge/cmd/server:envFloat", + "kind": "calls" + }, { "id": "function:neuroforge/cmd/server:run-\u003efunction:neuroforge/cmd/server:envInt:calls", "from": "function:neuroforge/cmd/server:run", "to": "function:neuroforge/cmd/server:envInt", "kind": "calls" }, + { + "id": "function:neuroforge/cmd/server:run-\u003efunction:neuroforge/cmd/server:firstNonEmptyMain:calls", + "from": "function:neuroforge/cmd/server:run", + "to": "function:neuroforge/cmd/server:firstNonEmptyMain", + "kind": "calls" + }, { "id": "function:neuroforge/cmd/server:run-\u003efunction:neuroforge/cmd/server:maxIntMain:calls", "from": "function:neuroforge/cmd/server:run", @@ -46787,6 +49532,12 @@ "kind": "calls_package", "label": "Getenv" }, + { + "id": "function:neuroforge/cmd/worker:claim-\u003efunction:neuroforge/cmd/worker:workerBody:calls", + "from": "function:neuroforge/cmd/worker:claim", + "to": "function:neuroforge/cmd/worker:workerBody", + "kind": "calls" + }, { "id": "function:neuroforge/cmd/worker:claim-\u003epackage:bytes:calls_package", "from": "function:neuroforge/cmd/worker:claim", @@ -46823,53 +49574,112 @@ "label": "NewRequest" }, { - "id": "function:neuroforge/cmd/worker:claim-\u003epackage:strings:calls_package", - "from": "function:neuroforge/cmd/worker:claim", + "id": "function:neuroforge/cmd/worker:complete-\u003efunction:neuroforge/cmd/worker:postJSON:calls", + "from": "function:neuroforge/cmd/worker:complete", + "to": "function:neuroforge/cmd/worker:postJSON", + "kind": "calls" + }, + { + "id": "function:neuroforge/cmd/worker:csv-\u003epackage:sort:calls_package", + "from": "function:neuroforge/cmd/worker:csv", + "to": "package:sort", + "kind": "calls_package", + "label": "Strings" + }, + { + "id": "function:neuroforge/cmd/worker:csv-\u003epackage:strings:calls_package", + "from": "function:neuroforge/cmd/worker:csv", "to": "package:strings", "kind": "calls_package", - "label": "TrimRight" + "label": "Split" }, { - "id": "function:neuroforge/cmd/worker:complete-\u003epackage:bytes:calls_package", - "from": "function:neuroforge/cmd/worker:complete", - "to": "package:bytes", + "id": "function:neuroforge/cmd/worker:envDuration-\u003epackage:os:calls_package", + "from": "function:neuroforge/cmd/worker:envDuration", + "to": "package:os", "kind": "calls_package", - "label": "NewReader" + "label": "Getenv" }, { - "id": "function:neuroforge/cmd/worker:complete-\u003epackage:encoding/json:calls_package", - "from": "function:neuroforge/cmd/worker:complete", - "to": "package:encoding/json", - "kind": "calls_package", - "label": "Marshal" - }, - { - "id": "function:neuroforge/cmd/worker:complete-\u003epackage:fmt:calls_package", - "from": "function:neuroforge/cmd/worker:complete", - "to": "package:fmt", - "kind": "calls_package", - "label": "Errorf" - }, - { - "id": "function:neuroforge/cmd/worker:complete-\u003epackage:io:calls_package", - "from": "function:neuroforge/cmd/worker:complete", - "to": "package:io", - "kind": "calls_package", - "label": "ReadAll" - }, - { - "id": "function:neuroforge/cmd/worker:complete-\u003epackage:net/http:calls_package", - "from": "function:neuroforge/cmd/worker:complete", - "to": "package:net/http", - "kind": "calls_package", - "label": "NewRequest" - }, - { - "id": "function:neuroforge/cmd/worker:complete-\u003epackage:strings:calls_package", - "from": "function:neuroforge/cmd/worker:complete", + "id": "function:neuroforge/cmd/worker:envDuration-\u003epackage:strings:calls_package", + "from": "function:neuroforge/cmd/worker:envDuration", "to": "package:strings", "kind": "calls_package", - "label": "TrimRight" + "label": "TrimSpace" + }, + { + "id": "function:neuroforge/cmd/worker:envDuration-\u003epackage:time:calls_package", + "from": "function:neuroforge/cmd/worker:envDuration", + "to": "package:time", + "kind": "calls_package", + "label": "ParseDuration" + }, + { + "id": "function:neuroforge/cmd/worker:envInt-\u003epackage:os:calls_package", + "from": "function:neuroforge/cmd/worker:envInt", + "to": "package:os", + "kind": "calls_package", + "label": "Getenv" + }, + { + "id": "function:neuroforge/cmd/worker:envInt-\u003epackage:strconv:calls_package", + "from": "function:neuroforge/cmd/worker:envInt", + "to": "package:strconv", + "kind": "calls_package", + "label": "Atoi" + }, + { + "id": "function:neuroforge/cmd/worker:envInt-\u003epackage:strings:calls_package", + "from": "function:neuroforge/cmd/worker:envInt", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, + { + "id": "function:neuroforge/cmd/worker:firstNonEmpty-\u003epackage:strings:calls_package", + "from": "function:neuroforge/cmd/worker:firstNonEmpty", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, + { + "id": "function:neuroforge/cmd/worker:hasCap-\u003epackage:strings:calls_package", + "from": "function:neuroforge/cmd/worker:hasCap", + "to": "package:strings", + "kind": "calls_package", + "label": "ToLower" + }, + { + "id": "function:neuroforge/cmd/worker:heartbeatLoop-\u003efunction:neuroforge/cmd/worker:leaseSet.snapshot:calls", + "from": "function:neuroforge/cmd/worker:heartbeatLoop", + "to": "function:neuroforge/cmd/worker:leaseSet.snapshot", + "kind": "calls" + }, + { + "id": "function:neuroforge/cmd/worker:heartbeatLoop-\u003efunction:neuroforge/cmd/worker:postJSON:calls", + "from": "function:neuroforge/cmd/worker:heartbeatLoop", + "to": "function:neuroforge/cmd/worker:postJSON", + "kind": "calls" + }, + { + "id": "function:neuroforge/cmd/worker:heartbeatLoop-\u003efunction:neuroforge/cmd/worker:workerBody:calls", + "from": "function:neuroforge/cmd/worker:heartbeatLoop", + "to": "function:neuroforge/cmd/worker:workerBody", + "kind": "calls" + }, + { + "id": "function:neuroforge/cmd/worker:heartbeatLoop-\u003epackage:log:calls_package", + "from": "function:neuroforge/cmd/worker:heartbeatLoop", + "to": "package:log", + "kind": "calls_package", + "label": "Printf" + }, + { + "id": "function:neuroforge/cmd/worker:heartbeatLoop-\u003epackage:time:calls_package", + "from": "function:neuroforge/cmd/worker:heartbeatLoop", + "to": "package:time", + "kind": "calls_package", + "label": "NewTicker" }, { "id": "function:neuroforge/cmd/worker:hostname-\u003epackage:os:calls_package", @@ -46879,15 +49689,40 @@ "label": "Hostname" }, { - "id": "function:neuroforge/cmd/worker:main-\u003efunction:neuroforge/cmd/worker:claim:calls", + "id": "function:neuroforge/cmd/worker:lowerDefault-\u003epackage:strings:calls_package", + "from": "function:neuroforge/cmd/worker:lowerDefault", + "to": "package:strings", + "kind": "calls_package", + "label": "ToLower" + }, + { + "id": "function:neuroforge/cmd/worker:main-\u003efunction:neuroforge/cmd/worker:csv:calls", "from": "function:neuroforge/cmd/worker:main", - "to": "function:neuroforge/cmd/worker:claim", + "to": "function:neuroforge/cmd/worker:csv", "kind": "calls" }, { - "id": "function:neuroforge/cmd/worker:main-\u003efunction:neuroforge/cmd/worker:complete:calls", + "id": "function:neuroforge/cmd/worker:main-\u003efunction:neuroforge/cmd/worker:envDuration:calls", "from": "function:neuroforge/cmd/worker:main", - "to": "function:neuroforge/cmd/worker:complete", + "to": "function:neuroforge/cmd/worker:envDuration", + "kind": "calls" + }, + { + "id": "function:neuroforge/cmd/worker:main-\u003efunction:neuroforge/cmd/worker:envInt:calls", + "from": "function:neuroforge/cmd/worker:main", + "to": "function:neuroforge/cmd/worker:envInt", + "kind": "calls" + }, + { + "id": "function:neuroforge/cmd/worker:main-\u003efunction:neuroforge/cmd/worker:firstNonEmpty:calls", + "from": "function:neuroforge/cmd/worker:main", + "to": "function:neuroforge/cmd/worker:firstNonEmpty", + "kind": "calls" + }, + { + "id": "function:neuroforge/cmd/worker:main-\u003efunction:neuroforge/cmd/worker:heartbeatLoop:calls", + "from": "function:neuroforge/cmd/worker:main", + "to": "function:neuroforge/cmd/worker:heartbeatLoop", "kind": "calls" }, { @@ -46897,11 +49732,30 @@ "kind": "calls" }, { - "id": "function:neuroforge/cmd/worker:main-\u003efunction:neuroforge/cmd/worker:run:calls", + "id": "function:neuroforge/cmd/worker:main-\u003efunction:neuroforge/cmd/worker:lowerDefault:calls", "from": "function:neuroforge/cmd/worker:main", - "to": "function:neuroforge/cmd/worker:run", + "to": "function:neuroforge/cmd/worker:lowerDefault", "kind": "calls" }, + { + "id": "function:neuroforge/cmd/worker:main-\u003efunction:neuroforge/cmd/worker:pollLoop:calls", + "from": "function:neuroforge/cmd/worker:main", + "to": "function:neuroforge/cmd/worker:pollLoop", + "kind": "calls" + }, + { + "id": "function:neuroforge/cmd/worker:main-\u003efunction:neuroforge/cmd/worker:register:calls", + "from": "function:neuroforge/cmd/worker:main", + "to": "function:neuroforge/cmd/worker:register", + "kind": "calls" + }, + { + "id": "function:neuroforge/cmd/worker:main-\u003epackage:context:calls_package", + "from": "function:neuroforge/cmd/worker:main", + "to": "package:context", + "kind": "calls_package", + "label": "Background" + }, { "id": "function:neuroforge/cmd/worker:main-\u003epackage:flag:calls_package", "from": "function:neuroforge/cmd/worker:main", @@ -46916,6 +49770,13 @@ "kind": "calls_package", "label": "Fatal" }, + { + "id": "function:neuroforge/cmd/worker:main-\u003epackage:os/signal:calls_package", + "from": "function:neuroforge/cmd/worker:main", + "to": "package:os/signal", + "kind": "calls_package", + "label": "NotifyContext" + }, { "id": "function:neuroforge/cmd/worker:main-\u003epackage:os:calls_package", "from": "function:neuroforge/cmd/worker:main", @@ -46931,11 +49792,227 @@ "label": "TrimSpace" }, { - "id": "function:neuroforge/cmd/worker:main-\u003epackage:time:calls_package", - "from": "function:neuroforge/cmd/worker:main", + "id": "function:neuroforge/cmd/worker:ollamaChat-\u003efunction:neuroforge/cmd/worker:firstNonEmpty:calls", + "from": "function:neuroforge/cmd/worker:ollamaChat", + "to": "function:neuroforge/cmd/worker:firstNonEmpty", + "kind": "calls" + }, + { + "id": "function:neuroforge/cmd/worker:ollamaChat-\u003efunction:neuroforge/cmd/worker:postOllama:calls", + "from": "function:neuroforge/cmd/worker:ollamaChat", + "to": "function:neuroforge/cmd/worker:postOllama", + "kind": "calls" + }, + { + "id": "function:neuroforge/cmd/worker:ollamaChat-\u003epackage:fmt:calls_package", + "from": "function:neuroforge/cmd/worker:ollamaChat", + "to": "package:fmt", + "kind": "calls_package", + "label": "Errorf" + }, + { + "id": "function:neuroforge/cmd/worker:ollamaChat-\u003epackage:strings:calls_package", + "from": "function:neuroforge/cmd/worker:ollamaChat", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, + { + "id": "function:neuroforge/cmd/worker:ollamaEmbed-\u003efunction:neuroforge/cmd/worker:firstNonEmpty:calls", + "from": "function:neuroforge/cmd/worker:ollamaEmbed", + "to": "function:neuroforge/cmd/worker:firstNonEmpty", + "kind": "calls" + }, + { + "id": "function:neuroforge/cmd/worker:ollamaEmbed-\u003efunction:neuroforge/cmd/worker:postOllama:calls", + "from": "function:neuroforge/cmd/worker:ollamaEmbed", + "to": "function:neuroforge/cmd/worker:postOllama", + "kind": "calls" + }, + { + "id": "function:neuroforge/cmd/worker:ollamaEmbed-\u003epackage:fmt:calls_package", + "from": "function:neuroforge/cmd/worker:ollamaEmbed", + "to": "package:fmt", + "kind": "calls_package", + "label": "Errorf" + }, + { + "id": "function:neuroforge/cmd/worker:ollamaEmbed-\u003epackage:strings:calls_package", + "from": "function:neuroforge/cmd/worker:ollamaEmbed", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, + { + "id": "function:neuroforge/cmd/worker:pollLoop-\u003efunction:neuroforge/cmd/worker:claim:calls", + "from": "function:neuroforge/cmd/worker:pollLoop", + "to": "function:neuroforge/cmd/worker:claim", + "kind": "calls" + }, + { + "id": "function:neuroforge/cmd/worker:pollLoop-\u003efunction:neuroforge/cmd/worker:complete:calls", + "from": "function:neuroforge/cmd/worker:pollLoop", + "to": "function:neuroforge/cmd/worker:complete", + "kind": "calls" + }, + { + "id": "function:neuroforge/cmd/worker:pollLoop-\u003efunction:neuroforge/cmd/worker:leaseSet.add:calls", + "from": "function:neuroforge/cmd/worker:pollLoop", + "to": "function:neuroforge/cmd/worker:leaseSet.add", + "kind": "calls" + }, + { + "id": "function:neuroforge/cmd/worker:pollLoop-\u003efunction:neuroforge/cmd/worker:leaseSet.del:calls", + "from": "function:neuroforge/cmd/worker:pollLoop", + "to": "function:neuroforge/cmd/worker:leaseSet.del", + "kind": "calls" + }, + { + "id": "function:neuroforge/cmd/worker:pollLoop-\u003efunction:neuroforge/cmd/worker:run:calls", + "from": "function:neuroforge/cmd/worker:pollLoop", + "to": "function:neuroforge/cmd/worker:run", + "kind": "calls" + }, + { + "id": "function:neuroforge/cmd/worker:pollLoop-\u003efunction:neuroforge/cmd/worker:waitOrDone:calls", + "from": "function:neuroforge/cmd/worker:pollLoop", + "to": "function:neuroforge/cmd/worker:waitOrDone", + "kind": "calls" + }, + { + "id": "function:neuroforge/cmd/worker:pollLoop-\u003epackage:context:calls_package", + "from": "function:neuroforge/cmd/worker:pollLoop", + "to": "package:context", + "kind": "calls_package", + "label": "WithTimeout" + }, + { + "id": "function:neuroforge/cmd/worker:pollLoop-\u003epackage:log:calls_package", + "from": "function:neuroforge/cmd/worker:pollLoop", + "to": "package:log", + "kind": "calls_package", + "label": "Printf" + }, + { + "id": "function:neuroforge/cmd/worker:pollLoop-\u003epackage:time:calls_package", + "from": "function:neuroforge/cmd/worker:pollLoop", "to": "package:time", "kind": "calls_package", - "label": "Sleep" + "label": "Duration" + }, + { + "id": "function:neuroforge/cmd/worker:postJSON-\u003epackage:bytes:calls_package", + "from": "function:neuroforge/cmd/worker:postJSON", + "to": "package:bytes", + "kind": "calls_package", + "label": "NewReader" + }, + { + "id": "function:neuroforge/cmd/worker:postJSON-\u003epackage:encoding/json:calls_package", + "from": "function:neuroforge/cmd/worker:postJSON", + "to": "package:encoding/json", + "kind": "calls_package", + "label": "Marshal" + }, + { + "id": "function:neuroforge/cmd/worker:postJSON-\u003epackage:fmt:calls_package", + "from": "function:neuroforge/cmd/worker:postJSON", + "to": "package:fmt", + "kind": "calls_package", + "label": "Errorf" + }, + { + "id": "function:neuroforge/cmd/worker:postJSON-\u003epackage:io:calls_package", + "from": "function:neuroforge/cmd/worker:postJSON", + "to": "package:io", + "kind": "calls_package", + "label": "ReadAll" + }, + { + "id": "function:neuroforge/cmd/worker:postJSON-\u003epackage:net/http:calls_package", + "from": "function:neuroforge/cmd/worker:postJSON", + "to": "package:net/http", + "kind": "calls_package", + "label": "NewRequest" + }, + { + "id": "function:neuroforge/cmd/worker:postJSON-\u003epackage:strings:calls_package", + "from": "function:neuroforge/cmd/worker:postJSON", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, + { + "id": "function:neuroforge/cmd/worker:postOllama-\u003epackage:bytes:calls_package", + "from": "function:neuroforge/cmd/worker:postOllama", + "to": "package:bytes", + "kind": "calls_package", + "label": "NewReader" + }, + { + "id": "function:neuroforge/cmd/worker:postOllama-\u003epackage:encoding/json:calls_package", + "from": "function:neuroforge/cmd/worker:postOllama", + "to": "package:encoding/json", + "kind": "calls_package", + "label": "Marshal" + }, + { + "id": "function:neuroforge/cmd/worker:postOllama-\u003epackage:fmt:calls_package", + "from": "function:neuroforge/cmd/worker:postOllama", + "to": "package:fmt", + "kind": "calls_package", + "label": "Errorf" + }, + { + "id": "function:neuroforge/cmd/worker:postOllama-\u003epackage:io:calls_package", + "from": "function:neuroforge/cmd/worker:postOllama", + "to": "package:io", + "kind": "calls_package", + "label": "ReadAll" + }, + { + "id": "function:neuroforge/cmd/worker:postOllama-\u003epackage:net/http:calls_package", + "from": "function:neuroforge/cmd/worker:postOllama", + "to": "package:net/http", + "kind": "calls_package", + "label": "NewRequestWithContext" + }, + { + "id": "function:neuroforge/cmd/worker:postOllama-\u003epackage:strings:calls_package", + "from": "function:neuroforge/cmd/worker:postOllama", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, + { + "id": "function:neuroforge/cmd/worker:register-\u003efunction:neuroforge/cmd/worker:postJSON:calls", + "from": "function:neuroforge/cmd/worker:register", + "to": "function:neuroforge/cmd/worker:postJSON", + "kind": "calls" + }, + { + "id": "function:neuroforge/cmd/worker:register-\u003efunction:neuroforge/cmd/worker:workerBody:calls", + "from": "function:neuroforge/cmd/worker:register", + "to": "function:neuroforge/cmd/worker:workerBody", + "kind": "calls" + }, + { + "id": "function:neuroforge/cmd/worker:run-\u003efunction:neuroforge/cmd/worker:hasCap:calls", + "from": "function:neuroforge/cmd/worker:run", + "to": "function:neuroforge/cmd/worker:hasCap", + "kind": "calls" + }, + { + "id": "function:neuroforge/cmd/worker:run-\u003efunction:neuroforge/cmd/worker:ollamaChat:calls", + "from": "function:neuroforge/cmd/worker:run", + "to": "function:neuroforge/cmd/worker:ollamaChat", + "kind": "calls" + }, + { + "id": "function:neuroforge/cmd/worker:run-\u003efunction:neuroforge/cmd/worker:ollamaEmbed:calls", + "from": "function:neuroforge/cmd/worker:run", + "to": "function:neuroforge/cmd/worker:ollamaEmbed", + "kind": "calls" }, { "id": "function:neuroforge/cmd/worker:run-\u003epackage:encoding/json:calls_package", @@ -46959,9 +50036,29 @@ "label": "Slice" }, { - "id": "function:neuroforge/internal/brain:Engine.ApplyJobResult-\u003efunction:neuroforge/internal/brain:Engine.reinforcePair:calls", + "id": "function:neuroforge/cmd/worker:waitOrDone-\u003epackage:time:calls_package", + "from": "function:neuroforge/cmd/worker:waitOrDone", + "to": "package:time", + "kind": "calls_package", + "label": "NewTimer" + }, + { + "id": "function:neuroforge/internal/brain:Engine.ApplyAndFinalizeJob-\u003efunction:neuroforge/internal/brain:Engine.ApplyJobResult:calls", + "from": "function:neuroforge/internal/brain:Engine.ApplyAndFinalizeJob", + "to": "function:neuroforge/internal/brain:Engine.ApplyJobResult", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.ApplyAndFinalizeJob-\u003epackage:errors:calls_package", + "from": "function:neuroforge/internal/brain:Engine.ApplyAndFinalizeJob", + "to": "package:errors", + "kind": "calls_package", + "label": "Is" + }, + { + "id": "function:neuroforge/internal/brain:Engine.ApplyJobResult-\u003efunction:neuroforge/internal/brain:vectorFingerprint:calls", "from": "function:neuroforge/internal/brain:Engine.ApplyJobResult", - "to": "function:neuroforge/internal/brain:Engine.reinforcePair", + "to": "function:neuroforge/internal/brain:vectorFingerprint", "kind": "calls" }, { @@ -46971,6 +50068,13 @@ "kind": "calls_package", "label": "Unmarshal" }, + { + "id": "function:neuroforge/internal/brain:Engine.ApplyJobResult-\u003epackage:fmt:calls_package", + "from": "function:neuroforge/internal/brain:Engine.ApplyJobResult", + "to": "package:fmt", + "kind": "calls_package", + "label": "Errorf" + }, { "id": "function:neuroforge/internal/brain:Engine.ApplyJobResult-\u003epackage:sort:calls_package", "from": "function:neuroforge/internal/brain:Engine.ApplyJobResult", @@ -46978,6 +50082,12 @@ "kind": "calls_package", "label": "Slice" }, + { + "id": "function:neuroforge/internal/brain:Engine.ApplyPendingJobResults-\u003efunction:neuroforge/internal/brain:Engine.ApplyAndFinalizeJob:calls", + "from": "function:neuroforge/internal/brain:Engine.ApplyPendingJobResults", + "to": "function:neuroforge/internal/brain:Engine.ApplyAndFinalizeJob", + "kind": "calls" + }, { "id": "function:neuroforge/internal/brain:Engine.Chat-\u003efunction:neuroforge/internal/brain:Engine.addMemory:calls", "from": "function:neuroforge/internal/brain:Engine.Chat", @@ -47142,6 +50252,25 @@ "kind": "calls_package", "label": "NewID" }, + { + "id": "function:neuroforge/internal/brain:Engine.ConfigureStagingPublisher-\u003efunction:neuroforge/internal/brain:dedupeStrings:calls", + "from": "function:neuroforge/internal/brain:Engine.ConfigureStagingPublisher", + "to": "function:neuroforge/internal/brain:dedupeStrings", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.ConfigureStagingPublisher-\u003efunction:neuroforge/internal/brain:normalizeAuthoritativeDomain:calls", + "from": "function:neuroforge/internal/brain:Engine.ConfigureStagingPublisher", + "to": "function:neuroforge/internal/brain:normalizeAuthoritativeDomain", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.ConfigureStagingPublisher-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:Engine.ConfigureStagingPublisher", + "to": "package:strings", + "kind": "calls_package", + "label": "ToLower" + }, { "id": "function:neuroforge/internal/brain:Engine.Consolidate-\u003efunction:neuroforge/internal/brain:Engine.SearchVector:calls", "from": "function:neuroforge/internal/brain:Engine.Consolidate", @@ -47423,6 +50552,19 @@ "kind": "calls_package", "label": "TrimSpace" }, + { + "id": "function:neuroforge/internal/brain:Engine.PlanGraphBackfill-\u003efunction:neuroforge/internal/brain:Engine.enqueueRelink:calls", + "from": "function:neuroforge/internal/brain:Engine.PlanGraphBackfill", + "to": "function:neuroforge/internal/brain:Engine.enqueueRelink", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.PlanGraphBackfill-\u003epackage:time:calls_package", + "from": "function:neuroforge/internal/brain:Engine.PlanGraphBackfill", + "to": "package:time", + "kind": "calls_package", + "label": "Duration" + }, { "id": "function:neuroforge/internal/brain:Engine.RebalanceShards-\u003efunction:neuroforge/internal/brain:Engine.replicateMemoryToShard:calls", "from": "function:neuroforge/internal/brain:Engine.RebalanceShards", @@ -47520,6 +50662,12 @@ "to": "function:neuroforge/internal/brain:Engine.ingestText", "kind": "calls" }, + { + "id": "function:neuroforge/internal/brain:Engine.Research-\u003efunction:neuroforge/internal/brain:Engine.stagingConfig:calls", + "from": "function:neuroforge/internal/brain:Engine.Research", + "to": "function:neuroforge/internal/brain:Engine.stagingConfig", + "kind": "calls" + }, { "id": "function:neuroforge/internal/brain:Engine.Research-\u003efunction:neuroforge/internal/brain:New:calls", "from": "function:neuroforge/internal/brain:Engine.Research", @@ -47538,6 +50686,18 @@ "to": "function:neuroforge/internal/brain:firstNonEmpty", "kind": "calls" }, + { + "id": "function:neuroforge/internal/brain:Engine.Research-\u003efunction:neuroforge/internal/brain:researchMaterialRelevant:calls", + "from": "function:neuroforge/internal/brain:Engine.Research", + "to": "function:neuroforge/internal/brain:researchMaterialRelevant", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.Research-\u003efunction:neuroforge/internal/brain:researchResultsContainRelevant:calls", + "from": "function:neuroforge/internal/brain:Engine.Research", + "to": "function:neuroforge/internal/brain:researchResultsContainRelevant", + "kind": "calls" + }, { "id": "function:neuroforge/internal/brain:Engine.Research-\u003efunction:neuroforge/internal/brain:researchTrace.emit:calls", "from": "function:neuroforge/internal/brain:Engine.Research", @@ -47550,6 +50710,12 @@ "to": "function:neuroforge/internal/brain:shortPreview", "kind": "calls" }, + { + "id": "function:neuroforge/internal/brain:Engine.Research-\u003efunction:neuroforge/internal/brain:sourceAuthorityFor:calls", + "from": "function:neuroforge/internal/brain:Engine.Research", + "to": "function:neuroforge/internal/brain:sourceAuthorityFor", + "kind": "calls" + }, { "id": "function:neuroforge/internal/brain:Engine.Research-\u003epackage:fmt:calls_package", "from": "function:neuroforge/internal/brain:Engine.Research", @@ -47564,6 +50730,13 @@ "kind": "calls_package", "label": "ResultLooksLikeDocument" }, + { + "id": "function:neuroforge/internal/brain:Engine.Research-\u003epackage:sort:calls_package", + "from": "function:neuroforge/internal/brain:Engine.Research", + "to": "package:sort", + "kind": "calls_package", + "label": "SliceStable" + }, { "id": "function:neuroforge/internal/brain:Engine.Research-\u003epackage:strings:calls_package", "from": "function:neuroforge/internal/brain:Engine.Research", @@ -47790,6 +50963,25 @@ "kind": "calls_package", "label": "NewTicker" }, + { + "id": "function:neuroforge/internal/brain:Engine.RunOrchestrator-\u003efunction:neuroforge/internal/brain:Engine.ApplyPendingJobResults:calls", + "from": "function:neuroforge/internal/brain:Engine.RunOrchestrator", + "to": "function:neuroforge/internal/brain:Engine.ApplyPendingJobResults", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.RunOrchestrator-\u003efunction:neuroforge/internal/brain:Engine.PlanGraphBackfill:calls", + "from": "function:neuroforge/internal/brain:Engine.RunOrchestrator", + "to": "function:neuroforge/internal/brain:Engine.PlanGraphBackfill", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.RunOrchestrator-\u003epackage:time:calls_package", + "from": "function:neuroforge/internal/brain:Engine.RunOrchestrator", + "to": "package:time", + "kind": "calls_package", + "label": "NewTimer" + }, { "id": "function:neuroforge/internal/brain:Engine.RunV3Maintenance-\u003efunction:neuroforge/internal/brain:Engine.Consolidate:calls", "from": "function:neuroforge/internal/brain:Engine.RunV3Maintenance", @@ -48002,12 +51194,30 @@ "to": "function:neuroforge/internal/brain:Engine.chatModelLimit", "kind": "calls" }, + { + "id": "function:neuroforge/internal/brain:Engine.chatModelJSONLimitOn-\u003efunction:neuroforge/internal/brain:Engine.chatModelLimitOnMode:calls", + "from": "function:neuroforge/internal/brain:Engine.chatModelJSONLimitOn", + "to": "function:neuroforge/internal/brain:Engine.chatModelLimitOnMode", + "kind": "calls" + }, { "id": "function:neuroforge/internal/brain:Engine.chatModelLimit-\u003efunction:neuroforge/internal/brain:Engine.chatModelLimitOn:calls", "from": "function:neuroforge/internal/brain:Engine.chatModelLimit", "to": "function:neuroforge/internal/brain:Engine.chatModelLimitOn", "kind": "calls" }, + { + "id": "function:neuroforge/internal/brain:Engine.chatModelLimitOn-\u003efunction:neuroforge/internal/brain:Engine.chatModelLimitOnMode:calls", + "from": "function:neuroforge/internal/brain:Engine.chatModelLimitOn", + "to": "function:neuroforge/internal/brain:Engine.chatModelLimitOnMode", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.chatModelLimitOnMode-\u003efunction:neuroforge/internal/brain:Engine.distributedChat:calls", + "from": "function:neuroforge/internal/brain:Engine.chatModelLimitOnMode", + "to": "function:neuroforge/internal/brain:Engine.distributedChat", + "kind": "calls" + }, { "id": "function:neuroforge/internal/brain:Engine.clusterPost-\u003epackage:bytes:calls_package", "from": "function:neuroforge/internal/brain:Engine.clusterPost", @@ -48064,6 +51274,90 @@ "kind": "calls_package", "label": "Duration" }, + { + "id": "function:neuroforge/internal/brain:Engine.collectGoalDraftEvidence-\u003efunction:neuroforge/internal/brain:goalEvidenceRelevant:calls", + "from": "function:neuroforge/internal/brain:Engine.collectGoalDraftEvidence", + "to": "function:neuroforge/internal/brain:goalEvidenceRelevant", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.collectGoalDraftEvidence-\u003efunction:neuroforge/internal/brain:memoryBelongsToGoal:calls", + "from": "function:neuroforge/internal/brain:Engine.collectGoalDraftEvidence", + "to": "function:neuroforge/internal/brain:memoryBelongsToGoal", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.collectGoalDraftEvidence-\u003efunction:neuroforge/internal/brain:sortDraftEvidenceByAuthority:calls", + "from": "function:neuroforge/internal/brain:Engine.collectGoalDraftEvidence", + "to": "function:neuroforge/internal/brain:sortDraftEvidenceByAuthority", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.collectGoalDraftEvidence-\u003efunction:neuroforge/internal/brain:sourceAuthorityFor:calls", + "from": "function:neuroforge/internal/brain:Engine.collectGoalDraftEvidence", + "to": "function:neuroforge/internal/brain:sourceAuthorityFor", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.collectGoalDraftEvidence-\u003epackage:sort:calls_package", + "from": "function:neuroforge/internal/brain:Engine.collectGoalDraftEvidence", + "to": "package:sort", + "kind": "calls_package", + "label": "SliceStable" + }, + { + "id": "function:neuroforge/internal/brain:Engine.collectGoalDraftEvidence-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:Engine.collectGoalDraftEvidence", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, + { + "id": "function:neuroforge/internal/brain:Engine.distributedChat-\u003efunction:neuroforge/internal/brain:Engine.waitDistributedJob:calls", + "from": "function:neuroforge/internal/brain:Engine.distributedChat", + "to": "function:neuroforge/internal/brain:Engine.waitDistributedJob", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.distributedChat-\u003epackage:context:calls_package", + "from": "function:neuroforge/internal/brain:Engine.distributedChat", + "to": "package:context", + "kind": "calls_package", + "label": "WithTimeout" + }, + { + "id": "function:neuroforge/internal/brain:Engine.distributedChat-\u003epackage:time:calls_package", + "from": "function:neuroforge/internal/brain:Engine.distributedChat", + "to": "package:time", + "kind": "calls_package", + "label": "Duration" + }, + { + "id": "function:neuroforge/internal/brain:Engine.distributedEmbed-\u003efunction:neuroforge/internal/brain:Engine.waitDistributedJob:calls", + "from": "function:neuroforge/internal/brain:Engine.distributedEmbed", + "to": "function:neuroforge/internal/brain:Engine.waitDistributedJob", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.distributedEmbed-\u003efunction:neuroforge/internal/brain:New:calls", + "from": "function:neuroforge/internal/brain:Engine.distributedEmbed", + "to": "function:neuroforge/internal/brain:New", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.distributedEmbed-\u003epackage:context:calls_package", + "from": "function:neuroforge/internal/brain:Engine.distributedEmbed", + "to": "package:context", + "kind": "calls_package", + "label": "WithTimeout" + }, + { + "id": "function:neuroforge/internal/brain:Engine.distributedEmbed-\u003epackage:time:calls_package", + "from": "function:neuroforge/internal/brain:Engine.distributedEmbed", + "to": "package:time", + "kind": "calls_package", + "label": "Duration" + }, { "id": "function:neuroforge/internal/brain:Engine.duplicateMemory-\u003efunction:neuroforge/internal/brain:Engine.SearchVector:calls", "from": "function:neuroforge/internal/brain:Engine.duplicateMemory", @@ -48083,6 +51377,55 @@ "kind": "calls_package", "label": "Now" }, + { + "id": "function:neuroforge/internal/brain:Engine.embed-\u003efunction:neuroforge/internal/brain:Engine.distributedEmbed:calls", + "from": "function:neuroforge/internal/brain:Engine.embed", + "to": "function:neuroforge/internal/brain:Engine.distributedEmbed", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.enqueueRelink-\u003efunction:neuroforge/internal/brain:Engine.SearchVector:calls", + "from": "function:neuroforge/internal/brain:Engine.enqueueRelink", + "to": "function:neuroforge/internal/brain:Engine.SearchVector", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.enqueueRelink-\u003efunction:neuroforge/internal/brain:New:calls", + "from": "function:neuroforge/internal/brain:Engine.enqueueRelink", + "to": "function:neuroforge/internal/brain:New", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.enqueueRelink-\u003efunction:neuroforge/internal/brain:vectorFingerprint:calls", + "from": "function:neuroforge/internal/brain:Engine.enqueueRelink", + "to": "function:neuroforge/internal/brain:vectorFingerprint", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.enqueueRelink-\u003epackage:strconv:calls_package", + "from": "function:neuroforge/internal/brain:Engine.enqueueRelink", + "to": "package:strconv", + "kind": "calls_package", + "label": "FormatInt" + }, + { + "id": "function:neuroforge/internal/brain:Engine.ensureGoalDraftArticleDepth-\u003efunction:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle:calls", + "from": "function:neuroforge/internal/brain:Engine.ensureGoalDraftArticleDepth", + "to": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.ensureGoalDraftArticleDepth-\u003efunction:neuroforge/internal/brain:Engine.stagingConfig:calls", + "from": "function:neuroforge/internal/brain:Engine.ensureGoalDraftArticleDepth", + "to": "function:neuroforge/internal/brain:Engine.stagingConfig", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.ensureGoalDraftArticleDepth-\u003efunction:neuroforge/internal/brain:validateDraftArticleBounds:calls", + "from": "function:neuroforge/internal/brain:Engine.ensureGoalDraftArticleDepth", + "to": "function:neuroforge/internal/brain:validateDraftArticleBounds", + "kind": "calls" + }, { "id": "function:neuroforge/internal/brain:Engine.evaluateReward-\u003efunction:neuroforge/internal/brain:Engine.chatModelLimitOn:calls", "from": "function:neuroforge/internal/brain:Engine.evaluateReward", @@ -48146,6 +51489,12 @@ "to": "function:neuroforge/internal/brain:dedupeStrings", "kind": "calls" }, + { + "id": "function:neuroforge/internal/brain:Engine.goalResearchQueries-\u003efunction:neuroforge/internal/brain:deterministicResearchQueries:calls", + "from": "function:neuroforge/internal/brain:Engine.goalResearchQueries", + "to": "function:neuroforge/internal/brain:deterministicResearchQueries", + "kind": "calls" + }, { "id": "function:neuroforge/internal/brain:Engine.goalResearchQueries-\u003efunction:neuroforge/internal/brain:researchQueryUseful:calls", "from": "function:neuroforge/internal/brain:Engine.goalResearchQueries", @@ -48297,6 +51646,12 @@ "to": "function:neuroforge/internal/brain:claimPreview", "kind": "calls" }, + { + "id": "function:neuroforge/internal/brain:Engine.ingestSourceText-\u003efunction:neuroforge/internal/brain:goalIDFromTags:calls", + "from": "function:neuroforge/internal/brain:Engine.ingestSourceText", + "to": "function:neuroforge/internal/brain:goalIDFromTags", + "kind": "calls" + }, { "id": "function:neuroforge/internal/brain:Engine.ingestSourceText-\u003efunction:neuroforge/internal/brain:hashText:calls", "from": "function:neuroforge/internal/brain:Engine.ingestSourceText", @@ -48422,12 +51777,36 @@ "to": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", "kind": "calls" }, + { + "id": "function:neuroforge/internal/brain:Engine.maybePublishGoalDraft-\u003efunction:neuroforge/internal/brain:countDraftIndependentCorroborations:calls", + "from": "function:neuroforge/internal/brain:Engine.maybePublishGoalDraft", + "to": "function:neuroforge/internal/brain:countDraftIndependentCorroborations", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.maybePublishGoalDraft-\u003efunction:neuroforge/internal/brain:deterministicStagingFailure:calls", + "from": "function:neuroforge/internal/brain:Engine.maybePublishGoalDraft", + "to": "function:neuroforge/internal/brain:deterministicStagingFailure", + "kind": "calls" + }, { "id": "function:neuroforge/internal/brain:Engine.maybePublishGoalDraft-\u003efunction:neuroforge/internal/brain:firstNonEmpty:calls", "from": "function:neuroforge/internal/brain:Engine.maybePublishGoalDraft", "to": "function:neuroforge/internal/brain:firstNonEmpty", "kind": "calls" }, + { + "id": "function:neuroforge/internal/brain:Engine.maybePublishGoalDraft-\u003efunction:neuroforge/internal/brain:stagingEvidenceSignature:calls", + "from": "function:neuroforge/internal/brain:Engine.maybePublishGoalDraft", + "to": "function:neuroforge/internal/brain:stagingEvidenceSignature", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.maybePublishGoalDraft-\u003efunction:neuroforge/internal/brain:summarizeEvidenceAuthority:calls", + "from": "function:neuroforge/internal/brain:Engine.maybePublishGoalDraft", + "to": "function:neuroforge/internal/brain:summarizeEvidenceAuthority", + "kind": "calls" + }, { "id": "function:neuroforge/internal/brain:Engine.maybePublishGoalDraft-\u003epackage:bytes:calls_package", "from": "function:neuroforge/internal/brain:Engine.maybePublishGoalDraft", @@ -48536,6 +51915,24 @@ "kind": "calls_package", "label": "Now" }, + { + "id": "function:neuroforge/internal/brain:Engine.refreshGoalResearchProgress-\u003efunction:neuroforge/internal/brain:goalEvidenceRelevant:calls", + "from": "function:neuroforge/internal/brain:Engine.refreshGoalResearchProgress", + "to": "function:neuroforge/internal/brain:goalEvidenceRelevant", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.refreshGoalResearchProgress-\u003efunction:neuroforge/internal/brain:memoryBelongsToGoal:calls", + "from": "function:neuroforge/internal/brain:Engine.refreshGoalResearchProgress", + "to": "function:neuroforge/internal/brain:memoryBelongsToGoal", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.refreshGoalResearchProgress-\u003efunction:neuroforge/internal/brain:sourceOriginKey:calls", + "from": "function:neuroforge/internal/brain:Engine.refreshGoalResearchProgress", + "to": "function:neuroforge/internal/brain:sourceOriginKey", + "kind": "calls" + }, { "id": "function:neuroforge/internal/brain:Engine.refreshGoalResearchProgress-\u003epackage:fmt:calls_package", "from": "function:neuroforge/internal/brain:Engine.refreshGoalResearchProgress", @@ -48562,7 +51959,7 @@ "from": "function:neuroforge/internal/brain:Engine.refreshGoalResearchProgress", "to": "package:strings", "kind": "calls_package", - "label": "TrimSpace" + "label": "ToLower" }, { "id": "function:neuroforge/internal/brain:Engine.remoteVectorSearch-\u003efunction:neuroforge/internal/brain:New:calls", @@ -48626,6 +52023,81 @@ "kind": "calls_package", "label": "Duration" }, + { + "id": "function:neuroforge/internal/brain:Engine.repairDraftGrounding-\u003efunction:neuroforge/internal/brain:Engine.chatModelJSONLimitOn:calls", + "from": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", + "to": "function:neuroforge/internal/brain:Engine.chatModelJSONLimitOn", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.repairDraftGrounding-\u003efunction:neuroforge/internal/brain:Engine.stagingConfig:calls", + "from": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", + "to": "function:neuroforge/internal/brain:Engine.stagingConfig", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.repairDraftGrounding-\u003efunction:neuroforge/internal/brain:New:calls", + "from": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", + "to": "function:neuroforge/internal/brain:New", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.repairDraftGrounding-\u003efunction:neuroforge/internal/brain:decodeStagingSynthesisJSON:calls", + "from": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", + "to": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.repairDraftGrounding-\u003efunction:neuroforge/internal/brain:evidencePackForPrompt:calls", + "from": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", + "to": "function:neuroforge/internal/brain:evidencePackForPrompt", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.repairDraftGrounding-\u003efunction:neuroforge/internal/brain:goalKeywords:calls", + "from": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", + "to": "function:neuroforge/internal/brain:goalKeywords", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.repairDraftGrounding-\u003efunction:neuroforge/internal/brain:researchMaterialRelevant:calls", + "from": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", + "to": "function:neuroforge/internal/brain:researchMaterialRelevant", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.repairDraftGrounding-\u003efunction:neuroforge/internal/brain:roleRoute:calls", + "from": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", + "to": "function:neuroforge/internal/brain:roleRoute", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.repairDraftGrounding-\u003efunction:neuroforge/internal/brain:validateDraftCriticalIdentifiers:calls", + "from": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", + "to": "function:neuroforge/internal/brain:validateDraftCriticalIdentifiers", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.repairDraftGrounding-\u003epackage:encoding/json:calls_package", + "from": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", + "to": "package:encoding/json", + "kind": "calls_package", + "label": "Marshal" + }, + { + "id": "function:neuroforge/internal/brain:Engine.repairDraftGrounding-\u003epackage:fmt:calls_package", + "from": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", + "to": "package:fmt", + "kind": "calls_package", + "label": "Sprintf" + }, + { + "id": "function:neuroforge/internal/brain:Engine.repairDraftGrounding-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, { "id": "function:neuroforge/internal/brain:Engine.replicateMemory-\u003epackage:bytes:calls_package", "from": "function:neuroforge/internal/brain:Engine.replicateMemory", @@ -48781,6 +52253,81 @@ "to": "function:neuroforge/internal/brain:electionTimeout", "kind": "calls" }, + { + "id": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle-\u003efunction:neuroforge/internal/brain:Engine.chatModelJSONLimitOn:calls", + "from": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", + "to": "function:neuroforge/internal/brain:Engine.chatModelJSONLimitOn", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle-\u003efunction:neuroforge/internal/brain:Engine.stagingConfig:calls", + "from": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", + "to": "function:neuroforge/internal/brain:Engine.stagingConfig", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle-\u003efunction:neuroforge/internal/brain:New:calls", + "from": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", + "to": "function:neuroforge/internal/brain:New", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle-\u003efunction:neuroforge/internal/brain:decodeStagingSynthesisJSON:calls", + "from": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", + "to": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle-\u003efunction:neuroforge/internal/brain:evidencePackForPrompt:calls", + "from": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", + "to": "function:neuroforge/internal/brain:evidencePackForPrompt", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle-\u003efunction:neuroforge/internal/brain:goalKeywords:calls", + "from": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", + "to": "function:neuroforge/internal/brain:goalKeywords", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle-\u003efunction:neuroforge/internal/brain:researchMaterialRelevant:calls", + "from": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", + "to": "function:neuroforge/internal/brain:researchMaterialRelevant", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle-\u003efunction:neuroforge/internal/brain:roleRoute:calls", + "from": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", + "to": "function:neuroforge/internal/brain:roleRoute", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle-\u003efunction:neuroforge/internal/brain:validateDraftArticleBounds:calls", + "from": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", + "to": "function:neuroforge/internal/brain:validateDraftArticleBounds", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle-\u003epackage:encoding/json:calls_package", + "from": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", + "to": "package:encoding/json", + "kind": "calls_package", + "label": "Marshal" + }, + { + "id": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle-\u003epackage:fmt:calls_package", + "from": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", + "to": "package:fmt", + "kind": "calls_package", + "label": "Sprintf" + }, + { + "id": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:Engine.reshapeGoalDraftArticle", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, { "id": "function:neuroforge/internal/brain:Engine.searchVectorFederated-\u003efunction:neuroforge/internal/brain:Engine.SearchVector:calls", "from": "function:neuroforge/internal/brain:Engine.searchVectorFederated", @@ -48839,9 +52386,33 @@ "label": "TrimSpace" }, { - "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:Engine.chatModelLimitOn:calls", + "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:Engine.chatModelJSONLimitOn:calls", "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", - "to": "function:neuroforge/internal/brain:Engine.chatModelLimitOn", + "to": "function:neuroforge/internal/brain:Engine.chatModelJSONLimitOn", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:Engine.ensureGoalDraftArticleDepth:calls", + "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", + "to": "function:neuroforge/internal/brain:Engine.ensureGoalDraftArticleDepth", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:Engine.repairDraftGrounding:calls", + "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", + "to": "function:neuroforge/internal/brain:Engine.repairDraftGrounding", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:Engine.stagingConfig:calls", + "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", + "to": "function:neuroforge/internal/brain:Engine.stagingConfig", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:Engine.verifyDraftClaims:calls", + "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", + "to": "function:neuroforge/internal/brain:Engine.verifyDraftClaims", "kind": "calls" }, { @@ -48850,18 +52421,42 @@ "to": "function:neuroforge/internal/brain:New", "kind": "calls" }, + { + "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:articleQualityForDraft:calls", + "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", + "to": "function:neuroforge/internal/brain:articleQualityForDraft", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:decodeStagingSynthesisJSON:calls", + "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", + "to": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON", + "kind": "calls" + }, { "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:deterministicDraftAnswer:calls", "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", "to": "function:neuroforge/internal/brain:deterministicDraftAnswer", "kind": "calls" }, + { + "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:evidencePackForPrompt:calls", + "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", + "to": "function:neuroforge/internal/brain:evidencePackForPrompt", + "kind": "calls" + }, { "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:goalKeywords:calls", "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", "to": "function:neuroforge/internal/brain:goalKeywords", "kind": "calls" }, + { + "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:researchMaterialRelevant:calls", + "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", + "to": "function:neuroforge/internal/brain:researchMaterialRelevant", + "kind": "calls" + }, { "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:roleRoute:calls", "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", @@ -48869,18 +52464,23 @@ "kind": "calls" }, { - "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003epackage:encoding/json:calls_package", + "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:summarizeEvidenceAuthority:calls", "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", - "to": "package:encoding/json", - "kind": "calls_package", - "label": "Unmarshal" + "to": "function:neuroforge/internal/brain:summarizeEvidenceAuthority", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003efunction:neuroforge/internal/brain:validateDraftCriticalIdentifiers:calls", + "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", + "to": "function:neuroforge/internal/brain:validateDraftCriticalIdentifiers", + "kind": "calls" }, { "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003epackage:fmt:calls_package", "from": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft", "to": "package:fmt", "kind": "calls_package", - "label": "Fprintf" + "label": "Errorf" }, { "id": "function:neuroforge/internal/brain:Engine.synthesizeGoalDraft-\u003epackage:strings:calls_package", @@ -48889,6 +52489,95 @@ "kind": "calls_package", "label": "TrimSpace" }, + { + "id": "function:neuroforge/internal/brain:Engine.verifyDraftClaims-\u003efunction:neuroforge/internal/brain:Engine.chatModelJSONLimitOn:calls", + "from": "function:neuroforge/internal/brain:Engine.verifyDraftClaims", + "to": "function:neuroforge/internal/brain:Engine.chatModelJSONLimitOn", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.verifyDraftClaims-\u003efunction:neuroforge/internal/brain:Engine.stagingConfig:calls", + "from": "function:neuroforge/internal/brain:Engine.verifyDraftClaims", + "to": "function:neuroforge/internal/brain:Engine.stagingConfig", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.verifyDraftClaims-\u003efunction:neuroforge/internal/brain:New:calls", + "from": "function:neuroforge/internal/brain:Engine.verifyDraftClaims", + "to": "function:neuroforge/internal/brain:New", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.verifyDraftClaims-\u003efunction:neuroforge/internal/brain:decodeVerifierJSON:calls", + "from": "function:neuroforge/internal/brain:Engine.verifyDraftClaims", + "to": "function:neuroforge/internal/brain:decodeVerifierJSON", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.verifyDraftClaims-\u003efunction:neuroforge/internal/brain:evidencePackForPrompt:calls", + "from": "function:neuroforge/internal/brain:Engine.verifyDraftClaims", + "to": "function:neuroforge/internal/brain:evidencePackForPrompt", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.verifyDraftClaims-\u003efunction:neuroforge/internal/brain:extractDraftStatements:calls", + "from": "function:neuroforge/internal/brain:Engine.verifyDraftClaims", + "to": "function:neuroforge/internal/brain:extractDraftStatements", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.verifyDraftClaims-\u003efunction:neuroforge/internal/brain:roleRoute:calls", + "from": "function:neuroforge/internal/brain:Engine.verifyDraftClaims", + "to": "function:neuroforge/internal/brain:roleRoute", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.verifyDraftClaims-\u003efunction:neuroforge/internal/brain:sourceAuditForEvidence:calls", + "from": "function:neuroforge/internal/brain:Engine.verifyDraftClaims", + "to": "function:neuroforge/internal/brain:sourceAuditForEvidence", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.verifyDraftClaims-\u003epackage:fmt:calls_package", + "from": "function:neuroforge/internal/brain:Engine.verifyDraftClaims", + "to": "package:fmt", + "kind": "calls_package", + "label": "Errorf" + }, + { + "id": "function:neuroforge/internal/brain:Engine.verifyDraftClaims-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:Engine.verifyDraftClaims", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, + { + "id": "function:neuroforge/internal/brain:Engine.waitDistributedJob-\u003efunction:neuroforge/internal/brain:New:calls", + "from": "function:neuroforge/internal/brain:Engine.waitDistributedJob", + "to": "function:neuroforge/internal/brain:New", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:Engine.waitDistributedJob-\u003epackage:encoding/json:calls_package", + "from": "function:neuroforge/internal/brain:Engine.waitDistributedJob", + "to": "package:encoding/json", + "kind": "calls_package", + "label": "Unmarshal" + }, + { + "id": "function:neuroforge/internal/brain:Engine.waitDistributedJob-\u003epackage:fmt:calls_package", + "from": "function:neuroforge/internal/brain:Engine.waitDistributedJob", + "to": "package:fmt", + "kind": "calls_package", + "label": "Errorf" + }, + { + "id": "function:neuroforge/internal/brain:Engine.waitDistributedJob-\u003epackage:time:calls_package", + "from": "function:neuroforge/internal/brain:Engine.waitDistributedJob", + "to": "package:time", + "kind": "calls_package", + "label": "NewTicker" + }, { "id": "function:neuroforge/internal/brain:ResearchDomain-\u003epackage:net/url:calls_package", "from": "function:neuroforge/internal/brain:ResearchDomain", @@ -48910,6 +52599,26 @@ "kind": "calls_package", "label": "Slice" }, + { + "id": "function:neuroforge/internal/brain:allDigits-\u003epackage:unicode:calls_package", + "from": "function:neuroforge/internal/brain:allDigits", + "to": "package:unicode", + "kind": "calls_package", + "label": "IsDigit" + }, + { + "id": "function:neuroforge/internal/brain:articleQualityForDraft-\u003efunction:neuroforge/internal/brain:articleRuneCount:calls", + "from": "function:neuroforge/internal/brain:articleQualityForDraft", + "to": "function:neuroforge/internal/brain:articleRuneCount", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:articleRuneCount-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:articleRuneCount", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, { "id": "function:neuroforge/internal/brain:buildContext-\u003epackage:fmt:calls_package", "from": "function:neuroforge/internal/brain:buildContext", @@ -48937,6 +52646,89 @@ "kind": "calls_package", "label": "Join" }, + { + "id": "function:neuroforge/internal/brain:countDraftIndependentCorroborations-\u003efunction:neuroforge/internal/brain:sourceOriginKey:calls", + "from": "function:neuroforge/internal/brain:countDraftIndependentCorroborations", + "to": "function:neuroforge/internal/brain:sourceOriginKey", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:criticalIdentifiers-\u003efunction:neuroforge/internal/brain:slashSwitchIdentifiers:calls", + "from": "function:neuroforge/internal/brain:criticalIdentifiers", + "to": "function:neuroforge/internal/brain:slashSwitchIdentifiers", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:criticalIdentifiers-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:criticalIdentifiers", + "to": "package:strings", + "kind": "calls_package", + "label": "ToLower" + }, + { + "id": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON-\u003efunction:neuroforge/internal/brain:New:calls", + "from": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON", + "to": "function:neuroforge/internal/brain:New", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON-\u003efunction:neuroforge/internal/brain:repairInvalidJSONStringEscapes:calls", + "from": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON", + "to": "function:neuroforge/internal/brain:repairInvalidJSONStringEscapes", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON-\u003efunction:neuroforge/internal/brain:strictUnmarshalJSONObject:calls", + "from": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON", + "to": "function:neuroforge/internal/brain:strictUnmarshalJSONObject", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON-\u003epackage:fmt:calls_package", + "from": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON", + "to": "package:fmt", + "kind": "calls_package", + "label": "Errorf" + }, + { + "id": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:decodeStagingSynthesisJSON", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, + { + "id": "function:neuroforge/internal/brain:decodeVerifierJSON-\u003efunction:neuroforge/internal/brain:New:calls", + "from": "function:neuroforge/internal/brain:decodeVerifierJSON", + "to": "function:neuroforge/internal/brain:New", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:decodeVerifierJSON-\u003efunction:neuroforge/internal/brain:repairInvalidJSONStringEscapes:calls", + "from": "function:neuroforge/internal/brain:decodeVerifierJSON", + "to": "function:neuroforge/internal/brain:repairInvalidJSONStringEscapes", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:decodeVerifierJSON-\u003efunction:neuroforge/internal/brain:strictUnmarshalJSONObject:calls", + "from": "function:neuroforge/internal/brain:decodeVerifierJSON", + "to": "function:neuroforge/internal/brain:strictUnmarshalJSONObject", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:decodeVerifierJSON-\u003epackage:fmt:calls_package", + "from": "function:neuroforge/internal/brain:decodeVerifierJSON", + "to": "package:fmt", + "kind": "calls_package", + "label": "Errorf" + }, + { + "id": "function:neuroforge/internal/brain:decodeVerifierJSON-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:decodeVerifierJSON", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, { "id": "function:neuroforge/internal/brain:dedupeStrings-\u003epackage:strings:calls_package", "from": "function:neuroforge/internal/brain:dedupeStrings", @@ -48965,6 +52757,45 @@ "kind": "calls_package", "label": "Sprintf" }, + { + "id": "function:neuroforge/internal/brain:deterministicResearchQueries-\u003efunction:neuroforge/internal/brain:dedupeStrings:calls", + "from": "function:neuroforge/internal/brain:deterministicResearchQueries", + "to": "function:neuroforge/internal/brain:dedupeStrings", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:deterministicResearchQueries-\u003efunction:neuroforge/internal/brain:goalPreferredAuthorityDomains:calls", + "from": "function:neuroforge/internal/brain:deterministicResearchQueries", + "to": "function:neuroforge/internal/brain:goalPreferredAuthorityDomains", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:deterministicResearchQueries-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:deterministicResearchQueries", + "to": "package:strings", + "kind": "calls_package", + "label": "Join" + }, + { + "id": "function:neuroforge/internal/brain:deterministicStagingFailure-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:deterministicStagingFailure", + "to": "package:strings", + "kind": "calls_package", + "label": "ToLower" + }, + { + "id": "function:neuroforge/internal/brain:domainMatches-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:domainMatches", + "to": "package:strings", + "kind": "calls_package", + "label": "ToLower" + }, + { + "id": "function:neuroforge/internal/brain:draftEvidenceAuthorityScore-\u003efunction:neuroforge/internal/brain:sourceAuthorityFor:calls", + "from": "function:neuroforge/internal/brain:draftEvidenceAuthorityScore", + "to": "function:neuroforge/internal/brain:sourceAuthorityFor", + "kind": "calls" + }, { "id": "function:neuroforge/internal/brain:due-\u003epackage:time:calls_package", "from": "function:neuroforge/internal/brain:due", @@ -49007,6 +52838,51 @@ "kind": "calls_package", "label": "Clamp" }, + { + "id": "function:neuroforge/internal/brain:evidencePackForPrompt-\u003efunction:neuroforge/internal/brain:promptEvidenceTexts:calls", + "from": "function:neuroforge/internal/brain:evidencePackForPrompt", + "to": "function:neuroforge/internal/brain:promptEvidenceTexts", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:evidencePackForPrompt-\u003efunction:neuroforge/internal/brain:sourceAuditForEvidence:calls", + "from": "function:neuroforge/internal/brain:evidencePackForPrompt", + "to": "function:neuroforge/internal/brain:sourceAuditForEvidence", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:evidencePackForPrompt-\u003epackage:fmt:calls_package", + "from": "function:neuroforge/internal/brain:evidencePackForPrompt", + "to": "package:fmt", + "kind": "calls_package", + "label": "Sprintf" + }, + { + "id": "function:neuroforge/internal/brain:extractDraftStatements-\u003efunction:neuroforge/internal/brain:isActionableDraftStatement:calls", + "from": "function:neuroforge/internal/brain:extractDraftStatements", + "to": "function:neuroforge/internal/brain:isActionableDraftStatement", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:extractDraftStatements-\u003efunction:neuroforge/internal/brain:normalizeStatementText:calls", + "from": "function:neuroforge/internal/brain:extractDraftStatements", + "to": "function:neuroforge/internal/brain:normalizeStatementText", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:extractDraftStatements-\u003epackage:strconv:calls_package", + "from": "function:neuroforge/internal/brain:extractDraftStatements", + "to": "package:strconv", + "kind": "calls_package", + "label": "Itoa" + }, + { + "id": "function:neuroforge/internal/brain:extractDraftStatements-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:extractDraftStatements", + "to": "package:strings", + "kind": "calls_package", + "label": "ToLower" + }, { "id": "function:neuroforge/internal/brain:firstNonEmpty-\u003epackage:strings:calls_package", "from": "function:neuroforge/internal/brain:firstNonEmpty", @@ -49014,6 +52890,47 @@ "kind": "calls_package", "label": "TrimSpace" }, + { + "id": "function:neuroforge/internal/brain:goalAnchorTokens-\u003epackage:sort:calls_package", + "from": "function:neuroforge/internal/brain:goalAnchorTokens", + "to": "package:sort", + "kind": "calls_package", + "label": "SliceStable" + }, + { + "id": "function:neuroforge/internal/brain:goalAnchorTokens-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:goalAnchorTokens", + "to": "package:strings", + "kind": "calls_package", + "label": "Map" + }, + { + "id": "function:neuroforge/internal/brain:goalAnchorTokens-\u003epackage:unicode:calls_package", + "from": "function:neuroforge/internal/brain:goalAnchorTokens", + "to": "package:unicode", + "kind": "calls_package", + "label": "IsLetter" + }, + { + "id": "function:neuroforge/internal/brain:goalEvidenceRelevant-\u003efunction:neuroforge/internal/brain:researchMaterialRelevant:calls", + "from": "function:neuroforge/internal/brain:goalEvidenceRelevant", + "to": "function:neuroforge/internal/brain:researchMaterialRelevant", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:goalEvidenceRelevant-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:goalEvidenceRelevant", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, + { + "id": "function:neuroforge/internal/brain:goalIDFromTags-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:goalIDFromTags", + "to": "package:strings", + "kind": "calls_package", + "label": "HasPrefix" + }, { "id": "function:neuroforge/internal/brain:goalKeywords-\u003epackage:sort:calls_package", "from": "function:neuroforge/internal/brain:goalKeywords", @@ -49028,6 +52945,18 @@ "kind": "calls_package", "label": "Fields" }, + { + "id": "function:neuroforge/internal/brain:goalPreferredAuthorityDomains-\u003efunction:neuroforge/internal/brain:dedupeStrings:calls", + "from": "function:neuroforge/internal/brain:goalPreferredAuthorityDomains", + "to": "function:neuroforge/internal/brain:dedupeStrings", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:goalPreferredAuthorityDomains-\u003efunction:neuroforge/internal/brain:normalizedResearchWords:calls", + "from": "function:neuroforge/internal/brain:goalPreferredAuthorityDomains", + "to": "function:neuroforge/internal/brain:normalizedResearchWords", + "kind": "calls" + }, { "id": "function:neuroforge/internal/brain:hashText-\u003epackage:crypto/sha256:calls_package", "from": "function:neuroforge/internal/brain:hashText", @@ -49042,6 +52971,20 @@ "kind": "calls_package", "label": "EncodeToString" }, + { + "id": "function:neuroforge/internal/brain:isActionableDraftStatement-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:isActionableDraftStatement", + "to": "package:strings", + "kind": "calls_package", + "label": "ToLower" + }, + { + "id": "function:neuroforge/internal/brain:memoryBelongsToGoal-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:memoryBelongsToGoal", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, { "id": "function:neuroforge/internal/brain:memoryTypeForKind-\u003epackage:strings:calls_package", "from": "function:neuroforge/internal/brain:memoryTypeForKind", @@ -49049,6 +52992,34 @@ "kind": "calls_package", "label": "ToLower" }, + { + "id": "function:neuroforge/internal/brain:normalizeAuthoritativeDomain-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:normalizeAuthoritativeDomain", + "to": "package:strings", + "kind": "calls_package", + "label": "ToLower" + }, + { + "id": "function:neuroforge/internal/brain:normalizeStatementText-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:normalizeStatementText", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, + { + "id": "function:neuroforge/internal/brain:normalizedResearchWords-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:normalizedResearchWords", + "to": "package:strings", + "kind": "calls_package", + "label": "Map" + }, + { + "id": "function:neuroforge/internal/brain:normalizedResearchWords-\u003epackage:unicode:calls_package", + "from": "function:neuroforge/internal/brain:normalizedResearchWords", + "to": "package:unicode", + "kind": "calls_package", + "label": "IsLetter" + }, { "id": "function:neuroforge/internal/brain:parsePrediction-\u003epackage:strings:calls_package", "from": "function:neuroforge/internal/brain:parsePrediction", @@ -49090,6 +53061,13 @@ "kind": "calls_package", "label": "Clamp" }, + { + "id": "function:neuroforge/internal/brain:promptEvidenceTexts-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:promptEvidenceTexts", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, { "id": "function:neuroforge/internal/brain:rendezvousScore-\u003epackage:hash/fnv:calls_package", "from": "function:neuroforge/internal/brain:rendezvousScore", @@ -49110,6 +53088,30 @@ "to": "function:neuroforge/internal/brain:rendezvousScore", "kind": "calls" }, + { + "id": "function:neuroforge/internal/brain:repairInvalidJSONStringEscapes-\u003efunction:neuroforge/internal/brain:isJSONHex4:calls", + "from": "function:neuroforge/internal/brain:repairInvalidJSONStringEscapes", + "to": "function:neuroforge/internal/brain:isJSONHex4", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:researchMaterialRelevant-\u003efunction:neuroforge/internal/brain:allDigits:calls", + "from": "function:neuroforge/internal/brain:researchMaterialRelevant", + "to": "function:neuroforge/internal/brain:allDigits", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:researchMaterialRelevant-\u003efunction:neuroforge/internal/brain:goalAnchorTokens:calls", + "from": "function:neuroforge/internal/brain:researchMaterialRelevant", + "to": "function:neuroforge/internal/brain:goalAnchorTokens", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:researchMaterialRelevant-\u003efunction:neuroforge/internal/brain:normalizedResearchWords:calls", + "from": "function:neuroforge/internal/brain:researchMaterialRelevant", + "to": "function:neuroforge/internal/brain:normalizedResearchWords", + "kind": "calls" + }, { "id": "function:neuroforge/internal/brain:researchQueryUseful-\u003epackage:strings:calls_package", "from": "function:neuroforge/internal/brain:researchQueryUseful", @@ -49117,6 +53119,12 @@ "kind": "calls_package", "label": "ToLower" }, + { + "id": "function:neuroforge/internal/brain:researchResultsContainRelevant-\u003efunction:neuroforge/internal/brain:researchMaterialRelevant:calls", + "from": "function:neuroforge/internal/brain:researchResultsContainRelevant", + "to": "function:neuroforge/internal/brain:researchMaterialRelevant", + "kind": "calls" + }, { "id": "function:neuroforge/internal/brain:researchTrace.emit-\u003epackage:time:calls_package", "from": "function:neuroforge/internal/brain:researchTrace.emit", @@ -49150,6 +53158,26 @@ "kind": "calls_package", "label": "Join" }, + { + "id": "function:neuroforge/internal/brain:slashSwitchIdentifiers-\u003epackage:unicode:calls_package", + "from": "function:neuroforge/internal/brain:slashSwitchIdentifiers", + "to": "package:unicode", + "kind": "calls_package", + "label": "IsSpace" + }, + { + "id": "function:neuroforge/internal/brain:sortDraftEvidenceByAuthority-\u003efunction:neuroforge/internal/brain:draftEvidenceAuthorityScore:calls", + "from": "function:neuroforge/internal/brain:sortDraftEvidenceByAuthority", + "to": "function:neuroforge/internal/brain:draftEvidenceAuthorityScore", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:sortDraftEvidenceByAuthority-\u003epackage:sort:calls_package", + "from": "function:neuroforge/internal/brain:sortDraftEvidenceByAuthority", + "to": "package:sort", + "kind": "calls_package", + "label": "SliceStable" + }, { "id": "function:neuroforge/internal/brain:sortedGoalIDs-\u003epackage:sort:calls_package", "from": "function:neuroforge/internal/brain:sortedGoalIDs", @@ -49157,6 +53185,53 @@ "kind": "calls_package", "label": "Strings" }, + { + "id": "function:neuroforge/internal/brain:sourceAuditForEvidence-\u003efunction:neuroforge/internal/brain:sourceAuthorityFor:calls", + "from": "function:neuroforge/internal/brain:sourceAuditForEvidence", + "to": "function:neuroforge/internal/brain:sourceAuthorityFor", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:sourceAuditForEvidence-\u003epackage:fmt:calls_package", + "from": "function:neuroforge/internal/brain:sourceAuditForEvidence", + "to": "package:fmt", + "kind": "calls_package", + "label": "Sprintf" + }, + { + "id": "function:neuroforge/internal/brain:sourceAuthorityFor-\u003efunction:neuroforge/internal/brain:domainMatches:calls", + "from": "function:neuroforge/internal/brain:sourceAuthorityFor", + "to": "function:neuroforge/internal/brain:domainMatches", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:sourceAuthorityFor-\u003epackage:net/url:calls_package", + "from": "function:neuroforge/internal/brain:sourceAuthorityFor", + "to": "package:net/url", + "kind": "calls_package", + "label": "Parse" + }, + { + "id": "function:neuroforge/internal/brain:sourceAuthorityFor-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:sourceAuthorityFor", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, + { + "id": "function:neuroforge/internal/brain:sourceOriginKey-\u003epackage:net/url:calls_package", + "from": "function:neuroforge/internal/brain:sourceOriginKey", + "to": "package:net/url", + "kind": "calls_package", + "label": "Parse" + }, + { + "id": "function:neuroforge/internal/brain:sourceOriginKey-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:sourceOriginKey", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, { "id": "function:neuroforge/internal/brain:sourcePolicyKey-\u003epackage:strings:calls_package", "from": "function:neuroforge/internal/brain:sourcePolicyKey", @@ -49185,6 +53260,66 @@ "kind": "calls_package", "label": "ToLower" }, + { + "id": "function:neuroforge/internal/brain:stagingEvidenceSignature-\u003epackage:crypto/sha256:calls_package", + "from": "function:neuroforge/internal/brain:stagingEvidenceSignature", + "to": "package:crypto/sha256", + "kind": "calls_package", + "label": "Sum256" + }, + { + "id": "function:neuroforge/internal/brain:stagingEvidenceSignature-\u003epackage:fmt:calls_package", + "from": "function:neuroforge/internal/brain:stagingEvidenceSignature", + "to": "package:fmt", + "kind": "calls_package", + "label": "Sprintf" + }, + { + "id": "function:neuroforge/internal/brain:stagingEvidenceSignature-\u003epackage:sort:calls_package", + "from": "function:neuroforge/internal/brain:stagingEvidenceSignature", + "to": "package:sort", + "kind": "calls_package", + "label": "Strings" + }, + { + "id": "function:neuroforge/internal/brain:stagingEvidenceSignature-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:stagingEvidenceSignature", + "to": "package:strings", + "kind": "calls_package", + "label": "Join" + }, + { + "id": "function:neuroforge/internal/brain:strictUnmarshalJSONObject-\u003efunction:neuroforge/internal/brain:New:calls", + "from": "function:neuroforge/internal/brain:strictUnmarshalJSONObject", + "to": "function:neuroforge/internal/brain:New", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:strictUnmarshalJSONObject-\u003epackage:encoding/json:calls_package", + "from": "function:neuroforge/internal/brain:strictUnmarshalJSONObject", + "to": "package:encoding/json", + "kind": "calls_package", + "label": "NewDecoder" + }, + { + "id": "function:neuroforge/internal/brain:strictUnmarshalJSONObject-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:strictUnmarshalJSONObject", + "to": "package:strings", + "kind": "calls_package", + "label": "NewReader" + }, + { + "id": "function:neuroforge/internal/brain:summarizeEvidenceAuthority-\u003efunction:neuroforge/internal/brain:sourceAuditForEvidence:calls", + "from": "function:neuroforge/internal/brain:summarizeEvidenceAuthority", + "to": "function:neuroforge/internal/brain:sourceAuditForEvidence", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:summarizeEvidenceAuthority-\u003efunction:neuroforge/internal/brain:sourceOriginKey:calls", + "from": "function:neuroforge/internal/brain:summarizeEvidenceAuthority", + "to": "function:neuroforge/internal/brain:sourceOriginKey", + "kind": "calls" + }, { "id": "function:neuroforge/internal/brain:summarizeObservation-\u003epackage:fmt:calls_package", "from": "function:neuroforge/internal/brain:summarizeObservation", @@ -49199,6 +53334,59 @@ "kind": "calls_package", "label": "Join" }, + { + "id": "function:neuroforge/internal/brain:validateDraftArticleBounds-\u003efunction:neuroforge/internal/brain:articleRuneCount:calls", + "from": "function:neuroforge/internal/brain:validateDraftArticleBounds", + "to": "function:neuroforge/internal/brain:articleRuneCount", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:validateDraftArticleBounds-\u003epackage:fmt:calls_package", + "from": "function:neuroforge/internal/brain:validateDraftArticleBounds", + "to": "package:fmt", + "kind": "calls_package", + "label": "Errorf" + }, + { + "id": "function:neuroforge/internal/brain:validateDraftCriticalIdentifiers-\u003efunction:neuroforge/internal/brain:criticalIdentifiers:calls", + "from": "function:neuroforge/internal/brain:validateDraftCriticalIdentifiers", + "to": "function:neuroforge/internal/brain:criticalIdentifiers", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:validateDraftCriticalIdentifiers-\u003epackage:fmt:calls_package", + "from": "function:neuroforge/internal/brain:validateDraftCriticalIdentifiers", + "to": "package:fmt", + "kind": "calls_package", + "label": "Errorf" + }, + { + "id": "function:neuroforge/internal/brain:validateDraftCriticalIdentifiers-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/brain:validateDraftCriticalIdentifiers", + "to": "package:strings", + "kind": "calls_package", + "label": "ToLower" + }, + { + "id": "function:neuroforge/internal/brain:vectorFingerprint-\u003efunction:neuroforge/internal/brain:New:calls", + "from": "function:neuroforge/internal/brain:vectorFingerprint", + "to": "function:neuroforge/internal/brain:New", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/brain:vectorFingerprint-\u003epackage:encoding/hex:calls_package", + "from": "function:neuroforge/internal/brain:vectorFingerprint", + "to": "package:encoding/hex", + "kind": "calls_package", + "label": "EncodeToString" + }, + { + "id": "function:neuroforge/internal/brain:vectorFingerprint-\u003epackage:math:calls_package", + "from": "function:neuroforge/internal/brain:vectorFingerprint", + "to": "package:math", + "kind": "calls_package", + "label": "Float32bits" + }, { "id": "function:neuroforge/internal/cost:Manager.ActualCost-\u003efunction:neuroforge/internal/cost:New:calls", "from": "function:neuroforge/internal/cost:Manager.ActualCost", @@ -49451,6 +53639,24 @@ "to": "function:neuroforge/internal/httpapi:maskedSecret", "kind": "calls" }, + { + "id": "function:neuroforge/internal/httpapi:Server.adminGraphBackfill-\u003efunction:neuroforge/internal/httpapi:Server.err:calls", + "from": "function:neuroforge/internal/httpapi:Server.adminGraphBackfill", + "to": "function:neuroforge/internal/httpapi:Server.err", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/httpapi:Server.adminGraphBackfill-\u003efunction:neuroforge/internal/httpapi:Server.json:calls", + "from": "function:neuroforge/internal/httpapi:Server.adminGraphBackfill", + "to": "function:neuroforge/internal/httpapi:Server.json", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/httpapi:Server.adminGraphStatus-\u003efunction:neuroforge/internal/httpapi:Server.json:calls", + "from": "function:neuroforge/internal/httpapi:Server.adminGraphStatus", + "to": "function:neuroforge/internal/httpapi:Server.json", + "kind": "calls" + }, { "id": "function:neuroforge/internal/httpapi:Server.adminKnowledgeEvents-\u003efunction:neuroforge/internal/httpapi:Server.json:calls", "from": "function:neuroforge/internal/httpapi:Server.adminKnowledgeEvents", @@ -49596,6 +53802,62 @@ "to": "function:neuroforge/internal/httpapi:Server.json", "kind": "calls" }, + { + "id": "function:neuroforge/internal/httpapi:Server.adminOrchestratorCancel-\u003efunction:neuroforge/internal/httpapi:Server.err:calls", + "from": "function:neuroforge/internal/httpapi:Server.adminOrchestratorCancel", + "to": "function:neuroforge/internal/httpapi:Server.err", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/httpapi:Server.adminOrchestratorCancel-\u003efunction:neuroforge/internal/httpapi:Server.json:calls", + "from": "function:neuroforge/internal/httpapi:Server.adminOrchestratorCancel", + "to": "function:neuroforge/internal/httpapi:Server.json", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/httpapi:Server.adminOrchestratorCancel-\u003efunction:neuroforge/internal/httpapi:decode:calls", + "from": "function:neuroforge/internal/httpapi:Server.adminOrchestratorCancel", + "to": "function:neuroforge/internal/httpapi:decode", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/httpapi:Server.adminOrchestratorCancel-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/httpapi:Server.adminOrchestratorCancel", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, + { + "id": "function:neuroforge/internal/httpapi:Server.adminOrchestratorJobs-\u003efunction:neuroforge/internal/httpapi:Server.json:calls", + "from": "function:neuroforge/internal/httpapi:Server.adminOrchestratorJobs", + "to": "function:neuroforge/internal/httpapi:Server.json", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/httpapi:Server.adminOrchestratorJobs-\u003epackage:strconv:calls_package", + "from": "function:neuroforge/internal/httpapi:Server.adminOrchestratorJobs", + "to": "package:strconv", + "kind": "calls_package", + "label": "Atoi" + }, + { + "id": "function:neuroforge/internal/httpapi:Server.adminOrchestratorRetry-\u003efunction:neuroforge/internal/httpapi:Server.err:calls", + "from": "function:neuroforge/internal/httpapi:Server.adminOrchestratorRetry", + "to": "function:neuroforge/internal/httpapi:Server.err", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/httpapi:Server.adminOrchestratorRetry-\u003efunction:neuroforge/internal/httpapi:Server.json:calls", + "from": "function:neuroforge/internal/httpapi:Server.adminOrchestratorRetry", + "to": "function:neuroforge/internal/httpapi:Server.json", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/httpapi:Server.adminOrchestratorStatus-\u003efunction:neuroforge/internal/httpapi:Server.json:calls", + "from": "function:neuroforge/internal/httpapi:Server.adminOrchestratorStatus", + "to": "function:neuroforge/internal/httpapi:Server.json", + "kind": "calls" + }, { "id": "function:neuroforge/internal/httpapi:Server.adminProviderHealth-\u003efunction:neuroforge/internal/httpapi:Server.json:calls", "from": "function:neuroforge/internal/httpapi:Server.adminProviderHealth", @@ -50300,6 +54562,13 @@ "to": "function:neuroforge/internal/httpapi:decode", "kind": "calls" }, + { + "id": "function:neuroforge/internal/httpapi:Server.goalsCreate-\u003epackage:errors:calls_package", + "from": "function:neuroforge/internal/httpapi:Server.goalsCreate", + "to": "package:errors", + "kind": "calls_package", + "label": "Is" + }, { "id": "function:neuroforge/internal/httpapi:Server.goalsDelete-\u003efunction:neuroforge/internal/httpapi:Server.err:calls", "from": "function:neuroforge/internal/httpapi:Server.goalsDelete", @@ -50561,6 +54830,12 @@ "kind": "calls_package", "label": "TrimSpace" }, + { + "id": "function:neuroforge/internal/httpapi:Server.integrationGraphStatus-\u003efunction:neuroforge/internal/httpapi:Server.json:calls", + "from": "function:neuroforge/internal/httpapi:Server.integrationGraphStatus", + "to": "function:neuroforge/internal/httpapi:Server.json", + "kind": "calls" + }, { "id": "function:neuroforge/internal/httpapi:Server.integrationKnowledgeDelete-\u003efunction:neuroforge/internal/httpapi:New:calls", "from": "function:neuroforge/internal/httpapi:Server.integrationKnowledgeDelete", @@ -50711,6 +54986,12 @@ "kind": "calls_package", "label": "TrimSpace" }, + { + "id": "function:neuroforge/internal/httpapi:Server.integrationOrchestratorStatus-\u003efunction:neuroforge/internal/httpapi:Server.json:calls", + "from": "function:neuroforge/internal/httpapi:Server.integrationOrchestratorStatus", + "to": "function:neuroforge/internal/httpapi:Server.json", + "kind": "calls" + }, { "id": "function:neuroforge/internal/httpapi:Server.integrationResearchGraph-\u003efunction:neuroforge/internal/httpapi:Server.json:calls", "from": "function:neuroforge/internal/httpapi:Server.integrationResearchGraph", @@ -51014,6 +55295,13 @@ "kind": "calls_package", "label": "FormatFloat" }, + { + "id": "function:neuroforge/internal/httpapi:Server.metricsEndpoint-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/httpapi:Server.metricsEndpoint", + "to": "package:strings", + "kind": "calls_package", + "label": "SplitN" + }, { "id": "function:neuroforge/internal/httpapi:Server.readyz-\u003efunction:neuroforge/internal/httpapi:Server.json:calls", "from": "function:neuroforge/internal/httpapi:Server.readyz", @@ -51294,6 +55582,12 @@ "to": "function:neuroforge/internal/httpapi:statusWriter.WriteHeader", "kind": "calls" }, + { + "id": "function:neuroforge/internal/httpapi:Server.workerClaim-\u003efunction:neuroforge/internal/httpapi:workerHeartbeatFromRequest:calls", + "from": "function:neuroforge/internal/httpapi:Server.workerClaim", + "to": "function:neuroforge/internal/httpapi:workerHeartbeatFromRequest", + "kind": "calls" + }, { "id": "function:neuroforge/internal/httpapi:Server.workerClaim-\u003epackage:time:calls_package", "from": "function:neuroforge/internal/httpapi:Server.workerClaim", @@ -51319,6 +55613,61 @@ "to": "function:neuroforge/internal/httpapi:decode", "kind": "calls" }, + { + "id": "function:neuroforge/internal/httpapi:Server.workerHeartbeat-\u003efunction:neuroforge/internal/httpapi:Server.err:calls", + "from": "function:neuroforge/internal/httpapi:Server.workerHeartbeat", + "to": "function:neuroforge/internal/httpapi:Server.err", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/httpapi:Server.workerHeartbeat-\u003efunction:neuroforge/internal/httpapi:Server.json:calls", + "from": "function:neuroforge/internal/httpapi:Server.workerHeartbeat", + "to": "function:neuroforge/internal/httpapi:Server.json", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/httpapi:Server.workerHeartbeat-\u003efunction:neuroforge/internal/httpapi:decode:calls", + "from": "function:neuroforge/internal/httpapi:Server.workerHeartbeat", + "to": "function:neuroforge/internal/httpapi:decode", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/httpapi:Server.workerHeartbeat-\u003efunction:neuroforge/internal/httpapi:workerHeartbeatFromRequest:calls", + "from": "function:neuroforge/internal/httpapi:Server.workerHeartbeat", + "to": "function:neuroforge/internal/httpapi:workerHeartbeatFromRequest", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/httpapi:Server.workerHeartbeat-\u003epackage:time:calls_package", + "from": "function:neuroforge/internal/httpapi:Server.workerHeartbeat", + "to": "package:time", + "kind": "calls_package", + "label": "Duration" + }, + { + "id": "function:neuroforge/internal/httpapi:Server.workerRegister-\u003efunction:neuroforge/internal/httpapi:Server.err:calls", + "from": "function:neuroforge/internal/httpapi:Server.workerRegister", + "to": "function:neuroforge/internal/httpapi:Server.err", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/httpapi:Server.workerRegister-\u003efunction:neuroforge/internal/httpapi:Server.json:calls", + "from": "function:neuroforge/internal/httpapi:Server.workerRegister", + "to": "function:neuroforge/internal/httpapi:Server.json", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/httpapi:Server.workerRegister-\u003efunction:neuroforge/internal/httpapi:decode:calls", + "from": "function:neuroforge/internal/httpapi:Server.workerRegister", + "to": "function:neuroforge/internal/httpapi:decode", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/httpapi:Server.workerRegister-\u003efunction:neuroforge/internal/httpapi:workerHeartbeatFromRequest:calls", + "from": "function:neuroforge/internal/httpapi:Server.workerRegister", + "to": "function:neuroforge/internal/httpapi:workerHeartbeatFromRequest", + "kind": "calls" + }, { "id": "function:neuroforge/internal/httpapi:approxP95-\u003epackage:math:calls_package", "from": "function:neuroforge/internal/httpapi:approxP95", @@ -51808,44 +56157,17 @@ "kind": "calls" }, { - "id": "function:neuroforge/internal/provider:Router.ChatOn-\u003efunction:neuroforge/internal/provider:Router.chatOllama:calls", - "from": "function:neuroforge/internal/provider:Router.ChatOn", - "to": "function:neuroforge/internal/provider:Router.chatOllama", + "id": "function:neuroforge/internal/provider:Router.ChatJSONOn-\u003efunction:neuroforge/internal/provider:Router.chatOn:calls", + "from": "function:neuroforge/internal/provider:Router.ChatJSONOn", + "to": "function:neuroforge/internal/provider:Router.chatOn", "kind": "calls" }, { - "id": "function:neuroforge/internal/provider:Router.ChatOn-\u003efunction:neuroforge/internal/provider:Router.chatOpenAI:calls", + "id": "function:neuroforge/internal/provider:Router.ChatOn-\u003efunction:neuroforge/internal/provider:Router.chatOn:calls", "from": "function:neuroforge/internal/provider:Router.ChatOn", - "to": "function:neuroforge/internal/provider:Router.chatOpenAI", + "to": "function:neuroforge/internal/provider:Router.chatOn", "kind": "calls" }, - { - "id": "function:neuroforge/internal/provider:Router.ChatOn-\u003efunction:neuroforge/internal/provider:Router.ollamaOrderFor:calls", - "from": "function:neuroforge/internal/provider:Router.ChatOn", - "to": "function:neuroforge/internal/provider:Router.ollamaOrderFor", - "kind": "calls" - }, - { - "id": "function:neuroforge/internal/provider:Router.ChatOn-\u003epackage:errors:calls_package", - "from": "function:neuroforge/internal/provider:Router.ChatOn", - "to": "package:errors", - "kind": "calls_package", - "label": "New" - }, - { - "id": "function:neuroforge/internal/provider:Router.ChatOn-\u003epackage:fmt:calls_package", - "from": "function:neuroforge/internal/provider:Router.ChatOn", - "to": "package:fmt", - "kind": "calls_package", - "label": "Errorf" - }, - { - "id": "function:neuroforge/internal/provider:Router.ChatOn-\u003epackage:strings:calls_package", - "from": "function:neuroforge/internal/provider:Router.ChatOn", - "to": "package:strings", - "kind": "calls_package", - "label": "TrimSpace" - }, { "id": "function:neuroforge/internal/provider:Router.Embed-\u003efunction:neuroforge/internal/provider:Router.EmbedOn:calls", "from": "function:neuroforge/internal/provider:Router.Embed", @@ -51970,6 +56292,45 @@ "kind": "calls_package", "label": "TrimSpace" }, + { + "id": "function:neuroforge/internal/provider:Router.chatOn-\u003efunction:neuroforge/internal/provider:Router.chatOllama:calls", + "from": "function:neuroforge/internal/provider:Router.chatOn", + "to": "function:neuroforge/internal/provider:Router.chatOllama", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/provider:Router.chatOn-\u003efunction:neuroforge/internal/provider:Router.chatOpenAI:calls", + "from": "function:neuroforge/internal/provider:Router.chatOn", + "to": "function:neuroforge/internal/provider:Router.chatOpenAI", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/provider:Router.chatOn-\u003efunction:neuroforge/internal/provider:Router.ollamaOrderFor:calls", + "from": "function:neuroforge/internal/provider:Router.chatOn", + "to": "function:neuroforge/internal/provider:Router.ollamaOrderFor", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/provider:Router.chatOn-\u003epackage:errors:calls_package", + "from": "function:neuroforge/internal/provider:Router.chatOn", + "to": "package:errors", + "kind": "calls_package", + "label": "New" + }, + { + "id": "function:neuroforge/internal/provider:Router.chatOn-\u003epackage:fmt:calls_package", + "from": "function:neuroforge/internal/provider:Router.chatOn", + "to": "package:fmt", + "kind": "calls_package", + "label": "Errorf" + }, + { + "id": "function:neuroforge/internal/provider:Router.chatOn-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/provider:Router.chatOn", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, { "id": "function:neuroforge/internal/provider:Router.chatOpenAI-\u003efunction:neuroforge/internal/provider:Router.doJSON:calls", "from": "function:neuroforge/internal/provider:Router.chatOpenAI", @@ -52810,6 +57171,12 @@ "to": "function:neuroforge/internal/store:Store.rebuildProvenanceSourceIndexLocked", "kind": "calls" }, + { + "id": "function:neuroforge/internal/store:New-\u003efunction:neuroforge/internal/store:Store.rebuildSynapseAdjLocked:calls", + "from": "function:neuroforge/internal/store:New", + "to": "function:neuroforge/internal/store:Store.rebuildSynapseAdjLocked", + "kind": "calls" + }, { "id": "function:neuroforge/internal/store:New-\u003efunction:neuroforge/internal/store:Store.replayWAL:calls", "from": "function:neuroforge/internal/store:New", @@ -53598,14 +57965,83 @@ "label": "Now" }, { - "id": "function:neuroforge/internal/store:Store.ClaimJob-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", - "from": "function:neuroforge/internal/store:Store.ClaimJob", + "id": "function:neuroforge/internal/store:Store.CancelJob-\u003efunction:neuroforge/internal/store:New:calls", + "from": "function:neuroforge/internal/store:Store.CancelJob", + "to": "function:neuroforge/internal/store:New", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.CancelJob-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", + "from": "function:neuroforge/internal/store:Store.CancelJob", "to": "function:neuroforge/internal/store:Store.commitLocked", "kind": "calls" }, { - "id": "function:neuroforge/internal/store:Store.ClaimJob-\u003epackage:time:calls_package", + "id": "function:neuroforge/internal/store:Store.CancelJob-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/store:Store.CancelJob", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, + { + "id": "function:neuroforge/internal/store:Store.CancelJob-\u003epackage:time:calls_package", + "from": "function:neuroforge/internal/store:Store.CancelJob", + "to": "package:time", + "kind": "calls_package", + "label": "Now" + }, + { + "id": "function:neuroforge/internal/store:Store.ClaimJob-\u003efunction:neuroforge/internal/store:Store.ClaimJobForWorker:calls", "from": "function:neuroforge/internal/store:Store.ClaimJob", + "to": "function:neuroforge/internal/store:Store.ClaimJobForWorker", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.ClaimJobForWorker-\u003efunction:neuroforge/internal/store:New:calls", + "from": "function:neuroforge/internal/store:Store.ClaimJobForWorker", + "to": "function:neuroforge/internal/store:New", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.ClaimJobForWorker-\u003efunction:neuroforge/internal/store:NewID:calls", + "from": "function:neuroforge/internal/store:Store.ClaimJobForWorker", + "to": "function:neuroforge/internal/store:NewID", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.ClaimJobForWorker-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", + "from": "function:neuroforge/internal/store:Store.ClaimJobForWorker", + "to": "function:neuroforge/internal/store:Store.commitLocked", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.ClaimJobForWorker-\u003efunction:neuroforge/internal/store:Store.normalizeJobForClaimLocked:calls", + "from": "function:neuroforge/internal/store:Store.ClaimJobForWorker", + "to": "function:neuroforge/internal/store:Store.normalizeJobForClaimLocked", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.ClaimJobForWorker-\u003efunction:neuroforge/internal/store:Store.registerWorkerLocked:calls", + "from": "function:neuroforge/internal/store:Store.ClaimJobForWorker", + "to": "function:neuroforge/internal/store:Store.registerWorkerLocked", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.ClaimJobForWorker-\u003efunction:neuroforge/internal/store:workerMatchesJob:calls", + "from": "function:neuroforge/internal/store:Store.ClaimJobForWorker", + "to": "function:neuroforge/internal/store:workerMatchesJob", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.ClaimJobForWorker-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/store:Store.ClaimJobForWorker", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, + { + "id": "function:neuroforge/internal/store:Store.ClaimJobForWorker-\u003epackage:time:calls_package", + "from": "function:neuroforge/internal/store:Store.ClaimJobForWorker", "to": "package:time", "kind": "calls_package", "label": "Now" @@ -53761,20 +58197,46 @@ "kind": "calls" }, { - "id": "function:neuroforge/internal/store:Store.CompleteJob-\u003efunction:neuroforge/internal/store:New:calls", + "id": "function:neuroforge/internal/store:Store.CompleteJob-\u003efunction:neuroforge/internal/store:Store.CompleteJobLease:calls", "from": "function:neuroforge/internal/store:Store.CompleteJob", + "to": "function:neuroforge/internal/store:Store.CompleteJobLease", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.CompleteJobLease-\u003efunction:neuroforge/internal/store:New:calls", + "from": "function:neuroforge/internal/store:Store.CompleteJobLease", "to": "function:neuroforge/internal/store:New", "kind": "calls" }, { - "id": "function:neuroforge/internal/store:Store.CompleteJob-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", - "from": "function:neuroforge/internal/store:Store.CompleteJob", + "id": "function:neuroforge/internal/store:Store.CompleteJobLease-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", + "from": "function:neuroforge/internal/store:Store.CompleteJobLease", "to": "function:neuroforge/internal/store:Store.commitLocked", "kind": "calls" }, { - "id": "function:neuroforge/internal/store:Store.CompleteJob-\u003epackage:time:calls_package", - "from": "function:neuroforge/internal/store:Store.CompleteJob", + "id": "function:neuroforge/internal/store:Store.CompleteJobLease-\u003efunction:neuroforge/internal/store:maxInt:calls", + "from": "function:neuroforge/internal/store:Store.CompleteJobLease", + "to": "function:neuroforge/internal/store:maxInt", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.CompleteJobLease-\u003epackage:fmt:calls_package", + "from": "function:neuroforge/internal/store:Store.CompleteJobLease", + "to": "package:fmt", + "kind": "calls_package", + "label": "Errorf" + }, + { + "id": "function:neuroforge/internal/store:Store.CompleteJobLease-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/store:Store.CompleteJobLease", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, + { + "id": "function:neuroforge/internal/store:Store.CompleteJobLease-\u003epackage:time:calls_package", + "from": "function:neuroforge/internal/store:Store.CompleteJobLease", "to": "package:time", "kind": "calls_package", "label": "Now" @@ -53860,6 +58322,12 @@ "to": "function:neuroforge/internal/store:Store.commitLocked", "kind": "calls" }, + { + "id": "function:neuroforge/internal/store:Store.DecayAndPruneSynapses-\u003efunction:neuroforge/internal/store:Store.unindexSynapseLocked:calls", + "from": "function:neuroforge/internal/store:Store.DecayAndPruneSynapses", + "to": "function:neuroforge/internal/store:Store.unindexSynapseLocked", + "kind": "calls" + }, { "id": "function:neuroforge/internal/store:Store.DecayAndPruneSynapses-\u003efunction:neuroforge/internal/store:pow:calls", "from": "function:neuroforge/internal/store:Store.DecayAndPruneSynapses", @@ -53922,6 +58390,12 @@ "to": "function:neuroforge/internal/store:Store.unindexProvenanceSourceLocked", "kind": "calls" }, + { + "id": "function:neuroforge/internal/store:Store.DeleteMemoriesBatch-\u003efunction:neuroforge/internal/store:Store.unindexSynapseLocked:calls", + "from": "function:neuroforge/internal/store:Store.DeleteMemoriesBatch", + "to": "function:neuroforge/internal/store:Store.unindexSynapseLocked", + "kind": "calls" + }, { "id": "function:neuroforge/internal/store:Store.DeleteMemoriesBatch-\u003efunction:neuroforge/internal/store:Store.untrackHotMemoryLocked:calls", "from": "function:neuroforge/internal/store:Store.DeleteMemoriesBatch", @@ -53959,6 +58433,12 @@ "to": "function:neuroforge/internal/store:Store.unindexProvenanceSourceLocked", "kind": "calls" }, + { + "id": "function:neuroforge/internal/store:Store.DeleteMemory-\u003efunction:neuroforge/internal/store:Store.unindexSynapseLocked:calls", + "from": "function:neuroforge/internal/store:Store.DeleteMemory", + "to": "function:neuroforge/internal/store:Store.unindexSynapseLocked", + "kind": "calls" + }, { "id": "function:neuroforge/internal/store:Store.DeleteMemory-\u003efunction:neuroforge/internal/store:Store.untrackHotMemoryLocked:calls", "from": "function:neuroforge/internal/store:Store.DeleteMemory", @@ -54028,27 +58508,65 @@ "label": "Itoa" }, { - "id": "function:neuroforge/internal/store:Store.EnqueueJob-\u003efunction:neuroforge/internal/store:NewID:calls", + "id": "function:neuroforge/internal/store:Store.EnqueueJob-\u003efunction:neuroforge/internal/store:Store.EnqueueJobSpec:calls", "from": "function:neuroforge/internal/store:Store.EnqueueJob", + "to": "function:neuroforge/internal/store:Store.EnqueueJobSpec", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.EnqueueJobSpec-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", + "from": "function:neuroforge/internal/store:Store.EnqueueJobSpec", + "to": "function:neuroforge/internal/store:ClusterLog.append", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.EnqueueJobSpec-\u003efunction:neuroforge/internal/store:New:calls", + "from": "function:neuroforge/internal/store:Store.EnqueueJobSpec", + "to": "function:neuroforge/internal/store:New", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.EnqueueJobSpec-\u003efunction:neuroforge/internal/store:NewID:calls", + "from": "function:neuroforge/internal/store:Store.EnqueueJobSpec", "to": "function:neuroforge/internal/store:NewID", "kind": "calls" }, { - "id": "function:neuroforge/internal/store:Store.EnqueueJob-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", - "from": "function:neuroforge/internal/store:Store.EnqueueJob", + "id": "function:neuroforge/internal/store:Store.EnqueueJobSpec-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", + "from": "function:neuroforge/internal/store:Store.EnqueueJobSpec", "to": "function:neuroforge/internal/store:Store.commitLocked", "kind": "calls" }, { - "id": "function:neuroforge/internal/store:Store.EnqueueJob-\u003epackage:encoding/json:calls_package", - "from": "function:neuroforge/internal/store:Store.EnqueueJob", + "id": "function:neuroforge/internal/store:Store.EnqueueJobSpec-\u003efunction:neuroforge/internal/store:normalizeCapabilities:calls", + "from": "function:neuroforge/internal/store:Store.EnqueueJobSpec", + "to": "function:neuroforge/internal/store:normalizeCapabilities", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.EnqueueJobSpec-\u003epackage:encoding/json:calls_package", + "from": "function:neuroforge/internal/store:Store.EnqueueJobSpec", "to": "package:encoding/json", "kind": "calls_package", "label": "Marshal" }, { - "id": "function:neuroforge/internal/store:Store.EnqueueJob-\u003epackage:time:calls_package", - "from": "function:neuroforge/internal/store:Store.EnqueueJob", + "id": "function:neuroforge/internal/store:Store.EnqueueJobSpec-\u003epackage:fmt:calls_package", + "from": "function:neuroforge/internal/store:Store.EnqueueJobSpec", + "to": "package:fmt", + "kind": "calls_package", + "label": "Errorf" + }, + { + "id": "function:neuroforge/internal/store:Store.EnqueueJobSpec-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/store:Store.EnqueueJobSpec", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, + { + "id": "function:neuroforge/internal/store:Store.EnqueueJobSpec-\u003epackage:time:calls_package", + "from": "function:neuroforge/internal/store:Store.EnqueueJobSpec", "to": "package:time", "kind": "calls_package", "label": "Now" @@ -54072,6 +58590,45 @@ "kind": "calls_package", "label": "Marshal" }, + { + "id": "function:neuroforge/internal/store:Store.FinishMasterApply-\u003efunction:neuroforge/internal/store:New:calls", + "from": "function:neuroforge/internal/store:Store.FinishMasterApply", + "to": "function:neuroforge/internal/store:New", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.FinishMasterApply-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", + "from": "function:neuroforge/internal/store:Store.FinishMasterApply", + "to": "function:neuroforge/internal/store:Store.commitLocked", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.FinishMasterApply-\u003efunction:neuroforge/internal/store:maxInt:calls", + "from": "function:neuroforge/internal/store:Store.FinishMasterApply", + "to": "function:neuroforge/internal/store:maxInt", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.FinishMasterApply-\u003epackage:fmt:calls_package", + "from": "function:neuroforge/internal/store:Store.FinishMasterApply", + "to": "package:fmt", + "kind": "calls_package", + "label": "Errorf" + }, + { + "id": "function:neuroforge/internal/store:Store.FinishMasterApply-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/store:Store.FinishMasterApply", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, + { + "id": "function:neuroforge/internal/store:Store.FinishMasterApply-\u003epackage:time:calls_package", + "from": "function:neuroforge/internal/store:Store.FinishMasterApply", + "to": "package:time", + "kind": "calls_package", + "label": "Now" + }, { "id": "function:neuroforge/internal/store:Store.FinishResearchRun-\u003efunction:neuroforge/internal/store:New:calls", "from": "function:neuroforge/internal/store:Store.FinishResearchRun", @@ -54172,6 +58729,108 @@ "kind": "calls_package", "label": "Now" }, + { + "id": "function:neuroforge/internal/store:Store.GraphBackfillCandidates-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", + "from": "function:neuroforge/internal/store:Store.GraphBackfillCandidates", + "to": "function:neuroforge/internal/store:ClusterLog.append", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.GraphBackfillCandidates-\u003efunction:neuroforge/internal/store:Store.fullMemoryForReadLocked:calls", + "from": "function:neuroforge/internal/store:Store.GraphBackfillCandidates", + "to": "function:neuroforge/internal/store:Store.fullMemoryForReadLocked", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.GraphBackfillCandidates-\u003efunction:neuroforge/internal/store:cloneMemory:calls", + "from": "function:neuroforge/internal/store:Store.GraphBackfillCandidates", + "to": "function:neuroforge/internal/store:cloneMemory", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.GraphBackfillCandidates-\u003efunction:neuroforge/internal/store:memorySearchable:calls", + "from": "function:neuroforge/internal/store:Store.GraphBackfillCandidates", + "to": "function:neuroforge/internal/store:memorySearchable", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.GraphBackfillCandidates-\u003epackage:sort:calls_package", + "from": "function:neuroforge/internal/store:Store.GraphBackfillCandidates", + "to": "package:sort", + "kind": "calls_package", + "label": "Strings" + }, + { + "id": "function:neuroforge/internal/store:Store.GraphBackfillCandidates-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/store:Store.GraphBackfillCandidates", + "to": "package:strings", + "kind": "calls_package", + "label": "HasPrefix" + }, + { + "id": "function:neuroforge/internal/store:Store.GraphBackfillCandidates-\u003epackage:time:calls_package", + "from": "function:neuroforge/internal/store:Store.GraphBackfillCandidates", + "to": "package:time", + "kind": "calls_package", + "label": "Now" + }, + { + "id": "function:neuroforge/internal/store:Store.GraphStats-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", + "from": "function:neuroforge/internal/store:Store.GraphStats", + "to": "function:neuroforge/internal/store:ClusterLog.append", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.HasLiveWorker-\u003efunction:neuroforge/internal/store:Store.WorkersSnapshot:calls", + "from": "function:neuroforge/internal/store:Store.HasLiveWorker", + "to": "function:neuroforge/internal/store:Store.WorkersSnapshot", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.HasLiveWorker-\u003efunction:neuroforge/internal/store:capabilitiesContain:calls", + "from": "function:neuroforge/internal/store:Store.HasLiveWorker", + "to": "function:neuroforge/internal/store:capabilitiesContain", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.HasLiveWorker-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/store:Store.HasLiveWorker", + "to": "package:strings", + "kind": "calls_package", + "label": "ToLower" + }, + { + "id": "function:neuroforge/internal/store:Store.HeartbeatWorker-\u003efunction:neuroforge/internal/store:New:calls", + "from": "function:neuroforge/internal/store:Store.HeartbeatWorker", + "to": "function:neuroforge/internal/store:New", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.HeartbeatWorker-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", + "from": "function:neuroforge/internal/store:Store.HeartbeatWorker", + "to": "function:neuroforge/internal/store:Store.commitLocked", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.HeartbeatWorker-\u003efunction:neuroforge/internal/store:Store.registerWorkerLocked:calls", + "from": "function:neuroforge/internal/store:Store.HeartbeatWorker", + "to": "function:neuroforge/internal/store:Store.registerWorkerLocked", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.HeartbeatWorker-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/store:Store.HeartbeatWorker", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, + { + "id": "function:neuroforge/internal/store:Store.HeartbeatWorker-\u003epackage:time:calls_package", + "from": "function:neuroforge/internal/store:Store.HeartbeatWorker", + "to": "package:time", + "kind": "calls_package", + "label": "Now" + }, { "id": "function:neuroforge/internal/store:Store.IndexSnapshotStatus-\u003efunction:neuroforge/internal/store:Store.IndexSnapshotStatusUnlocked:calls", "from": "function:neuroforge/internal/store:Store.IndexSnapshotStatus", @@ -54185,6 +58844,39 @@ "kind": "calls_package", "label": "Marshal" }, + { + "id": "function:neuroforge/internal/store:Store.Job-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", + "from": "function:neuroforge/internal/store:Store.Job", + "to": "function:neuroforge/internal/store:ClusterLog.append", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.Job-\u003epackage:encoding/json:calls_package", + "from": "function:neuroforge/internal/store:Store.Job", + "to": "package:encoding/json", + "kind": "calls_package", + "label": "RawMessage" + }, + { + "id": "function:neuroforge/internal/store:Store.JobsSnapshot-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", + "from": "function:neuroforge/internal/store:Store.JobsSnapshot", + "to": "function:neuroforge/internal/store:ClusterLog.append", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.JobsSnapshot-\u003epackage:sort:calls_package", + "from": "function:neuroforge/internal/store:Store.JobsSnapshot", + "to": "package:sort", + "kind": "calls_package", + "label": "Slice" + }, + { + "id": "function:neuroforge/internal/store:Store.JobsSnapshot-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/store:Store.JobsSnapshot", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, { "id": "function:neuroforge/internal/store:Store.KnowledgeGraph-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", "from": "function:neuroforge/internal/store:Store.KnowledgeGraph", @@ -54341,6 +59033,31 @@ "to": "function:neuroforge/internal/store:cloneMemory", "kind": "calls" }, + { + "id": "function:neuroforge/internal/store:Store.MarkGraphLinked-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", + "from": "function:neuroforge/internal/store:Store.MarkGraphLinked", + "to": "function:neuroforge/internal/store:Store.commitLocked", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.MarkGraphLinked-\u003efunction:neuroforge/internal/store:Store.materializeMemoryLocked:calls", + "from": "function:neuroforge/internal/store:Store.MarkGraphLinked", + "to": "function:neuroforge/internal/store:Store.materializeMemoryLocked", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.MarkGraphLinked-\u003efunction:neuroforge/internal/store:cloneMemory:calls", + "from": "function:neuroforge/internal/store:Store.MarkGraphLinked", + "to": "function:neuroforge/internal/store:cloneMemory", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.MarkGraphLinked-\u003epackage:time:calls_package", + "from": "function:neuroforge/internal/store:Store.MarkGraphLinked", + "to": "package:time", + "kind": "calls_package", + "label": "Now" + }, { "id": "function:neuroforge/internal/store:Store.MemoriesSnapshot-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", "from": "function:neuroforge/internal/store:Store.MemoriesSnapshot", @@ -54416,6 +59133,12 @@ "to": "function:neuroforge/internal/store:previewHeap.Len", "kind": "calls" }, + { + "id": "function:neuroforge/internal/store:Store.OrchestratorStatus-\u003efunction:neuroforge/internal/store:Store.WorkersSnapshot:calls", + "from": "function:neuroforge/internal/store:Store.OrchestratorStatus", + "to": "function:neuroforge/internal/store:Store.WorkersSnapshot", + "kind": "calls" + }, { "id": "function:neuroforge/internal/store:Store.PauseGoal-\u003efunction:neuroforge/internal/store:New:calls", "from": "function:neuroforge/internal/store:Store.PauseGoal", @@ -54494,6 +59217,33 @@ "kind": "calls_package", "label": "HasSuffix" }, + { + "id": "function:neuroforge/internal/store:Store.PendingMasterApplyJobs-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", + "from": "function:neuroforge/internal/store:Store.PendingMasterApplyJobs", + "to": "function:neuroforge/internal/store:ClusterLog.append", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.PendingMasterApplyJobs-\u003epackage:encoding/json:calls_package", + "from": "function:neuroforge/internal/store:Store.PendingMasterApplyJobs", + "to": "package:encoding/json", + "kind": "calls_package", + "label": "RawMessage" + }, + { + "id": "function:neuroforge/internal/store:Store.PendingMasterApplyJobs-\u003epackage:sort:calls_package", + "from": "function:neuroforge/internal/store:Store.PendingMasterApplyJobs", + "to": "package:sort", + "kind": "calls_package", + "label": "Slice" + }, + { + "id": "function:neuroforge/internal/store:Store.PendingMasterApplyJobs-\u003epackage:time:calls_package", + "from": "function:neuroforge/internal/store:Store.PendingMasterApplyJobs", + "to": "package:time", + "kind": "calls_package", + "label": "Now" + }, { "id": "function:neuroforge/internal/store:Store.PrepareClusterEntry-\u003efunction:neuroforge/internal/store:New:calls", "from": "function:neuroforge/internal/store:Store.PrepareClusterEntry", @@ -54538,6 +59288,32 @@ "kind": "calls_package", "label": "Join" }, + { + "id": "function:neuroforge/internal/store:Store.PruneTerminalJobs-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", + "from": "function:neuroforge/internal/store:Store.PruneTerminalJobs", + "to": "function:neuroforge/internal/store:ClusterLog.append", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.PruneTerminalJobs-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", + "from": "function:neuroforge/internal/store:Store.PruneTerminalJobs", + "to": "function:neuroforge/internal/store:Store.commitLocked", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.PruneTerminalJobs-\u003epackage:sort:calls_package", + "from": "function:neuroforge/internal/store:Store.PruneTerminalJobs", + "to": "package:sort", + "kind": "calls_package", + "label": "Slice" + }, + { + "id": "function:neuroforge/internal/store:Store.PruneTerminalJobs-\u003epackage:time:calls_package", + "from": "function:neuroforge/internal/store:Store.PruneTerminalJobs", + "to": "package:time", + "kind": "calls_package", + "label": "Now" + }, { "id": "function:neuroforge/internal/store:Store.RebuildDiskANN-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", "from": "function:neuroforge/internal/store:Store.RebuildDiskANN", @@ -54747,33 +59523,77 @@ "label": "Now" }, { - "id": "function:neuroforge/internal/store:Store.Reinforce-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", + "id": "function:neuroforge/internal/store:Store.RegisterWorker-\u003efunction:neuroforge/internal/store:New:calls", + "from": "function:neuroforge/internal/store:Store.RegisterWorker", + "to": "function:neuroforge/internal/store:New", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.RegisterWorker-\u003efunction:neuroforge/internal/store:Store.registerWorkerLocked:calls", + "from": "function:neuroforge/internal/store:Store.RegisterWorker", + "to": "function:neuroforge/internal/store:Store.registerWorkerLocked", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.RegisterWorker-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/store:Store.RegisterWorker", + "to": "package:strings", + "kind": "calls_package", + "label": "TrimSpace" + }, + { + "id": "function:neuroforge/internal/store:Store.RegisterWorker-\u003epackage:time:calls_package", + "from": "function:neuroforge/internal/store:Store.RegisterWorker", + "to": "package:time", + "kind": "calls_package", + "label": "Now" + }, + { + "id": "function:neuroforge/internal/store:Store.Reinforce-\u003efunction:neuroforge/internal/store:Store.ReinforceRelation:calls", "from": "function:neuroforge/internal/store:Store.Reinforce", + "to": "function:neuroforge/internal/store:Store.ReinforceRelation", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.ReinforceRelation-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", + "from": "function:neuroforge/internal/store:Store.ReinforceRelation", "to": "function:neuroforge/internal/store:Store.commitLocked", "kind": "calls" }, { - "id": "function:neuroforge/internal/store:Store.Reinforce-\u003efunction:neuroforge/internal/store:edgeKey:calls", - "from": "function:neuroforge/internal/store:Store.Reinforce", + "id": "function:neuroforge/internal/store:Store.ReinforceRelation-\u003efunction:neuroforge/internal/store:Store.indexSynapseLocked:calls", + "from": "function:neuroforge/internal/store:Store.ReinforceRelation", + "to": "function:neuroforge/internal/store:Store.indexSynapseLocked", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.ReinforceRelation-\u003efunction:neuroforge/internal/store:appendUniqueRelation:calls", + "from": "function:neuroforge/internal/store:Store.ReinforceRelation", + "to": "function:neuroforge/internal/store:appendUniqueRelation", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.ReinforceRelation-\u003efunction:neuroforge/internal/store:edgeKey:calls", + "from": "function:neuroforge/internal/store:Store.ReinforceRelation", "to": "function:neuroforge/internal/store:edgeKey", "kind": "calls" }, { - "id": "function:neuroforge/internal/store:Store.Reinforce-\u003efunction:neuroforge/internal/store:pow:calls", - "from": "function:neuroforge/internal/store:Store.Reinforce", + "id": "function:neuroforge/internal/store:Store.ReinforceRelation-\u003efunction:neuroforge/internal/store:pow:calls", + "from": "function:neuroforge/internal/store:Store.ReinforceRelation", "to": "function:neuroforge/internal/store:pow", "kind": "calls" }, { - "id": "function:neuroforge/internal/store:Store.Reinforce-\u003epackage:neuroforge/internal/vector:calls_package", - "from": "function:neuroforge/internal/store:Store.Reinforce", + "id": "function:neuroforge/internal/store:Store.ReinforceRelation-\u003epackage:neuroforge/internal/vector:calls_package", + "from": "function:neuroforge/internal/store:Store.ReinforceRelation", "to": "package:neuroforge/internal/vector", "kind": "calls_package", "label": "Clamp" }, { - "id": "function:neuroforge/internal/store:Store.Reinforce-\u003epackage:time:calls_package", - "from": "function:neuroforge/internal/store:Store.Reinforce", + "id": "function:neuroforge/internal/store:Store.ReinforceRelation-\u003epackage:time:calls_package", + "from": "function:neuroforge/internal/store:Store.ReinforceRelation", "to": "package:time", "kind": "calls_package", "label": "Now" @@ -54872,6 +59692,32 @@ "kind": "calls_package", "label": "Now" }, + { + "id": "function:neuroforge/internal/store:Store.RetryJob-\u003efunction:neuroforge/internal/store:New:calls", + "from": "function:neuroforge/internal/store:Store.RetryJob", + "to": "function:neuroforge/internal/store:New", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.RetryJob-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", + "from": "function:neuroforge/internal/store:Store.RetryJob", + "to": "function:neuroforge/internal/store:Store.commitLocked", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.RetryJob-\u003epackage:fmt:calls_package", + "from": "function:neuroforge/internal/store:Store.RetryJob", + "to": "package:fmt", + "kind": "calls_package", + "label": "Errorf" + }, + { + "id": "function:neuroforge/internal/store:Store.RetryJob-\u003epackage:time:calls_package", + "from": "function:neuroforge/internal/store:Store.RetryJob", + "to": "package:time", + "kind": "calls_package", + "label": "Now" + }, { "id": "function:neuroforge/internal/store:Store.RunRetention-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", "from": "function:neuroforge/internal/store:Store.RunRetention", @@ -55504,6 +60350,19 @@ "to": "function:neuroforge/internal/store:cloneGoal", "kind": "calls" }, + { + "id": "function:neuroforge/internal/store:Store.UpsertGoal-\u003efunction:neuroforge/internal/store:goalFingerprint:calls", + "from": "function:neuroforge/internal/store:Store.UpsertGoal", + "to": "function:neuroforge/internal/store:goalFingerprint", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.UpsertGoal-\u003epackage:fmt:calls_package", + "from": "function:neuroforge/internal/store:Store.UpsertGoal", + "to": "package:fmt", + "kind": "calls_package", + "label": "Errorf" + }, { "id": "function:neuroforge/internal/store:Store.UpsertGoal-\u003epackage:neuroforge/internal/vector:calls_package", "from": "function:neuroforge/internal/store:Store.UpsertGoal", @@ -55525,6 +60384,44 @@ "kind": "calls_package", "label": "Now" }, + { + "id": "function:neuroforge/internal/store:Store.UpsertRelationEvidence-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", + "from": "function:neuroforge/internal/store:Store.UpsertRelationEvidence", + "to": "function:neuroforge/internal/store:Store.commitLocked", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.UpsertRelationEvidence-\u003efunction:neuroforge/internal/store:Store.indexSynapseLocked:calls", + "from": "function:neuroforge/internal/store:Store.UpsertRelationEvidence", + "to": "function:neuroforge/internal/store:Store.indexSynapseLocked", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.UpsertRelationEvidence-\u003efunction:neuroforge/internal/store:appendUniqueRelation:calls", + "from": "function:neuroforge/internal/store:Store.UpsertRelationEvidence", + "to": "function:neuroforge/internal/store:appendUniqueRelation", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.UpsertRelationEvidence-\u003efunction:neuroforge/internal/store:edgeKey:calls", + "from": "function:neuroforge/internal/store:Store.UpsertRelationEvidence", + "to": "function:neuroforge/internal/store:edgeKey", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.UpsertRelationEvidence-\u003epackage:neuroforge/internal/vector:calls_package", + "from": "function:neuroforge/internal/store:Store.UpsertRelationEvidence", + "to": "package:neuroforge/internal/vector", + "kind": "calls_package", + "label": "Clamp" + }, + { + "id": "function:neuroforge/internal/store:Store.UpsertRelationEvidence-\u003epackage:time:calls_package", + "from": "function:neuroforge/internal/store:Store.UpsertRelationEvidence", + "to": "package:time", + "kind": "calls_package", + "label": "Now" + }, { "id": "function:neuroforge/internal/store:Store.UpsertSource-\u003efunction:neuroforge/internal/store:New:calls", "from": "function:neuroforge/internal/store:Store.UpsertSource", @@ -55602,6 +60499,26 @@ "kind": "calls_package", "label": "HasSuffix" }, + { + "id": "function:neuroforge/internal/store:Store.WorkersSnapshot-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", + "from": "function:neuroforge/internal/store:Store.WorkersSnapshot", + "to": "function:neuroforge/internal/store:ClusterLog.append", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.WorkersSnapshot-\u003epackage:sort:calls_package", + "from": "function:neuroforge/internal/store:Store.WorkersSnapshot", + "to": "package:sort", + "kind": "calls_package", + "label": "Slice" + }, + { + "id": "function:neuroforge/internal/store:Store.WorkersSnapshot-\u003epackage:time:calls_package", + "from": "function:neuroforge/internal/store:Store.WorkersSnapshot", + "to": "package:time", + "kind": "calls_package", + "label": "Now" + }, { "id": "function:neuroforge/internal/store:Store.appendClusterLogDecision-\u003efunction:neuroforge/internal/store:ClusterLog.AppendDecision:calls", "from": "function:neuroforge/internal/store:Store.appendClusterLogDecision", @@ -55685,6 +60602,18 @@ "to": "function:neuroforge/internal/store:ClusterLog.append", "kind": "calls" }, + { + "id": "function:neuroforge/internal/store:Store.applyWALEvent-\u003efunction:neuroforge/internal/store:Store.indexSynapseLocked:calls", + "from": "function:neuroforge/internal/store:Store.applyWALEvent", + "to": "function:neuroforge/internal/store:Store.indexSynapseLocked", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.applyWALEvent-\u003efunction:neuroforge/internal/store:Store.rebuildSynapseAdjLocked:calls", + "from": "function:neuroforge/internal/store:Store.applyWALEvent", + "to": "function:neuroforge/internal/store:Store.rebuildSynapseAdjLocked", + "kind": "calls" + }, { "id": "function:neuroforge/internal/store:Store.applyWALEvent-\u003efunction:neuroforge/internal/store:applyResearchEvent:calls", "from": "function:neuroforge/internal/store:Store.applyWALEvent", @@ -55867,6 +60796,44 @@ "to": "function:neuroforge/internal/store:cloneMemory", "kind": "calls" }, + { + "id": "function:neuroforge/internal/store:Store.graphExpandLocked-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", + "from": "function:neuroforge/internal/store:Store.graphExpandLocked", + "to": "function:neuroforge/internal/store:ClusterLog.append", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.graphExpandLocked-\u003efunction:neuroforge/internal/store:Store.fullMemoryForReadLocked:calls", + "from": "function:neuroforge/internal/store:Store.graphExpandLocked", + "to": "function:neuroforge/internal/store:Store.fullMemoryForReadLocked", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.graphExpandLocked-\u003efunction:neuroforge/internal/store:cloneMemory:calls", + "from": "function:neuroforge/internal/store:Store.graphExpandLocked", + "to": "function:neuroforge/internal/store:cloneMemory", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.graphExpandLocked-\u003efunction:neuroforge/internal/store:memorySearchable:calls", + "from": "function:neuroforge/internal/store:Store.graphExpandLocked", + "to": "function:neuroforge/internal/store:memorySearchable", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.graphExpandLocked-\u003epackage:neuroforge/internal/vector:calls_package", + "from": "function:neuroforge/internal/store:Store.graphExpandLocked", + "to": "package:neuroforge/internal/vector", + "kind": "calls_package", + "label": "Cosine" + }, + { + "id": "function:neuroforge/internal/store:Store.graphExpandLocked-\u003epackage:sort:calls_package", + "from": "function:neuroforge/internal/store:Store.graphExpandLocked", + "to": "package:sort", + "kind": "calls_package", + "label": "Slice" + }, { "id": "function:neuroforge/internal/store:Store.indexCountMatchesLocked-\u003efunction:neuroforge/internal/store:indexMode:calls", "from": "function:neuroforge/internal/store:Store.indexCountMatchesLocked", @@ -56111,6 +61078,31 @@ "kind": "calls_package", "label": "NewHNSW" }, + { + "id": "function:neuroforge/internal/store:Store.normalizeJobForClaimLocked-\u003efunction:neuroforge/internal/store:Store.commitLocked:calls", + "from": "function:neuroforge/internal/store:Store.normalizeJobForClaimLocked", + "to": "function:neuroforge/internal/store:Store.commitLocked", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.normalizeJobForClaimLocked-\u003efunction:neuroforge/internal/store:Store.dependencyStateLocked:calls", + "from": "function:neuroforge/internal/store:Store.normalizeJobForClaimLocked", + "to": "function:neuroforge/internal/store:Store.dependencyStateLocked", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.normalizeJobForClaimLocked-\u003efunction:neuroforge/internal/store:maxInt:calls", + "from": "function:neuroforge/internal/store:Store.normalizeJobForClaimLocked", + "to": "function:neuroforge/internal/store:maxInt", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.normalizeJobForClaimLocked-\u003epackage:time:calls_package", + "from": "function:neuroforge/internal/store:Store.normalizeJobForClaimLocked", + "to": "package:time", + "kind": "calls_package", + "label": "Duration" + }, { "id": "function:neuroforge/internal/store:Store.oldestHotLocked-\u003efunction:neuroforge/internal/store:previewHeap.Pop:calls", "from": "function:neuroforge/internal/store:Store.oldestHotLocked", @@ -56250,6 +61242,25 @@ "to": "function:neuroforge/internal/store:Store.indexProvenanceSourceLocked", "kind": "calls" }, + { + "id": "function:neuroforge/internal/store:Store.rebuildSynapseAdjLocked-\u003efunction:neuroforge/internal/store:Store.indexSynapseLocked:calls", + "from": "function:neuroforge/internal/store:Store.rebuildSynapseAdjLocked", + "to": "function:neuroforge/internal/store:Store.indexSynapseLocked", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.registerWorkerLocked-\u003efunction:neuroforge/internal/store:normalizeCapabilities:calls", + "from": "function:neuroforge/internal/store:Store.registerWorkerLocked", + "to": "function:neuroforge/internal/store:normalizeCapabilities", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:Store.registerWorkerLocked-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/store:Store.registerWorkerLocked", + "to": "package:strings", + "kind": "calls_package", + "label": "ToLower" + }, { "id": "function:neuroforge/internal/store:Store.replayWAL-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", "from": "function:neuroforge/internal/store:Store.replayWAL", @@ -56406,6 +61417,12 @@ "to": "function:neuroforge/internal/store:Store.fullMemoryForReadLocked", "kind": "calls" }, + { + "id": "function:neuroforge/internal/store:Store.searchVectorLocked-\u003efunction:neuroforge/internal/store:Store.graphExpandLocked:calls", + "from": "function:neuroforge/internal/store:Store.searchVectorLocked", + "to": "function:neuroforge/internal/store:Store.graphExpandLocked", + "kind": "calls" + }, { "id": "function:neuroforge/internal/store:Store.searchVectorLocked-\u003efunction:neuroforge/internal/store:cloneMemory:calls", "from": "function:neuroforge/internal/store:Store.searchVectorLocked", @@ -57112,6 +62129,12 @@ "kind": "calls_package", "label": "ReadFull" }, + { + "id": "function:neuroforge/internal/store:appendUniqueRelation-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", + "from": "function:neuroforge/internal/store:appendUniqueRelation", + "to": "function:neuroforge/internal/store:ClusterLog.append", + "kind": "calls" + }, { "id": "function:neuroforge/internal/store:appendUniqueString-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", "from": "function:neuroforge/internal/store:appendUniqueString", @@ -57210,6 +62233,13 @@ "kind": "calls_package", "label": "Errorf" }, + { + "id": "function:neuroforge/internal/store:capabilitiesContain-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/store:capabilitiesContain", + "to": "package:strings", + "kind": "calls_package", + "label": "ToLower" + }, { "id": "function:neuroforge/internal/store:cleanupOldIndexBases-\u003epackage:os:calls_package", "from": "function:neuroforge/internal/store:cleanupOldIndexBases", @@ -57329,6 +62359,13 @@ "to": "function:neuroforge/internal/store:serializeVectorColumns", "kind": "calls" }, + { + "id": "function:neuroforge/internal/store:goalFingerprint-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/store:goalFingerprint", + "to": "package:strings", + "kind": "calls_package", + "label": "ToLower" + }, { "id": "function:neuroforge/internal/store:hashSnapshotNode-\u003epackage:neuroforge/internal/vector:calls_package", "from": "function:neuroforge/internal/store:hashSnapshotNode", @@ -57483,6 +62520,26 @@ "to": "function:neuroforge/internal/store:New", "kind": "calls" }, + { + "id": "function:neuroforge/internal/store:normalizeCapabilities-\u003efunction:neuroforge/internal/store:ClusterLog.append:calls", + "from": "function:neuroforge/internal/store:normalizeCapabilities", + "to": "function:neuroforge/internal/store:ClusterLog.append", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:normalizeCapabilities-\u003epackage:sort:calls_package", + "from": "function:neuroforge/internal/store:normalizeCapabilities", + "to": "package:sort", + "kind": "calls_package", + "label": "Strings" + }, + { + "id": "function:neuroforge/internal/store:normalizeCapabilities-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/store:normalizeCapabilities", + "to": "package:strings", + "kind": "calls_package", + "label": "ToLower" + }, { "id": "function:neuroforge/internal/store:openClusterLog-\u003efunction:neuroforge/internal/store:ClusterLog.scan:calls", "from": "function:neuroforge/internal/store:openClusterLog", @@ -57751,6 +62808,19 @@ "to": "function:neuroforge/internal/store:paethByte", "kind": "calls" }, + { + "id": "function:neuroforge/internal/store:workerMatchesJob-\u003efunction:neuroforge/internal/store:capabilitiesContain:calls", + "from": "function:neuroforge/internal/store:workerMatchesJob", + "to": "function:neuroforge/internal/store:capabilitiesContain", + "kind": "calls" + }, + { + "id": "function:neuroforge/internal/store:workerMatchesJob-\u003epackage:strings:calls_package", + "from": "function:neuroforge/internal/store:workerMatchesJob", + "to": "package:strings", + "kind": "calls_package", + "label": "ToLower" + }, { "id": "function:neuroforge/internal/store:writeAtomic-\u003epackage:encoding/json:calls_package", "from": "function:neuroforge/internal/store:writeAtomic", @@ -59182,6 +64252,18 @@ "to": "route:GET /api/graph/ticket", "kind": "defines_route" }, + { + "id": "package:mega-control-\u003eroute:GET /api/neuroforge/graph-status:defines_route", + "from": "package:mega-control", + "to": "route:GET /api/neuroforge/graph-status", + "kind": "defines_route" + }, + { + "id": "package:mega-control-\u003eroute:GET /api/neuroforge/orchestrator:defines_route", + "from": "package:mega-control", + "to": "route:GET /api/neuroforge/orchestrator", + "kind": "defines_route" + }, { "id": "package:mega-control-\u003eroute:GET /api/status:defines_route", "from": "package:mega-control", @@ -59224,18 +64306,36 @@ "to": "file:platform/neuroforge/internal/brain/brain.go", "kind": "contains_file" }, + { + "id": "package:neuroforge/internal/brain-\u003efile:platform/neuroforge/internal/brain/distributed.go:contains_file", + "from": "package:neuroforge/internal/brain", + "to": "file:platform/neuroforge/internal/brain/distributed.go", + "kind": "contains_file" + }, { "id": "package:neuroforge/internal/brain-\u003efile:platform/neuroforge/internal/brain/goal_progress.go:contains_file", "from": "package:neuroforge/internal/brain", "to": "file:platform/neuroforge/internal/brain/goal_progress.go", "kind": "contains_file" }, + { + "id": "package:neuroforge/internal/brain-\u003efile:platform/neuroforge/internal/brain/orchestrator.go:contains_file", + "from": "package:neuroforge/internal/brain", + "to": "file:platform/neuroforge/internal/brain/orchestrator.go", + "kind": "contains_file" + }, { "id": "package:neuroforge/internal/brain-\u003efile:platform/neuroforge/internal/brain/policy.go:contains_file", "from": "package:neuroforge/internal/brain", "to": "file:platform/neuroforge/internal/brain/policy.go", "kind": "contains_file" }, + { + "id": "package:neuroforge/internal/brain-\u003efile:platform/neuroforge/internal/brain/research_quality.go:contains_file", + "from": "package:neuroforge/internal/brain", + "to": "file:platform/neuroforge/internal/brain/research_quality.go", + "kind": "contains_file" + }, { "id": "package:neuroforge/internal/brain-\u003efile:platform/neuroforge/internal/brain/research_trace.go:contains_file", "from": "package:neuroforge/internal/brain", @@ -59248,6 +64348,12 @@ "to": "file:platform/neuroforge/internal/brain/staging.go", "kind": "contains_file" }, + { + "id": "package:neuroforge/internal/brain-\u003efile:platform/neuroforge/internal/brain/staging_quality.go:contains_file", + "from": "package:neuroforge/internal/brain", + "to": "file:platform/neuroforge/internal/brain/staging_quality.go", + "kind": "contains_file" + }, { "id": "package:neuroforge/internal/brain-\u003efile:platform/neuroforge/internal/brain/v3.go:contains_file", "from": "package:neuroforge/internal/brain", @@ -59404,6 +64510,12 @@ "to": "route:GET /admin/api/export", "kind": "defines_route" }, + { + "id": "package:neuroforge/internal/httpapi-\u003eroute:GET /admin/api/graph/status:defines_route", + "from": "package:neuroforge/internal/httpapi", + "to": "route:GET /admin/api/graph/status", + "kind": "defines_route" + }, { "id": "package:neuroforge/internal/httpapi-\u003eroute:GET /admin/api/index/disk:defines_route", "from": "package:neuroforge/internal/httpapi", @@ -59458,6 +64570,18 @@ "to": "route:GET /admin/api/model-routing", "kind": "defines_route" }, + { + "id": "package:neuroforge/internal/httpapi-\u003eroute:GET /admin/api/orchestrator/jobs:defines_route", + "from": "package:neuroforge/internal/httpapi", + "to": "route:GET /admin/api/orchestrator/jobs", + "kind": "defines_route" + }, + { + "id": "package:neuroforge/internal/httpapi-\u003eroute:GET /admin/api/orchestrator/status:defines_route", + "from": "package:neuroforge/internal/httpapi", + "to": "route:GET /admin/api/orchestrator/status", + "kind": "defines_route" + }, { "id": "package:neuroforge/internal/httpapi-\u003eroute:GET /admin/api/research:defines_route", "from": "package:neuroforge/internal/httpapi", @@ -59554,6 +64678,18 @@ "to": "route:GET /api/v1/integrations/graph/research", "kind": "defines_route" }, + { + "id": "package:neuroforge/internal/httpapi-\u003eroute:GET /api/v1/integrations/graph/status:defines_route", + "from": "package:neuroforge/internal/httpapi", + "to": "route:GET /api/v1/integrations/graph/status", + "kind": "defines_route" + }, + { + "id": "package:neuroforge/internal/httpapi-\u003eroute:GET /api/v1/integrations/orchestrator/status:defines_route", + "from": "package:neuroforge/internal/httpapi", + "to": "route:GET /api/v1/integrations/orchestrator/status", + "kind": "defines_route" + }, { "id": "package:neuroforge/internal/httpapi-\u003eroute:GET /api/v1/learning-cycles:defines_route", "from": "package:neuroforge/internal/httpapi", @@ -59650,6 +64786,12 @@ "to": "route:POST /admin/api/consolidate", "kind": "defines_route" }, + { + "id": "package:neuroforge/internal/httpapi-\u003eroute:POST /admin/api/graph/backfill:defines_route", + "from": "package:neuroforge/internal/httpapi", + "to": "route:POST /admin/api/graph/backfill", + "kind": "defines_route" + }, { "id": "package:neuroforge/internal/httpapi-\u003eroute:POST /admin/api/index/disk/rebuild:defines_route", "from": "package:neuroforge/internal/httpapi", @@ -59668,6 +64810,18 @@ "to": "route:POST /admin/api/knowledge/search", "kind": "defines_route" }, + { + "id": "package:neuroforge/internal/httpapi-\u003eroute:POST /admin/api/orchestrator/jobs/{id}/cancel:defines_route", + "from": "package:neuroforge/internal/httpapi", + "to": "route:POST /admin/api/orchestrator/jobs/{id}/cancel", + "kind": "defines_route" + }, + { + "id": "package:neuroforge/internal/httpapi-\u003eroute:POST /admin/api/orchestrator/jobs/{id}/retry:defines_route", + "from": "package:neuroforge/internal/httpapi", + "to": "route:POST /admin/api/orchestrator/jobs/{id}/retry", + "kind": "defines_route" + }, { "id": "package:neuroforge/internal/httpapi-\u003eroute:POST /admin/api/provider-health:defines_route", "from": "package:neuroforge/internal/httpapi", @@ -59824,6 +64978,18 @@ "to": "route:POST /api/v1/worker/complete", "kind": "defines_route" }, + { + "id": "package:neuroforge/internal/httpapi-\u003eroute:POST /api/v1/worker/heartbeat:defines_route", + "from": "package:neuroforge/internal/httpapi", + "to": "route:POST /api/v1/worker/heartbeat", + "kind": "defines_route" + }, + { + "id": "package:neuroforge/internal/httpapi-\u003eroute:POST /api/v1/worker/register:defines_route", + "from": "package:neuroforge/internal/httpapi", + "to": "route:POST /api/v1/worker/register", + "kind": "defines_route" + }, { "id": "package:neuroforge/internal/httpapi-\u003eroute:POST /internal/v1/cluster/abort:defines_route", "from": "package:neuroforge/internal/httpapi", @@ -59932,6 +65098,12 @@ "to": "file:platform/neuroforge/internal/store/diskann.go", "kind": "contains_file" }, + { + "id": "package:neuroforge/internal/store-\u003efile:platform/neuroforge/internal/store/graph_index.go:contains_file", + "from": "package:neuroforge/internal/store", + "to": "file:platform/neuroforge/internal/store/graph_index.go", + "kind": "contains_file" + }, { "id": "package:neuroforge/internal/store-\u003efile:platform/neuroforge/internal/store/index_segments.go:contains_file", "from": "package:neuroforge/internal/store", @@ -59962,6 +65134,12 @@ "to": "file:platform/neuroforge/internal/store/observability.go", "kind": "contains_file" }, + { + "id": "package:neuroforge/internal/store-\u003efile:platform/neuroforge/internal/store/orchestrator.go:contains_file", + "from": "package:neuroforge/internal/store", + "to": "file:platform/neuroforge/internal/store/orchestrator.go", + "kind": "contains_file" + }, { "id": "package:neuroforge/internal/store-\u003efile:platform/neuroforge/internal/store/pagecache.go:contains_file", "from": "package:neuroforge/internal/store", @@ -60136,6 +65314,12 @@ "to": "function:neuroforge/internal/httpapi:Server.adminExport", "kind": "handles" }, + { + "id": "route:GET /admin/api/graph/status-\u003efunction:neuroforge/internal/httpapi:Server.adminGraphStatus:handles", + "from": "route:GET /admin/api/graph/status", + "to": "function:neuroforge/internal/httpapi:Server.adminGraphStatus", + "kind": "handles" + }, { "id": "route:GET /admin/api/index/disk-\u003efunction:neuroforge/internal/httpapi:Server.adminDiskANNStatus:handles", "from": "route:GET /admin/api/index/disk", @@ -60190,6 +65374,18 @@ "to": "function:neuroforge/internal/httpapi:Server.adminGetModelRouting", "kind": "handles" }, + { + "id": "route:GET /admin/api/orchestrator/jobs-\u003efunction:neuroforge/internal/httpapi:Server.adminOrchestratorJobs:handles", + "from": "route:GET /admin/api/orchestrator/jobs", + "to": "function:neuroforge/internal/httpapi:Server.adminOrchestratorJobs", + "kind": "handles" + }, + { + "id": "route:GET /admin/api/orchestrator/status-\u003efunction:neuroforge/internal/httpapi:Server.adminOrchestratorStatus:handles", + "from": "route:GET /admin/api/orchestrator/status", + "to": "function:neuroforge/internal/httpapi:Server.adminOrchestratorStatus", + "kind": "handles" + }, { "id": "route:GET /admin/api/research-\u003efunction:neuroforge/internal/httpapi:Server.adminResearchGet:handles", "from": "route:GET /admin/api/research", @@ -60412,6 +65608,18 @@ "to": "function:github.com/example/glpi-ai-agent/internal/web:Server.learningList", "kind": "handles" }, + { + "id": "route:GET /api/neuroforge/graph-status-\u003efunction:mega-control:server.handleNeuroForgeGraphStatus:handles", + "from": "route:GET /api/neuroforge/graph-status", + "to": "function:mega-control:server.handleNeuroForgeGraphStatus", + "kind": "handles" + }, + { + "id": "route:GET /api/neuroforge/orchestrator-\u003efunction:mega-control:server.handleNeuroForgeOrchestrator:handles", + "from": "route:GET /api/neuroforge/orchestrator", + "to": "function:mega-control:server.handleNeuroForgeOrchestrator", + "kind": "handles" + }, { "id": "route:GET /api/outcomes-\u003efunction:github.com/example/glpi-ai-agent/internal/web:Server.outcomeList:handles", "from": "route:GET /api/outcomes", @@ -60496,6 +65704,18 @@ "to": "function:neuroforge/internal/httpapi:Server.integrationResearchGraph", "kind": "handles" }, + { + "id": "route:GET /api/v1/integrations/graph/status-\u003efunction:neuroforge/internal/httpapi:Server.integrationGraphStatus:handles", + "from": "route:GET /api/v1/integrations/graph/status", + "to": "function:neuroforge/internal/httpapi:Server.integrationGraphStatus", + "kind": "handles" + }, + { + "id": "route:GET /api/v1/integrations/orchestrator/status-\u003efunction:neuroforge/internal/httpapi:Server.integrationOrchestratorStatus:handles", + "from": "route:GET /api/v1/integrations/orchestrator/status", + "to": "function:neuroforge/internal/httpapi:Server.integrationOrchestratorStatus", + "kind": "handles" + }, { "id": "route:GET /api/v1/learning-cycles-\u003efunction:neuroforge/internal/httpapi:Server.learningCycles:handles", "from": "route:GET /api/v1/learning-cycles", @@ -60616,6 +65836,12 @@ "to": "function:neuroforge/internal/httpapi:Server.adminConsolidate", "kind": "handles" }, + { + "id": "route:POST /admin/api/graph/backfill-\u003efunction:neuroforge/internal/httpapi:Server.adminGraphBackfill:handles", + "from": "route:POST /admin/api/graph/backfill", + "to": "function:neuroforge/internal/httpapi:Server.adminGraphBackfill", + "kind": "handles" + }, { "id": "route:POST /admin/api/index/disk/rebuild-\u003efunction:neuroforge/internal/httpapi:Server.adminDiskANNBuild:handles", "from": "route:POST /admin/api/index/disk/rebuild", @@ -60634,6 +65860,18 @@ "to": "function:neuroforge/internal/httpapi:Server.adminKnowledgeSearch", "kind": "handles" }, + { + "id": "route:POST /admin/api/orchestrator/jobs/{id}/cancel-\u003efunction:neuroforge/internal/httpapi:Server.adminOrchestratorCancel:handles", + "from": "route:POST /admin/api/orchestrator/jobs/{id}/cancel", + "to": "function:neuroforge/internal/httpapi:Server.adminOrchestratorCancel", + "kind": "handles" + }, + { + "id": "route:POST /admin/api/orchestrator/jobs/{id}/retry-\u003efunction:neuroforge/internal/httpapi:Server.adminOrchestratorRetry:handles", + "from": "route:POST /admin/api/orchestrator/jobs/{id}/retry", + "to": "function:neuroforge/internal/httpapi:Server.adminOrchestratorRetry", + "kind": "handles" + }, { "id": "route:POST /admin/api/provider-health-\u003efunction:neuroforge/internal/httpapi:Server.adminProviderHealth:handles", "from": "route:POST /admin/api/provider-health", @@ -60880,6 +66118,18 @@ "to": "function:neuroforge/internal/httpapi:Server.workerComplete", "kind": "handles" }, + { + "id": "route:POST /api/v1/worker/heartbeat-\u003efunction:neuroforge/internal/httpapi:Server.workerHeartbeat:handles", + "from": "route:POST /api/v1/worker/heartbeat", + "to": "function:neuroforge/internal/httpapi:Server.workerHeartbeat", + "kind": "handles" + }, + { + "id": "route:POST /api/v1/worker/register-\u003efunction:neuroforge/internal/httpapi:Server.workerRegister:handles", + "from": "route:POST /api/v1/worker/register", + "to": "function:neuroforge/internal/httpapi:Server.workerRegister", + "kind": "handles" + }, { "id": "route:POST /internal/v1/cluster/abort-\u003efunction:neuroforge/internal/httpapi:Server.clusterAbort:handles", "from": "route:POST /internal/v1/cluster/abort", @@ -61019,17 +66269,29 @@ "kind": "depends_on" }, { - "id": "service:neuroforge-worker-\u003eservice:neuroforge:depends_on", - "from": "service:neuroforge-worker", + "id": "service:neuroforge-worker-cpu-\u003eservice:neuroforge:depends_on", + "from": "service:neuroforge-worker-cpu", "to": "service:neuroforge", "kind": "depends_on" + }, + { + "id": "service:neuroforge-worker-gpu-\u003eservice:neuroforge:depends_on", + "from": "service:neuroforge-worker-gpu", + "to": "service:neuroforge", + "kind": "depends_on" + }, + { + "id": "service:neuroforge-worker-gpu-\u003eservice:ollama:depends_on", + "from": "service:neuroforge-worker-gpu", + "to": "service:ollama", + "kind": "depends_on" } ], "meta": { - "edges": 6633, + "edges": 7210, "format_version": 1, "generator": "go-ast+compose", "modules": 4, - "nodes": 1689 + "nodes": 1830 } } diff --git a/services/control/index.html b/services/control/index.html index bb3b7ee..2b6ab47 100644 --- a/services/control/index.html +++ b/services/control/index.html @@ -9,6 +9,8 @@

Service Status

10-Sekunden-Refresh · optionale Engineering-Komponenten beeinflussen Readiness nicht
+

Orchestrator & Knowledge Graph

Master/Subagent-Leases · Queue-Zustand · n:m-Graphgesundheit
+
@@ -29,7 +31,7 @@ const palette={ticket:'#7eb6ff',run:'#9a8cff',knowledge:'#70d5ae',validated_outc function nodeColor(n){if(n.status==='problem'||n.status==='error'||n.status==='fail'||n.status==='failed')return '#ff7f92';if(n.status==='superseded'||n.status==='archived')return '#65758a';if(n.status==='corrected'||n.status==='corroborated')return '#ffd16d';return palette[n.kind]||palette[n.group]||'#9ab0c8'} function resize(){let r=canvas.getBoundingClientRect(),d=devicePixelRatio||1;canvas.width=Math.round(r.width*d);canvas.height=Math.round(r.height*d);ctx.setTransform(d,0,0,d,0,0);draw()}addEventListener('resize',resize); async function jget(url){let r=await fetch(url,{cache:'no-store'});if(!r.ok)throw new Error(await r.text()||r.statusText);return r.json()} -async function loadStatus(){try{const [c,r]=await Promise.all([jget('/api/config'),jget('/api/status')]);config=c;mode.textContent=c.vector_backend;controlled.textContent=c.controlled_learning==='true'?'ON':'OFF';outcomeRetrieval.textContent=(c.outcome_retrieval==='true'?'ON':'OFF')+' · K='+c.outcome_retrieval_search_k+' · min '+c.outcome_retrieval_min_similarity;research.textContent=(c.research_enabled==='true'?'ON':'OFF')+' / '+(c.searxng_enabled==='true'?'SearXNG':'kein SearXNG')+' / '+(c.kb_staging_bridge==='true'?'Staging':'kein Staging');autonomy.textContent=c.autonomy_enabled==='true'?'ON':'OFF';services.innerHTML=(r.services||[]).map(s=>`

${esc(s.name)}${s.optional?' (optional)':''}

${s.ok?'ONLINE':s.optional?'OPTIONAL/OFFLINE':'PROBLEM'} · HTTP ${s.status||'–'} · ${s.latency_ms} ms
${s.public_url?`

Oberfläche öffnen

`:''}
${esc(JSON.stringify(s.detail||s.error||{},null,2))}
`).join('')}catch(e){services.innerHTML=`
Status nicht verfügbar: ${esc(e.message)}
`}} +async function loadStatus(){try{const [c,r,o,g]=await Promise.all([jget('/api/config'),jget('/api/status'),jget('/api/neuroforge/orchestrator'),jget('/api/neuroforge/graph-status')]);config=c;mode.textContent=c.vector_backend;controlled.textContent=c.controlled_learning==='true'?'ON':'OFF';outcomeRetrieval.textContent=(c.outcome_retrieval==='true'?'ON':'OFF')+' · K='+c.outcome_retrieval_search_k+' · min '+c.outcome_retrieval_min_similarity;research.textContent=(c.research_enabled==='true'?'ON':'OFF')+' / '+(c.searxng_enabled==='true'?'SearXNG':'kein SearXNG')+' / '+(c.kb_staging_bridge==='true'?'Staging':'kein Staging');autonomy.textContent=c.autonomy_enabled==='true'?'ON':'OFF';services.innerHTML=(r.services||[]).map(s=>`

${esc(s.name)}${s.optional?' (optional)':''}

${s.ok?'ONLINE':s.optional?'OPTIONAL/OFFLINE':'PROBLEM'} · HTTP ${s.status||'–'} · ${s.latency_ms} ms
${s.public_url?`

Oberfläche öffnen

`:''}
${esc(JSON.stringify(s.detail||s.error||{},null,2))}
`).join('');let os=o.status||{},gs=g.stats||{},workers=os.workers||[],online=workers.filter(x=>x.status==='online').length,stale=workers.filter(x=>x.status==='stale').length;orchestration.innerHTML=`

Master / Scheduler

${o.leader?'LEADER':'FOLLOWER'}
${esc(JSON.stringify({queued:os.queued||0,claimed:os.claimed||0,retrying:os.retrying||0,applying:os.applying||0,blocked:os.blocked||0,failed:os.failed||0},null,2))}

Subagents

${online} ONLINE · ${stale} stale
${esc(JSON.stringify(workers,null,2))}

Knowledge Graph

${gs.synapses||0} Kanten · Grad Ø ${Number(gs.average_degree||0).toFixed(2)}
${esc(JSON.stringify(gs,null,2))}

Graph Connectivity

${gs.multi_linked_memories||0} n:m-Knoten
${esc(JSON.stringify({linked:gs.linked_memories||0,isolated:gs.isolated_memories||0,max_degree:gs.max_degree||0,components:gs.connected_components||0,largest_component:gs.largest_component||0},null,2))}
`}catch(e){services.innerHTML=`
Status nicht verfügbar: ${esc(e.message)}
`;orchestration.innerHTML=`
Orchestrator/Graph-Status nicht verfügbar: ${esc(e.message)}
`}} async function loadRuns(){try{let xs=await jget('/api/graph/runs?limit=80');runSelect.innerHTML=''+xs.map(x=>``).join('')}catch(e){runSelect.innerHTML=''}} function endpoint(){let s=scope.value,b=budget.value,q=search.value.trim();if(s==='runtime')return '/api/graph/runtime';if(s==='ticket')return runSelect.value?'/api/graph/ticket?run_id='+encodeURIComponent(runSelect.value):'';if(s==='learning')return '/api/graph/learning?limit='+Math.min(500,Number(b)||180);if(s==='research')return '/api/graph/research?runs=8&max_events='+Math.min(800,Number(b)||320);if(s==='brain')return '/api/graph/brain?max_nodes='+Math.min(700,Number(b)||320);if(s==='engineering')return '/api/graph/engineering?max_nodes='+encodeURIComponent(b)+(q?'&q='+encodeURIComponent(q):'');if(s==='impact')return q?'/api/graph/impact?max_nodes='+encodeURIComponent(b)+'&depth=2&q='+encodeURIComponent(q):'';return ''} async function loadGraph(){let u=endpoint();if(!u){graph={nodes:[],edges:[],title:'Run auswählen'};applyGraph();return}graphMeta.textContent='lädt…';try{graph=await jget(u);selected='';neighborhood=null;applyGraph()}catch(e){graph={nodes:[],edges:[],title:'Fehler',meta:{error:e.message}};applyGraph();graphMeta.textContent='Fehler: '+e.message}} diff --git a/services/control/main.go b/services/control/main.go index 4b1dc00..d11d024 100644 --- a/services/control/main.go +++ b/services/control/main.go @@ -105,6 +105,8 @@ func main() { mux.HandleFunc("GET /api/graph/learning", s.handleLearningGraph) mux.HandleFunc("GET /api/graph/research", s.handleResearchGraph) mux.HandleFunc("GET /api/graph/brain", s.handleBrainGraph) + mux.HandleFunc("GET /api/neuroforge/orchestrator", s.handleNeuroForgeOrchestrator) + mux.HandleFunc("GET /api/neuroforge/graph-status", s.handleNeuroForgeGraphStatus) mux.HandleFunc("GET /api/graph/engineering", s.handleEngineeringGraph) mux.HandleFunc("GET /api/graph/impact", s.handleEngineeringImpact) mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) { @@ -144,6 +146,14 @@ func main() { } } +func (s *server) handleNeuroForgeOrchestrator(w http.ResponseWriter, r *http.Request) { + s.proxyJSON(w, r, s.neuroforgeURL+"/api/v1/integrations/orchestrator/status", bearerHeader(s.neuroforgeKey)) +} + +func (s *server) handleNeuroForgeGraphStatus(w http.ResponseWriter, r *http.Request) { + s.proxyJSON(w, r, s.neuroforgeURL+"/api/v1/integrations/graph/status", bearerHeader(s.neuroforgeKey)) +} + func validateControlSecrets() error { check := func(name string, min int) error { v := strings.TrimSpace(os.Getenv(name))