ltcai 9.6.0 → 9.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +98 -306
  2. package/auto_setup.py +11 -1
  3. package/docs/CHANGELOG.md +91 -0
  4. package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
  5. package/docs/DEVELOPMENT.md +1 -1
  6. package/docs/ONBOARDING.md +1 -1
  7. package/docs/PERFORMANCE.md +106 -0
  8. package/docs/TRUST_MODEL.md +1 -1
  9. package/docs/WHY_LATTICE.md +1 -1
  10. package/docs/kg-schema.md +1 -1
  11. package/kg_schema.py +11 -1
  12. package/knowledge_graph.py +12 -2
  13. package/knowledge_graph_api.py +11 -1
  14. package/lattice_brain/__init__.py +1 -1
  15. package/lattice_brain/graph/proactive.py +583 -0
  16. package/lattice_brain/graph/retrieval.py +301 -8
  17. package/lattice_brain/graph/retrieval_vector.py +145 -0
  18. package/lattice_brain/ingestion.py +757 -16
  19. package/lattice_brain/quality.py +44 -9
  20. package/lattice_brain/runtime/multi_agent.py +38 -2
  21. package/latticeai/__init__.py +1 -1
  22. package/latticeai/api/brain_intelligence.py +39 -2
  23. package/latticeai/api/change_proposals.py +32 -3
  24. package/latticeai/api/chat.py +17 -0
  25. package/latticeai/api/chat_helpers.py +48 -0
  26. package/latticeai/api/chat_stream.py +9 -1
  27. package/latticeai/api/local_files.py +120 -2
  28. package/latticeai/api/review_queue.py +66 -2
  29. package/latticeai/app_factory.py +3 -0
  30. package/latticeai/core/agent.py +193 -135
  31. package/latticeai/core/agent_eval.py +278 -4
  32. package/latticeai/core/legacy_compatibility.py +15 -1
  33. package/latticeai/core/marketplace.py +1 -1
  34. package/latticeai/core/workspace_os.py +1 -1
  35. package/latticeai/runtime/router_registration.py +2 -0
  36. package/latticeai/services/architecture_readiness.py +1 -1
  37. package/latticeai/services/automation_intelligence.py +151 -14
  38. package/latticeai/services/brain_intelligence.py +169 -1
  39. package/latticeai/services/change_proposals.py +56 -4
  40. package/latticeai/services/product_readiness.py +1 -1
  41. package/latticeai/services/review_queue.py +43 -2
  42. package/llm_router.py +10 -1
  43. package/local_knowledge_api.py +10 -1
  44. package/ltcai_cli.py +11 -1
  45. package/mcp_registry.py +10 -1
  46. package/p_reinforce.py +10 -1
  47. package/package.json +1 -1
  48. package/scripts/brain_quality_eval.py +1 -1
  49. package/scripts/check_current_release_docs.mjs +1 -1
  50. package/scripts/profile_kg.py +360 -0
  51. package/setup_wizard.py +10 -1
  52. package/src-tauri/Cargo.lock +1 -1
  53. package/src-tauri/Cargo.toml +1 -1
  54. package/src-tauri/tauri.conf.json +1 -1
  55. package/static/app/asset-manifest.json +11 -11
  56. package/static/app/assets/{Act-BkOEmwBi.js → Act-Dd3z8AzF.js} +2 -1
  57. package/static/app/assets/Brain-BMkgdWnI.js +321 -0
  58. package/static/app/assets/Capture-D2Aw9gkv.js +1 -0
  59. package/static/app/assets/Library-Yreq-KW5.js +1 -0
  60. package/static/app/assets/System-CXNmmtEo.js +1 -0
  61. package/static/app/assets/{index-85wQvEie.css → index-7gY9t9Sd.css} +1 -1
  62. package/static/app/assets/index-CndfILiF.js +18 -0
  63. package/static/app/assets/primitives-DxsIXb6G.js +1 -0
  64. package/static/app/assets/textarea-DH7ne8VI.js +1 -0
  65. package/static/app/index.html +2 -2
  66. package/static/sw.js +1 -1
  67. package/static/app/assets/Brain-C9ITUsQ_.js +0 -321
  68. package/static/app/assets/Capture-C-ppTeud.js +0 -1
  69. package/static/app/assets/Library-CGQbgWTu.js +0 -1
  70. package/static/app/assets/System-djmj0n2_.js +0 -1
  71. package/static/app/assets/index-AF0-4XVv.js +0 -18
  72. package/static/app/assets/primitives-jbb2qv4Q.js +0 -1
  73. package/static/app/assets/textarea-CFoo0OxJ.js +0 -1
