loki-mode 7.129.5 → 8.0.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 (74) hide show
  1. package/README.md +81 -5
  2. package/SKILL.md +66 -5
  3. package/VERSION +1 -1
  4. package/autonomy/app-runner.sh +37 -0
  5. package/autonomy/completion-council.sh +862 -28
  6. package/autonomy/council-v2.sh +39 -0
  7. package/autonomy/crash.sh +3 -2
  8. package/autonomy/grill.sh +42 -0
  9. package/autonomy/hooks/validate-bash.sh +301 -4
  10. package/autonomy/lib/cockpit-render.sh +8 -2
  11. package/autonomy/lib/cr-rematerialize.py +101 -4
  12. package/autonomy/lib/deadline.py +957 -0
  13. package/autonomy/lib/dependency-setup.sh +205 -0
  14. package/autonomy/lib/done-recognition.sh +45 -0
  15. package/autonomy/lib/efficiency_cost.py +13 -6
  16. package/autonomy/lib/no_mock_scan.py +793 -0
  17. package/autonomy/lib/prd-enrich.sh +42 -0
  18. package/autonomy/lib/proof-analytics-props.py +69 -0
  19. package/autonomy/lib/proof-generator.py +282 -95
  20. package/autonomy/lib/proof-template.html +15 -12
  21. package/autonomy/lib/proof-verify.py +151 -102
  22. package/autonomy/lib/requirements_contract.py +848 -0
  23. package/autonomy/lib/sdk-mode.sh +103 -0
  24. package/autonomy/lib/secret-scan.sh +139 -0
  25. package/autonomy/lib/spec-expand.sh +208 -0
  26. package/autonomy/lib/tree_digest.py +266 -0
  27. package/autonomy/lib/voter-agents.sh +58 -8
  28. package/autonomy/lib/workspace_diff.py +124 -0
  29. package/autonomy/loki +189 -17
  30. package/autonomy/playwright-verify.sh +501 -0
  31. package/autonomy/prd-checklist.sh +17 -8
  32. package/autonomy/provider-offer.sh +111 -0
  33. package/autonomy/run.sh +4417 -676
  34. package/autonomy/sandbox.sh +42 -0
  35. package/autonomy/spec-interrogation.sh +148 -5
  36. package/autonomy/spec.sh +118 -1
  37. package/autonomy/telemetry.sh +133 -7
  38. package/autonomy/verify.sh +107 -0
  39. package/bin/loki +110 -3
  40. package/completions/_loki +10 -0
  41. package/completions/loki.bash +1 -1
  42. package/dashboard/__init__.py +1 -1
  43. package/dashboard/audit.py +96 -7
  44. package/dashboard/build_supervisor.py +1619 -0
  45. package/dashboard/control.py +8 -0
  46. package/dashboard/prompt_optimizer.py +7 -0
  47. package/dashboard/server.py +577 -22
  48. package/dashboard/static/trust.html +1 -1
  49. package/docs/ARCHITECTURE-OVERVIEW.md +207 -0
  50. package/docs/AUTONOMI-ECOSYSTEM.md +107 -0
  51. package/docs/INSTALLATION.md +19 -2
  52. package/docs/MODEL-EQUIVALENCE-HARNESS-PLAN.md +70 -0
  53. package/docs/PLANS-INDEX.md +33 -0
  54. package/docs/PRIVACY.md +29 -1
  55. package/docs/SIGNED-RECEIPTS.md +102 -0
  56. package/docs/SONNET5-DEFAULT-PLAN.md +1 -1
  57. package/docs/V8-ACCEPTANCE-TRIAGE-2026-07-24.md +114 -0
  58. package/docs/V8-AGENT-SDK-PLAN.md +692 -0
  59. package/docs/V8-COMPLEXITY-AUDIT-2026-07-24.md +45 -0
  60. package/docs/V8-MAJOR-RELEASE-PLAN.md +137 -0
  61. package/docs/V8-OVERNIGHT-PLAN-2026-07-25.md +82 -0
  62. package/docs/V8-RUNTIME-TRUTH-2026-07-25.md +232 -0
  63. package/docs/V8-SDK-RESEARCH-RAW.md +129 -0
  64. package/docs/alternative-installations.md +4 -4
  65. package/loki-ts/dist/loki.js +674 -285
  66. package/loki-ts/package.json +2 -0
  67. package/mcp/__init__.py +1 -1
  68. package/package.json +7 -6
  69. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  70. package/providers/claude.sh +75 -0
  71. package/providers/codex.sh +85 -13
  72. package/references/sdk-mode.md +106 -0
  73. package/skills/model-selection.md +1 -1
  74. package/skills/quality-gates.md +22 -0
@@ -10,7 +10,9 @@ import asyncio
10
10
  import json
11
11
  import logging
12
12
  import os
13
+ import platform
13
14
  import subprocess
15
+ import sys
14
16
  import threading
15
17
  import time
16
18
  from collections import defaultdict
@@ -31,7 +33,7 @@ from fastapi import (
31
33
  WebSocketDisconnect,
32
34
  )
