Add browser dashboard, run tracing, and logged navigation decisions

- dashboard.py: stdlib http.server + JSON API (tree/trace/dream/status,
  POST submit/relabel); static/ single-page Redux-DevTools-style inspector
- trace.py: runs.jsonl lifecycle events keyed by run_id
- scheduler/skills/skill: every decision carries run_id; navigation choices
  now logged (navigate:category/leaf); requeues stamp meta.parent_run
- cli: 'dashboard' subcommand; config.json untracked per-machine (see
  config.example.json)
This commit is contained in:
Denton Social
2026-09-23 13:43:39 -05:00
parent b568e6bd48
commit 0db41241be
15 changed files with 1581 additions and 16 deletions
+30 -1
View File
@@ -23,6 +23,7 @@ from .llm import LLMClient
from .log import DecisionLog
from .scheduler import Scheduler
from .skills import build_skills, build_tree, tree_summary
from .trace import TraceLog
def load_config(path: str = "config.json") -> dict:
@@ -45,6 +46,7 @@ def build_scheduler(config: dict) -> tuple[Scheduler, dict]:
model=config.get("llm", {}).get("model", "qwen2.5:3b"),
)
log = DecisionLog(config.get("log", "data/decisions.jsonl"))
trace = TraceLog(config.get("trace", "data/runs.jsonl"))
scheduler = Scheduler(
engine=engine,
llm=llm,
@@ -52,6 +54,7 @@ def build_scheduler(config: dict) -> tuple[Scheduler, dict]:
config=config,
tau=float(config.get("tau", 0.6)),
max_reentries=int(config.get("max_reentries", 3)),
trace=trace,
)
return scheduler, config
@@ -116,6 +119,8 @@ def scripted(scheduler: Scheduler, path: str) -> None:
def main(argv: list[str] | None = None) -> int:
argv = list(argv) if argv is not None else list(sys.argv[1:])
config = load_config(_extract_config_path(argv))
parser = argparse.ArgumentParser(prog="semif-agent")
parser.add_argument("--config", default="config.json")
sub = parser.add_subparsers(dest="command")
@@ -131,8 +136,15 @@ def main(argv: list[str] | None = None) -> int:
relabel_p.add_argument("id")
relabel_p.add_argument("outcome")
dash_p = sub.add_parser("dashboard", help="run the local browser dashboard")
dash_p.add_argument("--port", type=int, default=int(config.get("dashboard", {}).get("port", 8765)))
dash_p.add_argument(
"--replay",
action="store_true",
help="replay mode: do not warm the decision engine",
)
args = parser.parse_args(argv)
config = load_config(args.config)
scheduler, config = build_scheduler(config)
if args.command == "run":
@@ -149,10 +161,27 @@ def main(argv: list[str] | None = None) -> int:
elif args.command == "relabel":
ok = scheduler.log.relabel(args.id, args.outcome)
print("relabeled." if ok else f"no row with id {args.id}")
elif args.command == "dashboard":
from .dashboard import serve
if args.replay:
print("replay mode: reading decision log + trace; engine not warmed.")
serve(scheduler, port=args.port)
else:
parser.print_help()
return 0
def _extract_config_path(argv: list[str]) -> str:
"""Pull --config out of argv before the full parser runs, so the dashboard
subcommand can read dashboard.port from config for its default."""
for index, arg in enumerate(argv):
if arg == "--config" and index + 1 < len(argv):
return argv[index + 1]
if arg.startswith("--config="):
return arg.split("=", 1)[1]
return "config.json"
if __name__ == "__main__":
sys.exit(main())