@@ -319,28 +319,8 @@ class SingleAgentRuntime:
319
319
  parse_failures = 0
320
320
 
321
321
  for _ in range(budget):
322
- corrections_hint = (
323
- "\n\nCritic corrections from previous attempt:\n"
324
- + "\n".join(f"- {c}" for c in ctx.corrections)
325
- ) if ctx.corrections else ""
326
-
327
322
  request_workspace = getattr(req, "workspace_id", None)
328
- recent_kwargs = {
329
- "conversation_id": req.conversation_id,
330
- "user_email": current_user or None,
331
- }
332
- if request_workspace is not None:
333
- recent_kwargs["workspace_id"] = request_workspace
334
- recent_conversation = d.recent_chat_context(**recent_kwargs) or "(none)"
335
- context = (
336
- f"{d.executor_prompt}\n\n"
337
- f"[LANGUAGE HINT: {lang_hint}]\n"
338
- f"Workspace root: {d.agent_root}\n\n"
339
- f"PLAN:\n{json.dumps(ctx.plan, ensure_ascii=False)}\n\n"
340
- f"Recent conversation:\n{recent_conversation}\n\n"
341
- f"User request: {req.message}{corrections_hint}\n\n"
342
- f"Execution transcript:\n{json.dumps(ctx.transcript, ensure_ascii=False, indent=2)}"
343
- )
323
+ context = self._executor_context(ctx, req, lang_hint, current_user, request_workspace)
344
324
  raw = await d.generate_as(
345
325
  model_id,
346
326
  message="Execute the next step.",
@@ -352,33 +332,8 @@ class SingleAgentRuntime:
352
332
  ctx.trace.repair("execute", repairs=exec_repairs)
353
333
  except ValueError as exc:
354
334
  parse_failures += 1
355
- ctx.transcript.append({
356
- "state": AgentState.EXECUTING.value, "action": "parse_error",
357
- "raw": str(raw)[:400], "error": str(exc),
358
- })
359
- if parse_failures >= 3:
360
- ctx.trace.parse_error("execute", error=str(exc), recovered=False)
335
+ if self._note_parse_failure(ctx, raw, exc, parse_failures):
361
336
  break
362
- ctx.trace.parse_error("execute", error=str(exc), recovered=True)
363
- # Weak models often need one concrete reminder of the wire
364
- # format; feed it through the corrections channel and retry
365
- # instead of aborting the whole run on the first slip.
366
- hint = (
367
- 'Your last reply was not a single JSON action object. Reply with '
368
- 'EXACTLY one JSON object like {"thoughts": "...", "action": '
369
- '"tool_name", "args": {...}} and nothing else.'
370
- )
371
- if parse_failures >= 2:
372
- # Escalate: name the valid tools so the model stops
373
- # inventing action names or prose.
374
- valid = ", ".join(sorted(d.tool_governance.keys()))
375
- hint = (
376
- f"{hint} Valid action values are: {valid}, final. "
377
- 'Use {"action": "final", "message": "..."} to finish.'
378
- )
379
- if hint not in ctx.corrections:
380
- ctx.corrections.append(hint)
381
- ctx.trace.correction("execute", hint=hint)
382
337
  continue
383
338
 
384
339
  name = action.get("action")
@@ -402,14 +357,7 @@ class SingleAgentRuntime:
402
357
  return
403
358
 
404
359
  # Loop guard
405
- exec_steps = [s for s in ctx.transcript if s.get("state") == AgentState.EXECUTING.value]
406
- last = exec_steps[-1] if exec_steps else None
407
- if (
408
- name in d.file_create_actions and last
409
- and last.get("action") == name
410
- and (last.get("args") or {}) == args
411
- and "result" in last
412
- ):
360
+ if self._is_repeated_create(ctx, name, args):
413
361
  ctx.transcript.append({
414
362
  "state": AgentState.EXECUTING.value, "action": name,
415
363
  "error": "LOOP_DETECTED: identical action+args repeated — halted.",
@@ -428,93 +376,203 @@ class SingleAgentRuntime:
428
376
  policy = d.policy_for(name, args)
429
377
  risk = d.risk_level(policy)
430
378
 
431
- # Central change-class governance: create-new runs with minimal
432
- # friction, change/delete-existing becomes a review proposal.
433
- governor_allows_additive = False
434
- if d.change_governor is not None:
435
- verdict = d.change_governor.review(
436
- name, args, policy=dict(policy),
437
- user_email=current_user, workspace_id=request_workspace,
438
- )
439
- if verdict is not None and verdict.get("decision") == "proposed":
440
- proposal = verdict.get("proposal") or {}
441
- ctx.trace.tool("execute", name=name, outcome="proposed", risk=risk)
442
- ctx.transcript.append({
443
- "state": AgentState.EXECUTING.value, "action": name,
444
- "thoughts": thoughts, "args": {k: v for k, v in args.items() if k != "content"},
445
- "risk": risk, "governance": dict(policy),
446
- "result": {
447
- "proposed": True,
448
- "proposal_id": proposal.get("id"),
449
- "note": "기존 내용을 바꾸는 작업이라 변경 제안으로 저장했습니다. 검토함에서 승인하면 적용됩니다.",
450
- },
451
- })
452
- d.audit(
453
- "agent_change_proposed", user_email=current_user,
454
- action=name, proposal_id=proposal.get("id"),
455
- change_class=(verdict.get("classification") or {}).get("change_class"),
456
- )
457
- continue
458
- governor_allows_additive = (
459
- verdict is not None and verdict.get("decision") == "allow_additive"
460
- )
461
-
462
- if policy["risk"] == "destructive":
463
- ctx.trace.tool("execute", name=name, outcome="blocked_destructive", risk=risk)
464
- ctx.transcript.append({
465
- "state": AgentState.EXECUTING.value, "action": name,
466
- "thoughts": thoughts, "args": args, "risk": risk,
467
- "governance": dict(policy),
468
- "error": f"BLOCKED: destructive action '{name}' not permitted in agent mode.",
469
- })
470
- d.audit(
471
- "agent_blocked", user_email=current_user, source=getattr(req, "source", None) or "agent",
472
- action=name, reason="destructive", governance=dict(policy),
473
- )
379
+ proposed, governor_allows_additive = self._governor_review(
380
+ ctx, name, thoughts, args, policy, risk, current_user, request_workspace,
381
+ conversation_id=getattr(req, "conversation_id", None),
382
+ )
383
+ if proposed:
474
384
  continue
475
385
 
476
- if not policy["auto_approve"] and not ctx.approved_by_human and not governor_allows_additive:
477
- d.audit(
478
- "agent_exec", user_email=current_user, source=getattr(req, "source", None) or "agent",
479
- state=AgentState.EXECUTING.value, action=name, risk=risk,
480
- shell=policy["shell"], network=policy["network"],
481
- destructive=policy["destructive"], sandbox=policy["sandbox"],
482
- rollback=policy["rollback"],
483
- args={k: v for k, v in args.items() if k != "content"},
484
- )
485
- ctx.trace.tool("execute", name=name, outcome="blocked_approval", risk=risk)
486
- ctx.transcript.append({
487
- "state": AgentState.EXECUTING.value, "action": name,
488
- "thoughts": thoughts, "args": args, "risk": risk,
489
- "governance": dict(policy),
490
- "error": f"BLOCKED: action '{name}' requires explicit approval.",
491
- })
386
+ if self._blocked_by_gates(
387
+ ctx, req, name, thoughts, args, policy, risk,
388
+ current_user, governor_allows_additive,
389
+ ):
492
390
  continue
493
391
 
494
- try:
495
- d.check_role(name, current_user)
496
- # Shared tool lifecycle: pre_tool (may block) → execute → post_tool.
497
- result = dispatch_tool(
498
- d.hooks, name, args,
499
- lambda: d.execute_tool(name, args),
500
- user_email=current_user, source="agent",
501
- )
502
- ctx.trace.tool("execute", name=name, outcome="ok", risk=risk)
503
- ctx.transcript.append({
504
- "state": AgentState.EXECUTING.value, "action": name,
505
- "thoughts": thoughts, "args": args,
506
- "risk": risk, "governance": dict(policy), "result": result,
507
- })
508
- except (ToolError, KeyError, TypeError, PermissionError) as exc:
509
- ctx.trace.tool("execute", name=name, outcome="error", risk=risk)
510
- ctx.transcript.append({
511
- "state": AgentState.EXECUTING.value, "action": name,
512
- "thoughts": thoughts, "args": args,
513
- "risk": risk, "governance": dict(policy), "error": str(exc),
514
- })
392
+ self._dispatch_step(ctx, name, thoughts, args, policy, risk, current_user)
515
393
 
516
394
  ctx.state = AgentState.VERIFYING
517
395
 
396
+ def _executor_context(
397
+ self, ctx: AgentRunContext, req: Any, lang_hint: str,
398
+ current_user: str, request_workspace: Optional[str],
399
+ ) -> str:
400
+ """Assemble one executor turn's prompt (plan, corrections, recent chat)."""
401
+ d = self.deps
402
+ corrections_hint = (
403
+ "\n\nCritic corrections from previous attempt:\n"
404
+ + "\n".join(f"- {c}" for c in ctx.corrections)
405
+ ) if ctx.corrections else ""
406
+
407
+ recent_kwargs = {
408
+ "conversation_id": req.conversation_id,
409
+ "user_email": current_user or None,
410
+ }
411
+ if request_workspace is not None:
412
+ recent_kwargs["workspace_id"] = request_workspace
413
+ recent_conversation = d.recent_chat_context(**recent_kwargs) or "(none)"
414
+ return (
415
+ f"{d.executor_prompt}\n\n"
416
+ f"[LANGUAGE HINT: {lang_hint}]\n"
417
+ f"Workspace root: {d.agent_root}\n\n"
418
+ f"PLAN:\n{json.dumps(ctx.plan, ensure_ascii=False)}\n\n"
419
+ f"Recent conversation:\n{recent_conversation}\n\n"
420
+ f"User request: {req.message}{corrections_hint}\n\n"
421
+ f"Execution transcript:\n{json.dumps(ctx.transcript, ensure_ascii=False, indent=2)}"
422
+ )
423
+
424
+ def _note_parse_failure(
425
+ self, ctx: AgentRunContext, raw: Any, exc: ValueError, parse_failures: int,
426
+ ) -> bool:
427
+ """Record one executor parse slip; True when the run should stop retrying."""
428
+ ctx.transcript.append({
429
+ "state": AgentState.EXECUTING.value, "action": "parse_error",
430
+ "raw": str(raw)[:400], "error": str(exc),
431
+ })
432
+ if parse_failures >= 3:
433
+ ctx.trace.parse_error("execute", error=str(exc), recovered=False)
434
+ return True
435
+ ctx.trace.parse_error("execute", error=str(exc), recovered=True)
436
+ # Weak models often need one concrete reminder of the wire
437
+ # format; feed it through the corrections channel and retry
438
+ # instead of aborting the whole run on the first slip.
439
+ hint = (
440
+ 'Your last reply was not a single JSON action object. Reply with '
441
+ 'EXACTLY one JSON object like {"thoughts": "...", "action": '
442
+ '"tool_name", "args": {...}} and nothing else.'
443
+ )
444
+ if parse_failures >= 2:
445
+ # Escalate: name the valid tools so the model stops
446
+ # inventing action names or prose.
447
+ valid = ", ".join(sorted(self.deps.tool_governance.keys()))
448
+ hint = (
449
+ f"{hint} Valid action values are: {valid}, final. "
450
+ 'Use {"action": "final", "message": "..."} to finish.'
451
+ )
452
+ if hint not in ctx.corrections:
453
+ ctx.corrections.append(hint)
454
+ ctx.trace.correction("execute", hint=hint)
455
+ return False
456
+
457
+ def _is_repeated_create(self, ctx: AgentRunContext, name: Any, args: dict) -> bool:
458
+ """Loop guard: the same file-create action+args re-issued right after a result."""
459
+ exec_steps = [s for s in ctx.transcript if s.get("state") == AgentState.EXECUTING.value]
460
+ last = exec_steps[-1] if exec_steps else None
461
+ return bool(
462
+ name in self.deps.file_create_actions and last
463
+ and last.get("action") == name
464
+ and (last.get("args") or {}) == args
465
+ and "result" in last
466
+ )
467
+
468
+ def _governor_review(
469
+ self, ctx: AgentRunContext, name: str, thoughts: str, args: dict,
470
+ policy: dict, risk: str, current_user: str, request_workspace: Optional[str],
471
+ conversation_id: Optional[str] = None,
472
+ ) -> Tuple[bool, bool]:
473
+ """Central change-class governance: create-new runs with minimal
474
+ friction, change/delete-existing becomes a review proposal.
475
+
476
+ Returns ``(proposed, governor_allows_additive)``: ``proposed`` means the
477
+ step was staged as a proposal (skip execution); ``allows_additive`` lets
478
+ an additive create pass the classic approval gate.
479
+ """
480
+ d = self.deps
481
+ if d.change_governor is None:
482
+ return False, False
483
+ verdict = d.change_governor.review(
484
+ name, args, policy=dict(policy),
485
+ user_email=current_user, workspace_id=request_workspace,
486
+ conversation_id=conversation_id,
487
+ )
488
+ if verdict is not None and verdict.get("decision") == "proposed":
489
+ proposal = verdict.get("proposal") or {}
490
+ ctx.trace.tool("execute", name=name, outcome="proposed", risk=risk)
491
+ ctx.transcript.append({
492
+ "state": AgentState.EXECUTING.value, "action": name,
493
+ "thoughts": thoughts, "args": {k: v for k, v in args.items() if k != "content"},
494
+ "risk": risk, "governance": dict(policy),
495
+ "result": {
496
+ "proposed": True,
497
+ "proposal_id": proposal.get("id"),
498
+ "note": "기존 내용을 바꾸는 작업이라 변경 제안으로 저장했습니다. 검토함에서 승인하면 적용됩니다.",
499
+ },
500
+ })
501
+ d.audit(
502
+ "agent_change_proposed", user_email=current_user,
503
+ action=name, proposal_id=proposal.get("id"),
504
+ change_class=(verdict.get("classification") or {}).get("change_class"),
505
+ )
506
+ return True, False
507
+ return False, (verdict is not None and verdict.get("decision") == "allow_additive")
508
+
509
+ def _blocked_by_gates(
510
+ self, ctx: AgentRunContext, req: Any, name: str, thoughts: str, args: dict,
511
+ policy: dict, risk: str, current_user: str, governor_allows_additive: bool,
512
+ ) -> bool:
513
+ """Classic destructive / explicit-approval gates; True when the step was blocked."""
514
+ d = self.deps
515
+ if policy["risk"] == "destructive":
516
+ ctx.trace.tool("execute", name=name, outcome="blocked_destructive", risk=risk)
517
+ ctx.transcript.append({
518
+ "state": AgentState.EXECUTING.value, "action": name,
519
+ "thoughts": thoughts, "args": args, "risk": risk,
520
+ "governance": dict(policy),
521
+ "error": f"BLOCKED: destructive action '{name}' not permitted in agent mode.",
522
+ })
523
+ d.audit(
524
+ "agent_blocked", user_email=current_user, source=getattr(req, "source", None) or "agent",
525
+ action=name, reason="destructive", governance=dict(policy),
526
+ )
527
+ return True
528
+
529
+ if not policy["auto_approve"] and not ctx.approved_by_human and not governor_allows_additive:
530
+ d.audit(
531
+ "agent_exec", user_email=current_user, source=getattr(req, "source", None) or "agent",
532
+ state=AgentState.EXECUTING.value, action=name, risk=risk,
533
+ shell=policy["shell"], network=policy["network"],
534
+ destructive=policy["destructive"], sandbox=policy["sandbox"],
535
+ rollback=policy["rollback"],
536
+ args={k: v for k, v in args.items() if k != "content"},
537
+ )
538
+ ctx.trace.tool("execute", name=name, outcome="blocked_approval", risk=risk)
539
+ ctx.transcript.append({
540
+ "state": AgentState.EXECUTING.value, "action": name,
541
+ "thoughts": thoughts, "args": args, "risk": risk,
542
+ "governance": dict(policy),
543
+ "error": f"BLOCKED: action '{name}' requires explicit approval.",
544
+ })
545
+ return True
546
+ return False
547
+
548
+ def _dispatch_step(
549
+ self, ctx: AgentRunContext, name: str, thoughts: str, args: dict,
550
+ policy: dict, risk: str, current_user: str,
551
+ ) -> None:
552
+ """Role check + shared tool lifecycle, recorded on the transcript either way."""
553
+ d = self.deps
554
+ try:
555
+ d.check_role(name, current_user)
556
+ # Shared tool lifecycle: pre_tool (may block) → execute → post_tool.
557
+ result = dispatch_tool(
558
+ d.hooks, name, args,
559
+ lambda: d.execute_tool(name, args),
560
+ user_email=current_user, source="agent",
561
+ )
562
+ ctx.trace.tool("execute", name=name, outcome="ok", risk=risk)
563
+ ctx.transcript.append({
564
+ "state": AgentState.EXECUTING.value, "action": name,
565
+ "thoughts": thoughts, "args": args,
566
+ "risk": risk, "governance": dict(policy), "result": result,
567
+ })
568
+ except (ToolError, KeyError, TypeError, PermissionError) as exc:
569
+ ctx.trace.tool("execute", name=name, outcome="error", risk=risk)
570
+ ctx.transcript.append({
571
+ "state": AgentState.EXECUTING.value, "action": name,
572
+ "thoughts": thoughts, "args": args,
573
+ "risk": risk, "governance": dict(policy), "error": str(exc),
574
+ })
575
+
518
576
  # ── VERIFY ───────────────────────────────────────────────────────
519
577
  async def verify(
520
578
  self, ctx: AgentRunContext, req: Any, lang_hint: str, current_user: str,