33
35
  from fastapi.middleware.cors import CORSMiddleware
34
- from fastapi.responses import JSONResponse, PlainTextResponse
36
+ from fastapi.responses import JSONResponse, PlainTextResponse, Response
35
37
  from pydantic import BaseModel, ConfigDict, Field, field_validator
36
38
  from sqlalchemy import select, update, delete
37
39
  from sqlalchemy.ext.asyncio import AsyncSession
@@ -54,6 +56,7 @@ from . import auth
54
56
  from . import audit
55
57
  from . import app_secrets as secrets_mod
56
58
  from . import telemetry as _telemetry
59
+ from . import build_supervisor as _build_execution
57
60
  from .control import atomic_write_json, find_skill_dir, is_process_running
58
61
  from .activity_logger import get_activity_logger
59
62
  from .api_v2 import (
@@ -404,6 +407,29 @@ class SessionInfo(BaseModel):
404
407
  log_file: str = ""
405
408
 
406
409
 
410
+ def _npm_execution_target() -> tuple[str, str, str]:
411
+ """Return the npm-native target used by provider and verification commands."""
412
+ os_name = {
413
+ "darwin": "darwin",
414
+ "linux": "linux",
415
+ "win32": "win32",
416
+ }.get(sys.platform, "")
417
+ cpu = {
418
+ "aarch64": "arm64",
419
+ "arm64": "arm64",
420
+ "amd64": "x64",
421
+ "x86_64": "x64",
422
+ }.get(platform.machine().lower(), "")
423
+ libc = ""
424
+ if os_name == "linux":
425
+ detected_libc = platform.libc_ver()[0].lower()
426
+ libc = {"glibc": "glibc", "musl": "musl"}.get(detected_libc, "")
427
+ return os_name, cpu, libc
428
+
429
+
430
+ _EXECUTION_OS, _EXECUTION_CPU, _EXECUTION_LIBC = _npm_execution_target()
431
+
432
+
407
433
  class StatusResponse(BaseModel):
408
434
  """Schema for system status response."""
409
435
  status: str
@@ -413,6 +439,11 @@ class StatusResponse(BaseModel):
413
439
  running_agents: int = 0
414
440
  pending_tasks: int = 0
415
441
  database_connected: bool = True
442
+ # Native dependency target for commands executed by this engine. Callers
443
+ # must treat blanks as unknown and skip cross-environment preparation.
444
+ execution_os: str = _EXECUTION_OS
445
+ execution_cpu: str = _EXECUTION_CPU
446
+ execution_libc: str = _EXECUTION_LIBC
416
447
  # File-based session fields
417
448
  phase: str = ""
418
449
  iteration: int = 0
@@ -3059,7 +3090,8 @@ async def get_session_model():
3059
3090
  default = _normalize_session_pin(os.environ.get("LOKI_SESSION_MODEL")) or "sonnet"
3060
3091
  # Resolve on the route the runner will actually take: override-path clamp when
3061
3092
  # an override file is present, session-pin tier route otherwise. This closes
3062
- # the task-568 stock-path gap (a "sonnet" pin dispatches opus).
3093
+ # the task-568 stock-path gap (a "sonnet" pin dispatches sonnet post-v7.104.0;
3094
+ # the gap it originally fixed was the pre-flip opus default).
3063
3095
  if override is not None:
3064
3096
  effective = _clamp_to_max_tier(override)
3065
3097
  else:
@@ -3274,6 +3306,9 @@ class StartBuildRequest(BaseModel):
3274
3306
  # The path is path-guarded against ALLOWED_WORKSPACE_ROOTS (see
3275
3307
  # _validate_workspace) -- it is NOT a free-form filesystem write target.
3276
3308
  workspace: Optional[str] = None
3309
+ # Stable SaaS build UUID. Required for workspace starts so a retried POST
3310
+ # returns the same durable execution instead of launching a second runner.
3311
+ request_id: Optional[str] = None
3277
3312
  # Start-time execution model (haiku|sonnet|opus; fable NOT accepted). When a
3278
3313
  # valid alias is supplied, the run is pinned to EXACTLY that model for every
3279
3314
  # iteration via the LOKI_CLAUDE_MODEL_{PLANNING,DEVELOPMENT,FAST} env triple
@@ -3292,6 +3327,21 @@ class StartBuildRequest(BaseModel):
3292
3327
  # execution stays on the chosen/default execution model. Absent/invalid -> no
3293
3328
  # advisor pin (reviewers use the account default).
3294
3329
  advisor_model: Optional[str] = None
3330
+ # Build profile (e.g. "simple-web"): exported as LOKI_BUILD_PROFILE into the
3331
+ # run env, where run.sh's loki_apply_build_profile helper (run.sh:562) maps it
3332
+ # to a set of LOKI_PHASE_* gate defaults (a simple landing page skips the
3333
+ # gates irrelevant to a static frontend while KEEPING E2E, code review, the
3334
+ # completion council, security, and accessibility). Absent -> unset -> the
3335
+ # full gate suite runs (byte-identical to before this field existed). Never
3336
+ # weakens the moat: the council + evidence/proof gate are not disable-able by
3337
+ # a profile.
3338
+ build_profile: Optional[str] = None
3339
+ # Optional runtime complexity tier. Unknown values are ignored so they can
3340
+ # never widen policy or become arbitrary environment input.
3341
+ complexity: Optional[str] = None
3342
+ # The provider's first-pass excellence prompt is default-on. A caller may
3343
+ # explicitly disable it for non-UI work such as CLI and library builds.
3344
+ first_pass_directive: Optional[bool] = None
3295
3345
 
3296
3346
  def validate_provider(self) -> None:
3297
3347
  """Validate provider is from the supported list.
@@ -3478,6 +3528,274 @@ def _project_run_active(loki_dir: _Path) -> Optional[int]:
3478
3528
  return None
3479
3529
 
3480
3530
 
3531
+ def _supervised_start_response(state: dict[str, Any], replay: bool = False) -> dict[str, Any]:
3532
+ """Build the additive start response shared by new and replayed starts."""
3533
+ execution_id = str(state.get("execution_id") or "")
3534
+ provider = str(state.get("provider") or "")
3535
+ return {
3536
+ "success": True,
3537
+ "message": (
3538
+ "Existing build execution returned"
3539
+ if replay
3540
+ else f"Build accepted with provider {provider}"
3541
+ ),
3542
+ "execution_id": execution_id,
3543
+ "state": str(state.get("state") or "accepted"),
3544
+ "status_url": f"/api/control/builds/{execution_id}",
3545
+ # Deprecated compatibility handle. Callers must use execution_id.
3546
+ "pid": int(state.get("supervisor_pid") or 0),
3547
+ "spec": str(state.get("spec") or ""),
3548
+ "provider": provider,
3549
+ "workspace": str(state.get("workspace") or ""),
3550
+ "run_id": str(state.get("run_id") or ""),
3551
+ "model": str(state.get("model") or ""),
3552
+ "advisor_model": str(state.get("advisor_model") or ""),
3553
+ "build_profile": str(state.get("build_profile") or ""),
3554
+ "complexity": str(state.get("complexity") or "auto"),
3555
+ "first_pass_directive": bool(state.get("first_pass_directive", True)),
3556
+ }
3557
+
3558
+
3559
+ def _reap_supervisor_process(process: subprocess.Popen[Any]) -> None:
3560
+ """Wait for a dashboard child so an exited supervisor cannot stay zombie."""
3561
+ try:
3562
+ process.wait()
3563
+ except Exception:
3564
+ logging.getLogger(__name__).debug(
3565
+ "Build supervisor reaper could not wait for child", exc_info=True
3566
+ )
3567
+
3568
+
3569
+ def _start_supervised_workspace_build(
3570
+ request: Request,
3571
+ body: StartBuildRequest,
3572
+ execution_id: str,
3573
+ workspace_dir: _Path,
3574
+ loki_dir: _Path,
3575
+ spec_content: bytes,
3576
+ run_sh: _Path,
3577
+ skill_dir: _Path,
3578
+ popen_env: dict[str, str],
3579
+ start_model: str,
3580
+ advisor_model: str,
3581
+ build_profile: Optional[str],
3582
+ complexity: str,
3583
+ first_pass_directive: bool,
3584
+ ) -> JSONResponse:
3585
+ """Start one durable workspace execution without the run.sh --bg wrapper."""
3586
+ spec_sha256 = _build_execution.sha256_bytes(spec_content)
3587
+ fingerprint_payload = json.dumps(
3588
+ {
3589
+ "workspace": str(workspace_dir),
3590
+ "spec_sha256": spec_sha256,
3591
+ "provider": body.provider,
3592
+ "parallel": body.parallel,
3593
+ "model": start_model,
3594
+ "advisor_model": advisor_model,
3595
+ "build_profile": build_profile or "",
3596
+ "complexity": complexity,
3597
+ "first_pass_directive": first_pass_directive,
3598
+ },
3599
+ sort_keys=True,
3600
+ separators=(",", ":"),
3601
+ ).encode("utf-8")
3602
+ request_fingerprint = _build_execution.sha256_bytes(fingerprint_payload)
3603
+
3604
+ created = False
3605
+ with _build_execution.execution_lock(execution_id):
3606
+ existing = _build_execution.read_state_unlocked(execution_id)
3607
+ if existing is not None:
3608
+ if existing.get("request_fingerprint") != request_fingerprint:
3609
+ raise HTTPException(
3610
+ status_code=409,
3611
+ detail="request_id is already bound to different build inputs",
3612
+ )
3613
+ return JSONResponse(
3614
+ status_code=200,
3615
+ content=_supervised_start_response(existing, replay=True),
3616
+ )
3617
+ if _build_execution.state_path(execution_id).exists():
3618
+ raise HTTPException(
3619
+ status_code=409,
3620
+ detail="Execution state exists but cannot be read safely",
3621
+ )
3622
+
3623
+ active_pid = _project_run_active(loki_dir)
3624
+ if active_pid is not None:
3625
+ detail = "A build is already running in this project"
3626
+ if active_pid > 0:
3627
+ detail += f" (PID {active_pid})"
3628
+ raise HTTPException(status_code=409, detail=detail)
3629
+
3630
+ try:
3631
+ spec_file = _build_execution.write_spec_snapshot_unlocked(
3632
+ execution_id, spec_content
3633
+ )
3634
+ except OSError as exc:
3635
+ raise HTTPException(
3636
+ status_code=500, detail=f"Could not persist spec snapshot: {exc}"
3637
+ ) from exc
3638
+
3639
+ if os.environ.get("LOKI_HOST_SEATBELT", "").strip().lower() in {
3640
+ "1",
3641
+ "true",
3642
+ "yes",
3643
+ "on",
3644
+ }:
3645
+ try:
3646
+ auth_status = _build_execution.confined_provider_auth_status(
3647
+ body.provider,
3648
+ workspace_dir,
3649
+ run_sh,
3650
+ spec_file,
3651
+ )
3652
+ except (OSError, RuntimeError, ValueError):
3653
+ auth_status = {
3654
+ "available": False,
3655
+ "classification": "provider_auth_probe_failed",
3656
+ "action": (
3657
+ "Check the controlled-local provider auth boundary, "
3658
+ "then retry."
3659
+ ),
3660
+ }
3661
+ if auth_status.get("available") is not True:
3662
+ raise HTTPException(
3663
+ status_code=503,
3664
+ detail={
3665
+ "code": "provider_auth_unavailable_in_confinement",
3666
+ "provider": body.provider,
3667
+ "reason": str(
3668
+ auth_status.get("classification") or "unknown"
3669
+ ),
3670
+ "action": str(
3671
+ auth_status.get("action")
3672
+ or "Authenticate the provider, then retry."
3673
+ ),
3674
+ },
3675
+ )
3676
+
3677
+ accepted_at = _build_execution.utc_now()
3678
+ state: dict[str, Any] = {
3679
+ "schema_version": _build_execution.SCHEMA_VERSION,
3680
+ "execution_id": execution_id,
3681
+ "request_fingerprint": request_fingerprint,
3682
+ "workspace": str(workspace_dir),
3683
+ "spec": str(spec_file),
3684
+ "spec_sha256": spec_sha256,
3685
+ "provider": body.provider,
3686
+ "parallel": body.parallel,
3687
+ "model": start_model,
3688
+ "advisor_model": advisor_model,
3689
+ "build_profile": build_profile or "",
3690
+ "complexity": complexity,
3691
+ "first_pass_directive": first_pass_directive,
3692
+ "state": "accepted",
3693
+ "accepted_at": accepted_at,
3694
+ "started_at": None,
3695
+ "exited_at": None,
3696
+ "finished_at": None,
3697
+ "supervisor_pid": None,
3698
+ "supervisor_birth_token": None,
3699
+ "runner_pid": None,
3700
+ "runner_pgid": None,
3701
+ "runner_sid": None,
3702
+ "runner_birth_token": None,
3703
+ "returncode": None,
3704
+ "exit_code": None,
3705
+ "signal": None,
3706
+ "termination_reason": None,
3707
+ "descendants": _build_execution.empty_descendant_outcome(),
3708
+ "run_id": "",
3709
+ "final_tree_sha256": "",
3710
+ "proof": _build_execution.empty_proof_binding(execution_id),
3711
+ }
3712
+ _build_execution.write_state_unlocked(execution_id, state)
3713
+
3714
+ supervisor_args = [
3715
+ sys.executable,
3716
+ "-m",
3717
+ "dashboard.build_supervisor",
3718
+ "--execution-id",
3719
+ execution_id,
3720
+ "--run-sh",
3721
+ str(run_sh),
3722
+ "--workspace",
3723
+ str(workspace_dir),
3724
+ "--loki-dir",
3725
+ str(loki_dir),
3726
+ "--spec",
3727
+ str(spec_file),
3728
+ "--provider",
3729
+ body.provider,
3730
+ ]
3731
+ if body.parallel:
3732
+ supervisor_args.append("--parallel")
3733
+
3734
+ popen_env["LOKI_OWN_SESSION"] = "1"
3735
+ popen_env["LOKI_SPEC_SHA256"] = spec_sha256
3736
+ supervisor_log = _build_execution.execution_dir(execution_id) / "supervisor.log"
3737
+ try:
3738
+ with _build_execution.open_private_log(supervisor_log) as log_handle:
3739
+ process = subprocess.Popen(
3740
+ supervisor_args,
3741
+ stdout=log_handle,
3742
+ stderr=subprocess.STDOUT,
3743
+ start_new_session=True,
3744
+ cwd=str(skill_dir),
3745
+ env=popen_env,
3746
+ )
3747
+ except (OSError, subprocess.SubprocessError) as exc:
3748
+ finished_at = _build_execution.utc_now()
3749
+ state.update(
3750
+ state="exited",
3751
+ exited_at=finished_at,
3752
+ finished_at=finished_at,
3753
+ exit_code=None,
3754
+ signal=None,
3755
+ termination_reason="launch_failed",
3756
+ launch_error=str(exc),
3757
+ )
3758
+ _build_execution.write_state_unlocked(execution_id, state)
3759
+ raise HTTPException(
3760
+ status_code=500, detail=f"Failed to start build supervisor: {exc}"
3761
+ ) from exc
3762
+
3763
+ state.update(
3764
+ supervisor_pid=process.pid,
3765
+ supervisor_birth_token=_build_execution.process_birth_token(process.pid),
3766
+ supervisor_log=str(supervisor_log),
3767
+ )
3768
+ _build_execution.write_state_unlocked(execution_id, state)
3769
+ threading.Thread(
3770
+ target=_reap_supervisor_process,
3771
+ args=(process,),
3772
+ name=f"build-supervisor-reaper-{execution_id[:8]}",
3773
+ daemon=True,
3774
+ ).start()
3775
+ created = True
3776
+
3777
+ if created:
3778
+ audit.log_event(
3779
+ action="start",
3780
+ resource_type="session",
3781
+ details={
3782
+ "source": "dashboard",
3783
+ "provider": body.provider,
3784
+ "spec": state["spec"],
3785
+ "execution_id": execution_id,
3786
+ "supervisor_pid": state["supervisor_pid"],
3787
+ "workspace": str(workspace_dir),
3788
+ "model": start_model,
3789
+ "advisor_model": advisor_model,
3790
+ },
3791
+ ip_address=request.client.host if request.client else None,
3792
+ )
3793
+ return JSONResponse(
3794
+ status_code=202,
3795
+ content=_supervised_start_response(state),
3796
+ )
3797
+
3798
+
3481
3799
  @app.post("/api/control/start", dependencies=[Depends(auth.require_scope("control"))])
3482
3800
  async def start_build(request: Request, body: StartBuildRequest):
3483
3801
  """Start a Loki Mode build from a spec, kicked off from the browser.
@@ -3515,11 +3833,21 @@ async def start_build(request: Request, body: StartBuildRequest):
3515
3833
  # When omitted, behavior is byte-identical to before: the active dashboard
3516
3834
  # project (the engine's own _get_loki_dir).
3517
3835
  workspace_dir: Optional[_Path] = None
3836
+ execution_id = ""
3518
3837
  if body.workspace and body.workspace.strip():
3519
3838
  try:
3520
3839
  workspace_dir = _validate_workspace(body.workspace)
3521
3840
  except ValueError as e:
3522
3841
  raise HTTPException(status_code=400, detail=str(e))
3842
+ if not body.request_id:
3843
+ raise HTTPException(
3844
+ status_code=400,
3845
+ detail="request_id is required for workspace builds",
3846
+ )
3847
+ try:
3848
+ execution_id = _build_execution.validate_execution_id(body.request_id)
3849
+ except ValueError as e:
3850
+ raise HTTPException(status_code=400, detail=str(e))
3523
3851
 
3524
3852
  if workspace_dir is not None:
3525
3853
  # The build's project dir IS the workspace; its .loki lives inside it.
@@ -3538,17 +3866,28 @@ async def start_build(request: Request, body: StartBuildRequest):
3538
3866
  project_dir = loki_dir.parent if loki_dir.name == ".loki" else _Path.cwd()
3539
3867
  project_dir = project_dir.resolve()
3540
3868
 
3541
- # Single-flight: refuse if a run is already active in this project.
3542
- active_pid = _project_run_active(loki_dir)
3543
- if active_pid is not None:
3544
- detail = "A build is already running in this project"
3545
- if active_pid > 0:
3546
- detail += f" (PID {active_pid})"
3547
- raise HTTPException(status_code=409, detail=detail)
3869
+ # Legacy no-workspace starts keep their existing single-flight behavior.
3870
+ # Workspace starts check under their execution lock below so a retried POST
3871
+ # can return its existing execution before the active-run guard fires.
3872
+ if workspace_dir is None:
3873
+ active_pid = _project_run_active(loki_dir)
3874
+ if active_pid is not None:
3875
+ detail = "A build is already running in this project"
3876
+ if active_pid > 0:
3877
+ detail += f" (PID {active_pid})"
3878
+ raise HTTPException(status_code=409, detail=detail)
3548
3879
 
3549
3880
  # Resolve the spec to a concrete, path-guarded file.
3881
+ spec_content: Optional[bytes] = None
3550
3882
  try:
3551
- if has_path:
3883
+ if workspace_dir is not None:
3884
+ if has_path:
3885
+ source_spec = _validate_prd_path(body.prd_path.strip(), project_dir)
3886
+ spec_content = source_spec.read_bytes()
3887
+ else:
3888
+ spec_content = body.prd_text.encode("utf-8")
3889
+ spec_file = None
3890
+ elif has_path:
3552
3891
  spec_file = _validate_prd_path(body.prd_path.strip(), project_dir)
3553
3892
  else:
3554
3893
  spec_file = _write_spec_text(body.prd_text, project_dir)
@@ -3563,14 +3902,6 @@ async def start_build(request: Request, body: StartBuildRequest):
3563
3902
  if not run_sh.exists():
3564
3903
  raise HTTPException(status_code=500, detail=f"run.sh not found at {run_sh}")
3565
3904
 
3566
- # Build args: mirror control.py:start_session (provider, optional parallel,
3567
- # background, then the spec path).
3568
- args = [str(run_sh), "--provider", body.provider]
3569
- if body.parallel:
3570
- args.append("--parallel")
3571
- args.append("--bg")
3572
- args.append(str(spec_file))
3573
-
3574
3905
  # When a workspace is given, pass an explicit env that PINS run.sh to the
3575
3906
  # workspace. cwd alone is not enough: `loki` exports LOKI_DIR (default
3576
3907
  # .loki) into the dashboard process, and run.sh resolves its workspace as
@@ -3584,12 +3915,28 @@ async def start_build(request: Request, body: StartBuildRequest):
3584
3915
  # haiku|sonnet|opus (no fable); invalid/absent -> "" -> no pin.
3585
3916
  start_model = _normalize_start_model(body.model)
3586
3917
  advisor_model = _normalize_start_model(body.advisor_model)
3918
+ # Build profile: a light allow-list (only known profiles are forwarded, so a
3919
+ # stray value can never disable a gate we did not intend). Absent -> no profile.
3920
+ build_profile = body.build_profile if body.build_profile in ("simple-web",) else None
3921
+ complexity = (
3922
+ body.complexity
3923
+ if body.complexity in ("auto", "simple", "standard", "complex")
3924
+ else "auto"
3925
+ )
3926
+ first_pass_directive = body.first_pass_directive is not False
3587
3927
 
3588
3928
  # Build a custom env only when we actually need to change something
3589
3929
  # (workspace pin, start-time model pin, or advisor pin). When nothing is set,
3590
3930
  # env stays None (inherit) so behavior is byte-identical to before.
3591
3931
  popen_env = None
3592
- if workspace_dir is not None or start_model or advisor_model:
3932
+ if (
3933
+ workspace_dir is not None
3934
+ or start_model
3935
+ or advisor_model
3936
+ or build_profile
3937
+ or complexity != "auto"
3938
+ or not first_pass_directive
3939
+ ):
3593
3940
  popen_env = dict(os.environ)
3594
3941
  if workspace_dir is not None:
3595
3942
  popen_env["LOKI_TARGET_DIR"] = str(workspace_dir)
@@ -3611,6 +3958,40 @@ async def start_build(request: Request, body: StartBuildRequest):
3611
3958
  if advisor_model:
3612
3959
  # Opt-in Opus (or other) judge for code review; execution model unchanged.
3613
3960
  popen_env["LOKI_ADVISOR_MODEL"] = advisor_model
3961
+ if build_profile:
3962
+ # run.sh's loki_apply_build_profile maps this to LOKI_PHASE_* gate defaults
3963
+ # (skips gates irrelevant to a static frontend; keeps E2E, code review,
3964
+ # council, security, accessibility). The moat gates cannot be disabled here.
3965
+ popen_env["LOKI_BUILD_PROFILE"] = build_profile
3966
+ if complexity != "auto":
3967
+ popen_env["LOKI_COMPLEXITY"] = complexity
3968
+ if not first_pass_directive:
3969
+ popen_env["LOKI_FIRST_PASS_EXCELLENCE"] = "0"
3970
+
3971
+ if workspace_dir is not None:
3972
+ return _start_supervised_workspace_build(
3973
+ request=request,
3974
+ body=body,
3975
+ execution_id=execution_id,
3976
+ workspace_dir=workspace_dir,
3977
+ loki_dir=loki_dir,
3978
+ spec_content=spec_content or b"",
3979
+ run_sh=run_sh,
3980
+ skill_dir=skill_dir,
3981
+ popen_env=popen_env,
3982
+ start_model=start_model,
3983
+ advisor_model=advisor_model,
3984
+ build_profile=build_profile,
3985
+ complexity=complexity,
3986
+ first_pass_directive=first_pass_directive,
3987
+ )
3988
+
3989
+ # Legacy behavior for local dashboard starts without a workspace.
3990
+ args = [str(run_sh), "--provider", body.provider]
3991
+ if body.parallel:
3992
+ args.append("--parallel")
3993
+ args.append("--bg")
3994
+ args.append(str(spec_file))
3614
3995
  try:
3615
3996
  process = subprocess.Popen(
3616
3997
  args,
@@ -3698,9 +4079,177 @@ async def start_build(request: Request, body: StartBuildRequest):
3698
4079
  # so the UI can confirm what the run was actually pinned to.
3699
4080
  "model": start_model,
3700
4081
  "advisor_model": advisor_model,
4082
+ "build_profile": build_profile or "",
4083
+ "complexity": complexity,
4084
+ "first_pass_directive": first_pass_directive,
3701
4085
  }
3702
4086
 
3703
4087
 
4088
+ def _execution_status_response(state: dict[str, Any]) -> dict[str, Any]:
4089
+ """Add fail-closed liveness facts to a durable execution snapshot."""
4090
+ result = dict(state)
4091
+ defaults: dict[str, Any] = {
4092
+ "schema_version": _build_execution.SCHEMA_VERSION,
4093
+ "state": "accepted",
4094
+ "accepted_at": None,
4095
+ "started_at": None,
4096
+ "exited_at": None,
4097
+ "finished_at": None,
4098
+ "supervisor_pid": None,
4099
+ "supervisor_birth_token": None,
4100
+ "runner_pid": None,
4101
+ "runner_pgid": None,
4102
+ "runner_sid": None,
4103
+ "runner_birth_token": None,
4104
+ "returncode": None,
4105
+ "exit_code": None,
4106
+ "signal": None,
4107
+ "termination_reason": None,
4108
+ "descendants": _build_execution.empty_descendant_outcome(),
4109
+ "run_id": "",
4110
+ "final_tree_sha256": "",
4111
+ "proof": _build_execution.empty_proof_binding(
4112
+ str(state.get("execution_id") or "")
4113
+ ),
4114
+ }
4115
+ for key, value in defaults.items():
4116
+ result.setdefault(key, value)
4117
+ raw_proof = result.get("proof")
4118
+ proof = dict(raw_proof) if isinstance(raw_proof, dict) else {}
4119
+ proof.pop("proof_path", None)
4120
+ proof_defaults = dict(defaults["proof"])
4121
+ proof_defaults.pop("proof_path", None)
4122
+ for key, value in proof_defaults.items():
4123
+ proof.setdefault(key, value)
4124
+ result["proof"] = proof
4125
+ supervisor_alive = _build_execution.process_identity_matches(
4126
+ state.get("supervisor_pid"), state.get("supervisor_birth_token")
4127
+ )
4128
+ runner_alive = _build_execution.process_identity_matches(
4129
+ state.get("runner_pid"), state.get("runner_birth_token")
4130
+ )
4131
+ result["supervisor_alive"] = supervisor_alive
4132
+ result["runner_alive"] = runner_alive
4133
+ if result.get("state") == "exited":
4134
+ result["lifecycle_health"] = "exited"
4135
+ result["terminal"] = True
4136
+ result["reconciliation_reason"] = ""
4137
+ elif supervisor_alive:
4138
+ result["lifecycle_health"] = (
4139
+ "running" if result.get("state") == "running" else "starting"
4140
+ )
4141
+ result["terminal"] = False
4142
+ result["reconciliation_reason"] = ""
4143
+ elif runner_alive:
4144
+ result["lifecycle_health"] = "runner_orphaned"
4145
+ result["terminal"] = True
4146
+ result["reconciliation_reason"] = "supervisor_lost"
4147
+ else:
4148
+ result["lifecycle_health"] = "supervisor_lost"
4149
+ result["terminal"] = True
4150
+ result["reconciliation_reason"] = "supervisor_lost"
4151
+ return result
4152
+
4153
+
4154
+ @app.get(
4155
+ "/api/control/builds/{execution_id}",
4156
+ dependencies=[Depends(auth.require_scope("read"))],
4157
+ )
4158
+ async def get_build_execution(execution_id: str):
4159
+ """Read server-owned build state by durable execution identity."""
4160
+ try:
4161
+ execution_id = _build_execution.validate_execution_id(execution_id)
4162
+ except ValueError as exc:
4163
+ raise HTTPException(status_code=400, detail=str(exc)) from exc
4164
+ state = _build_execution.read_state(execution_id)
4165
+ if state is None:
4166
+ raise HTTPException(status_code=404, detail="Build execution not found")
4167
+ return _execution_status_response(state)
4168
+
4169
+
4170
+ @app.get(
4171
+ "/api/control/builds/{execution_id}/proof",
4172
+ dependencies=[Depends(auth.require_scope("read"))],
4173
+ )
4174
+ async def get_build_execution_proof(execution_id: str):
4175
+ """Serve the immutable server-owned proof snapshot for one execution."""
4176
+ try:
4177
+ execution_id = _build_execution.validate_execution_id(execution_id)
4178
+ except ValueError as exc:
4179
+ raise HTTPException(status_code=400, detail=str(exc)) from exc
4180
+ state = _build_execution.read_state(execution_id)
4181
+ if state is None:
4182
+ raise HTTPException(status_code=404, detail="Build execution not found")
4183
+ proof = state.get("proof") if isinstance(state.get("proof"), dict) else {}
4184
+ expected_sha = str(proof.get("document_sha256") or "")
4185
+ snapshot = _build_execution.execution_dir(execution_id) / "proof.json"
4186
+ try:
4187
+ raw = snapshot.read_bytes()
4188
+ except OSError as exc:
4189
+ raise HTTPException(status_code=404, detail="Proof snapshot not found") from exc
4190
+ if not expected_sha or _build_execution.sha256_bytes(raw) != expected_sha:
4191
+ raise HTTPException(
4192
+ status_code=409,
4193
+ detail="Proof snapshot integrity check failed",
4194
+ )
4195
+ return Response(
4196
+ content=raw,
4197
+ media_type="application/json",
4198
+ headers={"ETag": f'"sha256:{expected_sha}"'},
4199
+ )
4200
+
4201
+
4202
+ @app.post(
4203
+ "/api/control/builds/{execution_id}/stop",
4204
+ dependencies=[Depends(auth.require_scope("control"))],
4205
+ )
4206
+ async def stop_build_execution(request: Request, execution_id: str):
4207
+ """Ask the exact long-lived supervisor to stop its owned runner group."""
4208
+ if not _control_limiter.check("control"):
4209
+ raise HTTPException(status_code=429, detail="Rate limit exceeded")
4210
+ try:
4211
+ execution_id = _build_execution.validate_execution_id(execution_id)
4212
+ except ValueError as exc:
4213
+ raise HTTPException(status_code=400, detail=str(exc)) from exc
4214
+
4215
+ with _build_execution.execution_lock(execution_id):
4216
+ state = _build_execution.read_state_unlocked(execution_id)
4217
+ if state is None:
4218
+ raise HTTPException(status_code=404, detail="Build execution not found")
4219
+ if state.get("state") == "exited":
4220
+ return {
4221
+ "success": True,
4222
+ "message": "Build execution already exited",
4223
+ "execution_id": execution_id,
4224
+ "state": "exited",
4225
+ }
4226
+ if not _build_execution.process_identity_matches(
4227
+ state.get("supervisor_pid"), state.get("supervisor_birth_token")
4228
+ ):
4229
+ raise HTTPException(
4230
+ status_code=409,
4231
+ detail="Build supervisor identity cannot be verified",
4232
+ )
4233
+ state.setdefault("stop_requested_at", _build_execution.utc_now())
4234
+ _build_execution.write_state_unlocked(execution_id, state)
4235
+
4236
+ audit.log_event(
4237
+ action="stop",
4238
+ resource_type="session",
4239
+ details={"source": "api", "execution_id": execution_id},
4240
+ ip_address=request.client.host if request.client else None,
4241
+ )
4242
+ return JSONResponse(
4243
+ status_code=202,
4244
+ content={
4245
+ "success": True,
4246
+ "message": "Stop requested",
4247
+ "execution_id": execution_id,
4248
+ "state": str(state.get("state") or "running"),
4249
+ },
4250
+ )
4251
+
4252
+
3704
4253
  class RunningProjectStopRequest(BaseModel):
3705
4254
  """Schema for stopping a specific registered project from the switcher.
3706
4255
 
@@ -9942,7 +10491,10 @@ def get_prompt_versions():
9942
10491
  if not _read_limiter.check("prompt_versions"):
9943
10492
  raise HTTPException(status_code=429, detail="Rate limit exceeded")
9944
10493
  try:
9945
- return _get_prompt_optimizer().get_current_version()
10494
+ result = _get_prompt_optimizer().get_current_version()
10495
+ result["experimental"] = True
10496
+ result["note"] = "heuristic-only: returns content-hash version tracking, not an applied LLM optimization"
10497
+ return result
9946
10498
  except Exception as exc:
9947
10499
  logger.error("Prompt version read error: %s", exc)
9948
10500
  raise HTTPException(status_code=500, detail="Failed to read prompt versions")
@@ -9956,7 +10508,10 @@ def optimize_prompts(sessions: int = 10, dry_run: bool = True):
9956
10508
  if not _control_limiter.check("prompt_optimize"):
9957
10509
  raise HTTPException(status_code=429, detail="Rate limit exceeded")
9958
10510
  try:
9959
- return _get_prompt_optimizer().optimize(sessions=sessions, dry_run=dry_run)
10511
+ result = _get_prompt_optimizer().optimize(sessions=sessions, dry_run=dry_run)
10512
+ result["experimental"] = True
10513
+ result["note"] = "heuristic-only: returns content-hash version tracking, not an applied LLM optimization"
10514
+ return result
9960
10515
  except Exception as exc:
9961
10516
  logger.error("Prompt optimization error: %s", exc)
9962
10517
  raise HTTPException(status_code=500, detail="Failed to run prompt optimization")
@@ -9967,7 +10522,7 @@ def optimize_prompts(sessions: int = 10, dry_run: bool = True):
9967
10522
  # =============================================================================
9968
10523
  # Must be configured AFTER all API routes to avoid conflicts
9969
10524
 
9970
- from fastapi.responses import FileResponse, HTMLResponse, Response
10525
+ from fastapi.responses import FileResponse, HTMLResponse
9971
10526
 
9972
10527
  # Find static files in multiple possible locations
9973
10528
  DASHBOARD_DIR = os.path.dirname(os.path.abspath(__file__))