diff --git a/eslint.config.js b/eslint.config.js
index 96bc400..7ac0d58 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -8,5 +8,5 @@ export default defineConfig([
   { ignores: ["src-tauri/"]},
   { files: ["**/*.{js,mjs,cjs,jsx}"], plugins: { js }, extends: ["js/recommended"] },
   { files: ["**/*.{js,mjs,cjs,jsx}"], languageOptions: { globals: globals.browser } },
-  pluginReact.configs.flat.recommended,
+  pluginReact.configs.flat.recommended
 ]);
diff --git a/package-lock.json b/package-lock.json
index 5bc3316..5c892a2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,6 +12,7 @@
         "@tauri-apps/api": "^2",
         "@tauri-apps/plugin-opener": "^2",
         "@tauri-apps/plugin-store": "^2.2.0",
+        "clsx": "^2.1.1",
         "prop-types": "^15.8.1",
         "react": "^18.3.1",
         "react-dom": "^18.3.1",
@@ -24,6 +25,7 @@
         "@tailwindcss/vite": "^4.1.6",
         "@tauri-apps/cli": "^2",
         "@types/bun": "latest",
+        "@types/json-schema": "^7.0.15",
         "@typescript-eslint/eslint-plugin": "^8.36.0",
         "@typescript-eslint/parser": "^8.36.0",
         "@vitejs/plugin-react": "^4.3.4",
@@ -3795,6 +3797,15 @@
         "node": ">=6"
       }
     },
+    "node_modules/clsx": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+      "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/color-convert": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
diff --git a/package.json b/package.json
index 3a70da8..f5aa783 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
     "@tauri-apps/api": "^2",
     "@tauri-apps/plugin-opener": "^2",
     "@tauri-apps/plugin-store": "^2.2.0",
+    "clsx": "^2.1.1",
     "prop-types": "^15.8.1",
     "react": "^18.3.1",
     "react-dom": "^18.3.1",
@@ -26,6 +27,7 @@
     "@tailwindcss/vite": "^4.1.6",
     "@tauri-apps/cli": "^2",
     "@types/bun": "latest",
+    "@types/json-schema": "^7.0.15",
     "@typescript-eslint/eslint-plugin": "^8.36.0",
     "@typescript-eslint/parser": "^8.36.0",
     "@vitejs/plugin-react": "^4.3.4",
diff --git a/public/tools/apple_note.svg b/public/tools/apple_note.svg
new file mode 100644
index 0000000..7fef3ef
--- /dev/null
+++ b/public/tools/apple_note.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="120" height="120" viewBox="0 0 120 120"><defs><linearGradient id="e" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#F4D87E"></stop><stop offset="100%" stop-color="#F5C52C"></stop></linearGradient><filter id="c" width="110.2%" height="146.7%" x="-5.1%" y="-16.7%" filterUnits="objectBoundingBox"><feOffset dy="2" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset><feGaussianBlur in="shadowOffsetOuter1" result="shadowBlurOuter1" stdDeviation="2"></feGaussianBlur><feColorMatrix in="shadowBlurOuter1" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"></feColorMatrix></filter><rect id="a" width="120" height="120" x="0" y="0" rx="28"></rect><path id="d" d="M-9 0h137v30H-9z"></path></defs><g fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"></use></mask><use fill="#FFF" xlink:href="#a"></use><g mask="url(#b)"><use fill="#000" filter="url(#c)" xlink:href="#d"></use><use fill="url(#e)" xlink:href="#d"></use></g><path fill="#C7C5C9" d="M0 59h120v2H0zM0 89h120v2H0z" mask="url(#b)"></path><g fill="#C2C0C4" mask="url(#b)"><g transform="translate(0 35)"><circle cx="11.5" cy="1.5" r="1.5"></circle><circle cx="16.5" cy="1.5" r="1.5"></circle><circle cx="21.5" cy="1.5" r="1.5"></circle><circle cx="26.5" cy="1.5" r="1.5"></circle><circle cx="31.5" cy="1.5" r="1.5"></circle><circle cx="36.5" cy="1.5" r="1.5"></circle><circle cx="41.5" cy="1.5" r="1.5"></circle><circle cx="46.5" cy="1.5" r="1.5"></circle><circle cx="51.5" cy="1.5" r="1.5"></circle><circle cx="56.5" cy="1.5" r="1.5"></circle><circle cx="61.5" cy="1.5" r="1.5"></circle><circle cx="66.5" cy="1.5" r="1.5"></circle><circle cx="71.5" cy="1.5" r="1.5"></circle><circle cx="76.5" cy="1.5" r="1.5"></circle><circle cx="81.5" cy="1.5" r="1.5"></circle><circle cx="86.5" cy="1.5" r="1.5"></circle><circle cx="91.5" cy="1.5" r="1.5"></circle><circle cx="96.5" cy="1.5" r="1.5"></circle><circle cx="101.5" cy="1.5" r="1.5"></circle><circle cx="106.5" cy="1.5" r="1.5"></circle><circle cx="111.5" cy="1.5" r="1.5"></circle><circle cx="116.5" cy="1.5" r="1.5"></circle><circle cx="6.5" cy="1.5" r="1.5"></circle><circle cx="1.5" cy="1.5" r="1.5"></circle></g></g></g></svg>
\ No newline at end of file
diff --git a/public/tools/notion.svg b/public/tools/notion.svg
new file mode 100644
index 0000000..bf6442f
--- /dev/null
+++ b/public/tools/notion.svg
@@ -0,0 +1,4 @@
+<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path d="M6.017 4.313l55.333 -4.087c6.797 -0.583 8.543 -0.19 12.817 2.917l17.663 12.443c2.913 2.14 3.883 2.723 3.883 5.053v68.243c0 4.277 -1.553 6.807 -6.99 7.193L24.467 99.967c-4.08 0.193 -6.023 -0.39 -8.16 -3.113L3.3 79.94c-2.333 -3.113 -3.3 -5.443 -3.3 -8.167V11.113c0 -3.497 1.553 -6.413 6.017 -6.8z" fill="#fff"/>
+  <path fill-rule="evenodd" clip-rule="evenodd" d="M61.35 0.227l-55.333 4.087C1.553 4.7 0 7.617 0 11.113v60.66c0 2.723 0.967 5.053 3.3 8.167l13.007 16.913c2.137 2.723 4.08 3.307 8.16 3.113l64.257 -3.89c5.433 -0.387 6.99 -2.917 6.99 -7.193V20.64c0 -2.21 -0.873 -2.847 -3.443 -4.733L74.167 3.143c-4.273 -3.107 -6.02 -3.5 -12.817 -2.917zM25.92 19.523c-5.247 0.353 -6.437 0.433 -9.417 -1.99L8.927 11.507c-0.77 -0.78 -0.383 -1.753 1.557 -1.947l53.193 -3.887c4.467 -0.39 6.793 1.167 8.54 2.527l9.123 6.61c0.39 0.197 1.36 1.36 0.193 1.36l-54.933 3.307 -0.68 0.047zM19.803 88.3V30.367c0 -2.53 0.777 -3.697 3.103 -3.893L86 22.78c2.14 -0.193 3.107 1.167 3.107 3.693v57.547c0 2.53 -0.39 4.67 -3.883 4.863l-60.377 3.5c-3.493 0.193 -5.043 -0.97 -5.043 -4.083zm59.6 -54.827c0.387 1.75 0 3.5 -1.75 3.7l-2.91 0.577v42.773c-2.527 1.36 -4.853 2.137 -6.797 2.137 -3.107 0 -3.883 -0.973 -6.21 -3.887l-19.03 -29.94v28.967l6.02 1.363s0 3.5 -4.857 3.5l-13.39 0.777c-0.39 -0.78 0 -2.723 1.357 -3.11l3.497 -0.97v-38.3L30.48 40.667c-0.39 -1.75 0.58 -4.277 3.3 -4.473l14.367 -0.967 19.8 30.327v-26.83l-5.047 -0.58c-0.39 -2.143 1.163 -3.7 3.103 -3.89l13.4 -0.78z" fill="#000"/>
+</svg>
diff --git a/src-tauri/src/app_note.rs b/src-tauri/src/app_note.rs
new file mode 100644
index 0000000..fb74a51
--- /dev/null
+++ b/src-tauri/src/app_note.rs
@@ -0,0 +1,49 @@
+use std::{
+    fs, 
+    error::Error, 
+    io::Write, 
+    path::PathBuf, 
+    process::Command, 
+};
+
+fn write_temp_script(bytes: &[u8], stem: &str) -> std::io::Result<PathBuf> {
+    let mut path = std::env::temp_dir();
+    path.push(format!("{stem}.scpt"));
+    let mut file = fs::File::create(&path)?;
+    file.write_all(bytes)?;
+    Ok(path)
+}
+/*
+** append text to the last most recent note
+*/
+pub fn append_note(text: &str) -> Result<(), Box<dyn Error>> {
+    const APPEND_NOTE: &[u8] = include_bytes!("scripts/append_note.scpt");
+    let script_path = write_temp_script(APPEND_NOTE, "append_note")?;
+    let status = Command::new("osascript")
+        .arg(script_path)   
+        .arg(text)
+        .status()?;
+
+    if !status.success() {
+        return Err(format!("Failed to append note: {status}").into());
+    }
+    Ok(())
+}
+
+/*
+** create a brand new note wiith title and body 
+*/
+pub fn create_note(title: &str, body: &str) -> Result<(), Box<dyn Error>> {
+    const CREATE_NOTE: &[u8] = include_bytes!("scripts/create_note.scpt");
+    let script_path = write_temp_script(CREATE_NOTE, "create_note")?;
+    let status = Command::new("osascript")
+        .arg(script_path)  
+        .arg(title)
+        .arg(body)
+        .status()?;
+
+    if !status.success() {
+        return Err(format!("Failed to create note: {status}").into());
+    }
+    Ok(())
+}
\ No newline at end of file
diff --git a/src-tauri/src/apps.rs b/src-tauri/src/apps.rs
new file mode 100644
index 0000000..591c159
--- /dev/null
+++ b/src-tauri/src/apps.rs
@@ -0,0 +1,41 @@
+
+use std::error::Error;
+use crate::app_note::{append_note, create_note};
+
+/*
+** app functions 
+*/
+pub fn call(func: &str, args: &[&str]) -> Result<(), Box<dyn Error>> {
+    match func {
+        /*
+        ** this function basically use osascript to append text to the last most recent note.
+        ** it is launching app "Notes" and appending text to the last most recent note.
+        */
+        "append_note" => {
+            if args.len() != 1 {
+                return Err(format!("append_note expects 1 argument, but got {}", args.len()).into());
+            }
+            append_note(&args[0])?;
+            Ok(())
+        }
+
+        /*
+        ** this function also use osascript to create a new note with title and body.
+        ** it is launching app "Notes" and creating a new note with title and body.
+        */
+        "create_note" => {
+            if args.len() != 2 {
+                return Err(format!("create_note expects 2 arguments, but got {}", args.len()).into());
+            }
+            create_note(&args[0], &args[1])?;
+            Ok(())
+        }
+
+        /*************************************************************************************
+        ** if the function is not found, return an error in case of unexpected function call.
+        */
+        _ => {
+            return Err(format!("Unknown function: {func}").into());
+        }
+    }
+}
\ No newline at end of file
diff --git a/src-tauri/src/browser_manager.rs b/src-tauri/src/browser_manager.rs
index 7d47cfa..2f86aca 100644
--- a/src-tauri/src/browser_manager.rs
+++ b/src-tauri/src/browser_manager.rs
@@ -201,20 +201,20 @@ pub async fn launch_new_instance(
 
     if is_dev {
         command
-            .arg("--disable-web-security")
+            // .arg("--disable-web-security")
             .arg("--disable-features=VizDisplayCompositor")
-            .arg("--ignore-certificate-errors")
+            // .arg("--ignore-certificate-errors")
             .arg("--allow-running-insecure-content");
     }
 
     /*
     ** nice visual cue in dev, blank tab in prod
     */
-    command.arg(if is_dev {
-        "https://www.google.com"
-    } else {
-        "about:blank"
-    });
+    // command.arg(if is_dev {
+    //     "https://www.google.com"
+    // } else {
+    //     "about:blank"
+    // });
 
     let mut child_process = command
         .stdout(Stdio::piped())
diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs
index 807a57f..8d7af37 100644
--- a/src-tauri/src/commands.rs
+++ b/src-tauri/src/commands.rs
@@ -16,6 +16,9 @@ use crate::browser_manager::{
     sunset_browser_instance,
 };
 use crate::utils::download_and_extract;
+use crate::skills::download_skill_json;
+use crate::sketchs_browser::WebsiteSkills;
+use crate::apps::call;
 
 #[tauri::command]
 pub async fn download_and_extract_resource(url: String) -> Result<String, String> {
@@ -137,7 +140,7 @@ pub async fn launch_browser(browser_path: Option<String>) -> Result<String, Stri
 
     match launch_new_instance(&target_browser_path, port).await {
         Ok(ws_url) => {
-            let _ = create_new_page(port, Some("https://www.google.com")).await;
+            // let _ = create_new_page(port, Some("https://www.google.com")).await;
             Ok(ws_url)
         }
         Err(e) => Err(format!(
@@ -225,3 +228,22 @@ pub async fn debug_browser_connection(browser_path: String) -> Result<String, St
     Ok(debug_info.join("\n"))
 }
 
+#[tauri::command]
+pub async fn load_skills(
+    domain: &str, 
+    company: Option<String>, 
+    repo: Option<String>, 
+    branch: String
+) -> Result<WebsiteSkills, String> {
+    println!("loading skills for domain: {}", domain);
+    download_skill_json(domain.to_string(), company, repo, branch).await
+}
+
+#[tauri::command]
+pub async fn call_app(func: String, args: Vec<String>) -> Result<String, String> {
+    // run the dispatcher ; map Ok() to () and Err() to String
+    let string_refs: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
+    call(&func, &string_refs)
+        .map(|_| "OK".to_string())
+        .map_err(|e| e.to_string())
+}
\ No newline at end of file
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index 65c6c89..ea3104a 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -1,6 +1,10 @@
 mod utils;
+mod skills;
+mod apps;
+mod app_note;
 mod config;
 mod sketchs;
+mod sketchs_browser;
 mod network;
 mod commands;
 mod platform;
@@ -15,7 +19,9 @@ use commands::{
     validate_ws_endpoint,
     scan_for_existing_browsers,
     debug_browser_connection,
-    download_and_extract_resource
+    download_and_extract_resource,
+    load_skills,
+    call_app
 };
 
 #[cfg_attr(mobile, tauri::mobile_entry_point)]
@@ -32,7 +38,9 @@ pub fn run() {
             validate_ws_endpoint,
             scan_for_existing_browsers,
             debug_browser_connection,
-            download_and_extract_resource
+            download_and_extract_resource,
+            load_skills,
+            call_app
         ])
         .run(tauri::generate_context!())
         .expect("error while running tauri application");
diff --git a/src-tauri/src/network.rs b/src-tauri/src/network.rs
index b73d0da..25ae993 100644
--- a/src-tauri/src/network.rs
+++ b/src-tauri/src/network.rs
@@ -165,7 +165,7 @@ pub async fn create_new_page(port: u16, url: Option<&str>) -> Result<String, Str
         .build()
         .map_err(|e| format!("Failed to create HTTP client: {e}"))?;
 
-    let target_url = url.unwrap_or("about:blank");
+    let target_url = url.unwrap_or("");
     let new_page_url = format!("http://127.0.0.1:{port}/json/new?{target_url}");
 
     println!("creating new page: {new_page_url}");
diff --git a/src-tauri/src/scripts/append_note.scpt b/src-tauri/src/scripts/append_note.scpt
new file mode 100644
index 0000000..a0a7242
Binary files /dev/null and b/src-tauri/src/scripts/append_note.scpt differ
diff --git a/src-tauri/src/scripts/create_note.scpt b/src-tauri/src/scripts/create_note.scpt
new file mode 100644
index 0000000..dd5ac1f
Binary files /dev/null and b/src-tauri/src/scripts/create_note.scpt differ
diff --git a/src-tauri/src/sketchs.rs b/src-tauri/src/sketchs.rs
index ec92480..c2c7272 100644
--- a/src-tauri/src/sketchs.rs
+++ b/src-tauri/src/sketchs.rs
@@ -45,4 +45,5 @@ pub struct PuppeteerExecutionResult {
     pub error_message: Option<String>,
     pub current_url: Option<String>,
     pub page_context: Option<serde_json::Value>, 
-}
\ No newline at end of file
+}
+
diff --git a/src-tauri/src/sketchs_browser.rs b/src-tauri/src/sketchs_browser.rs
new file mode 100644
index 0000000..b274730
--- /dev/null
+++ b/src-tauri/src/sketchs_browser.rs
@@ -0,0 +1,32 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Deserialize, Serialize)]
+pub struct WebsiteSkills {
+    pub domain: String,
+    pub skills: Vec<SkillDefinition>,
+}
+
+#[derive(Debug, Deserialize, Serialize)]
+pub struct SkillDefinition {
+    pub name: String,
+    pub description: String,
+    #[serde(default)]
+    pub input: Option<std::collections::HashMap<String, String>>,
+    #[serde(default)]
+    pub output: Option<String>,
+    pub steps: Vec<SkillStep>,
+}
+
+#[derive(Debug, Deserialize, Serialize)]
+pub struct SkillStep {
+    pub action: String,
+    #[serde(default)]
+    pub selector: Option<String>,
+    #[serde(default)]
+    pub input_key: Option<String>,
+    #[serde(default)]
+    pub index: Option<u32>,
+    #[serde(default)]
+    pub output_key: Option<String>,
+}
+
diff --git a/src-tauri/src/skills.rs b/src-tauri/src/skills.rs
new file mode 100644
index 0000000..3c0e407
--- /dev/null
+++ b/src-tauri/src/skills.rs
@@ -0,0 +1,29 @@
+use reqwest::Client;
+use crate::sketchs_browser::WebsiteSkills;
+
+pub async fn download_skill_json(
+    domain: String, 
+    company: Option<String>, 
+    repo: Option<String>,
+    branch: String,
+) -> Result<WebsiteSkills, String> {
+    let company: String = company.unwrap_or_else(|| "runtime-org".to_string());
+    let repo: String = repo.unwrap_or_else(|| "sk".to_string());
+    let url: String = format!("https://raw.githubusercontent.com/{company}/{repo}/{branch}/skills/{domain}.json");
+    println!("downloading skills from: {}", url);
+    let text: String = Client::new()
+        .get(url)
+        .send()
+        .await
+        .map_err(|e| format!("Failed to download skill file for {domain}: {e}"))?
+        .text()
+        .await
+        .map_err(|e| format!("Failed to download skill file for {company}.{domain}: {e}"))?;
+
+    let parsed: WebsiteSkills = serde_json::from_str(&text)
+        .map_err(|e| format!("Failed to parse skill file for {domain}: {e}"))?;
+
+    println!("parsed: {:?}", parsed);
+
+    Ok(parsed)
+}
\ No newline at end of file
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 95ea376..955fc95 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -14,7 +14,11 @@
       {
         "title": "runtime",
         "width": 400,
-        "height": 700
+        "height": 700,
+        "resizable": true,
+        "minWidth": 400,
+        "maxWidth": 400,
+        "minHeight": 600
       }
     ],
     "security": {
@@ -30,6 +34,7 @@
       "icons/128x128@2x.png",
       "icons/icon.icns",
       "icons/icon.ico"
-    ]
+    ],
+    "resources": ["src/scripts/*.scpt"]
   }
 }
diff --git a/src/App.jsx b/src/App.jsx
index cbf792d..1c89a61 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,230 +1,135 @@
 import React, { useEffect, useCallback } from "react";
-
+import puppeteer from 'puppeteer-core/lib/esm/puppeteer/puppeteer-core-browser.js';
+import { invoke } from "@tauri-apps/api/core";
 import "./App.css";
+
 import Frame from "./components/layout/Frame";
 import HomeView from "./components/pages/HomeView";
 import HistoryView from "./components/pages/HistoryView";
 import SessionView from "./components/pages/SessionView";
 import { useAppState } from "./hooks/useAppState";
-import puppeteer from 'puppeteer-core/lib/esm/puppeteer/puppeteer-core-browser.js';
-import { invoke } from "@tauri-apps/api/core";
 
 function App() {
   const { 
     view,
-    selectedBrowserPath,
-    savedWsEndpoint,
-    setSavedWsEndpoint,
-    clearSavedWsEndpoint,
-    fetchBrowsers,
-    browserInstance,
-    logs,
     isConnected,
+    currentBrowserPath,
+    browserInstance,
+    rememberBrowser,
+    forgetBrowser,
     setBrowserInstance,
     setPageInstance,
     setIsConnected,
-    addLog,
-    // eslint-disable-next-line no-unused-vars
-    clearLogs
   } = useAppState();
 
-  const attemptReconnectionWithSavedEndpoint = async () => {
-    if (!savedWsEndpoint || !selectedBrowserPath) {
-      return;
+  /*
+  ** connect pptr
+  */
+  const connectPuppeteer = useCallback(async (ws, retries = 2) => {
+    while (retries--) {
+      try {
+        return await puppeteer.connect({
+          browserWSEndpoint: ws,
+          defaultViewport: null
+        })
+      } catch (error) {
+        if (retries === 0) throw error;
+        await new Promise(resolve => setTimeout(resolve, 1000));
+      }
     }
+  }, [])
+
+  const connectBrowser = useCallback(async (browserPath) => {
+    const path = browserPath || currentBrowserPath;
+    if (!path) { console.log("no browser selected"); return; }
 
     try {
-      addLog(`attempting to reconnect to saved endpoint: ${savedWsEndpoint}`, "info");
-      
-      const validationResult = await invoke("validate_ws_endpoint", { 
-        wsEndpoint: savedWsEndpoint, 
-        selectedBrowserPath 
-      });
-      
-      addLog(`rust: ${validationResult}`, "success");
-      setIsConnected(true);
-      
-      try {
-        addLog("connecting puppeteer to saved endpoint...", "info");
-        const browser = await puppeteer.connect({ 
-          browserWSEndpoint: savedWsEndpoint, 
-          defaultViewport: null 
-        });
-        
-        setBrowserInstance(browser);
-        
-        const pages = await browser.pages();
-        const currentPage = pages.length > 0 ? pages[0] : await browser.newPage();
-        setPageInstance(currentPage);
-        
-        addLog("successfully reconnected to existing browser instance ♾️", "success");
-        
-        browser.on("disconnected", () => {
-          addLog("puppeteer disconnected", "warn");
-          setBrowserInstance(null);
-          setPageInstance(null);
-          setIsConnected(false);
-          clearSavedWsEndpoint();
-        });
-        
-      } catch (puppeteerError) {
-        addLog(`could not connect puppeteer: ${puppeteerError.message}`, "warn");
-        setIsConnected(false);
-        clearSavedWsEndpoint();
-      }
-      
-    } catch (error) {
-      addLog(`saved endpoint is no longer valid: ${error.message}`, "warn");
+      /*
+      ** launch or reuse a browser
+      */
       setIsConnected(false);
-      clearSavedWsEndpoint();
-    }
-  };
+      const ws = await invoke("launch_browser", { browserPath: path });
+      console.log("connecting...");
+
+      /*
+      ** asserte the connection
+      */
+      await invoke("validate_connection", {
+        wsEndpoint: ws,
+        selectedBrowserPath: path
+      });
 
-  useEffect(() => {
-    fetchBrowsers(false);
-    console.log("isConnected", isConnected);
-    
-    // crystal clear
-    return () => {
-      if (isConnected) {
-        console.log("Disconnecting from browser...");
-        handleCloseBrowser(false);
-      }
-    };
-  }, [fetchBrowsers]);
+      /*
+      ** memorise endpoint so we can reconect next time
+      */
+      rememberBrowser(path, ws);
 
-  useEffect(() => {
-    if (selectedBrowserPath && savedWsEndpoint && !isConnected) {
-      attemptReconnectionWithSavedEndpoint();
-    }
-  }, [selectedBrowserPath, savedWsEndpoint]);
+      /*
+      ** attach pptr
+      */
+      const browser = await connectPuppeteer(ws);
+      console.log("browser connected", browser);
+      setBrowserInstance(browser);
 
-  const handleLaunchAndConnect = async () => {
-    if (!selectedBrowserPath) {
-      console.log("no browser selected");
-      return;
-    }
+      /*
+      ** open starter tab (will replace with our own static page)
+      */
 
-    // first forward
-    try {
-      console.log("launching browser...");
-      const wsEndpoint = await invoke("launch_browser", { browserPath: selectedBrowserPath });
-      console.log("connecting to", wsEndpoint);
-      
-      setSavedWsEndpoint(wsEndpoint);
+      const page = await browser.newPage();
       
-      try {
-        const validationResult = await invoke("validate_connection", { 
-          wsEndpoint, 
-          selectedBrowserPath 
-        });
-        addLog("Rust: " + validationResult, "success");
-        setIsConnected(true);
-      } catch (validationError) {
-        addLog(`Browser validation failed: ${validationError}`, "error");
-        setIsConnected(false);
-        clearSavedWsEndpoint();
-        return;
-      }
-      
-      let browser;
-      let retries = 2;
-      
-      addLog("Establishing puppeteer connection...", "info");
-      // second forward (pptr)
-      while (retries > 0) {
-        try {
-          addLog("-> Connecting puppeteer...", "info");
-          browser = await puppeteer.connect({ 
-            browserWSEndpoint: wsEndpoint, 
-            defaultViewport: null 
-          });
-          addLog("♾️ Puppeteer connected", "success");
-          break; 
-        } catch (connectError) {
-          console.log("connectError", connectError);
-          retries--;
-          if (retries === 0) {
-            addLog("Puppeteer connection failed, but browser is running", "warn");
-            addLog("This might be a temporary issue, try again", "info");
-            return;
-          }
-          addLog(`Puppeteer retry ${2-retries}/2...`, "info");
-          await new Promise(resolve => setTimeout(resolve, 1000));
-        }
-      }
+      setPageInstance(page);
 
-      setBrowserInstance(browser);
-      
-      const currentPage = await browser.newPage();
+      // await page.goto("https://www.youtube.com", {
+      //   waitUntil: "domcontentloaded",
+      //   timeout: 10000
+      // });
 
-      try {
-        await currentPage.goto("https://www.youtube.com", {
-          waitUntil: "domcontentloaded",
-          timeout: 10000
-        });
-      } catch (error) {
-        console.error("error", error) 
-      }
-      setPageInstance(currentPage);
-      
-      addLog("Full connection established ♾️", "success");
+      setIsConnected(true);
+      console.log("♾️");
 
+      /*
+      ** housekeeping
+      */
       browser.on("disconnected", () => {
-        addLog("Puppeteer disconnected", "warn");
+        console.log("Puppeteer disconnected");
         setBrowserInstance(null);
         setPageInstance(null);
         setIsConnected(false);
-        clearSavedWsEndpoint();
-      });
+        forgetBrowser(path);
+      })
 
     } catch (error) {
-      const errorMessage = error.message || (typeof error === "string" ? error: JSON.stringify(error));
-      addLog(`Failed to connect: ${errorMessage}`, "error");
+      console.log("failed to connect to browser", error);
       setIsConnected(false);
       setBrowserInstance(null);
       setPageInstance(null);
-      clearSavedWsEndpoint();
-    }
-  }
-
-  const handleCloseBrowser = useCallback(async (shouldDisconnectPuppeteer = false) => {
-    addLog("attempting to close browser...", "info");
-    if (shouldDisconnectPuppeteer && browserInstance && browserInstance.isConnected()) {
-      try {
-        await browserInstance.disconnect();
-        addLog("pptr instance disconnected.", 'info');
-      } catch (error) {
-        addLog(`Error disconnecting puppeteer: ${error.message}`, 'warn');
-      }
+      forgetBrowser(path);
     }
+  }, [
+    currentBrowserPath,
+    rememberBrowser,
+    forgetBrowser,
+    setBrowserInstance,
+    setPageInstance,
+    setIsConnected
+  ])
 
-    try {
-      await invoke('disconnect_from_browser');
-      addLog("Backend command to close browser completed.", 'success');
-    } catch (error) {
-      addLog(`Error disconnecting from backend: ${error.message}`, 'warn');
-    }
-
-    setBrowserInstance(null);
-    setPageInstance(null);
-    setIsConnected(false);
-    clearSavedWsEndpoint();
-  }, [browserInstance, addLog, clearSavedWsEndpoint]);
-
-
+  /*
+  ** home view props
+  */
   const homeViewProps = {
-    selectedBrowserPath,
-    logs,
+    currentBrowserPath,
     isConnected,
-
-    onLaunchAndConnect: handleLaunchAndConnect,
-    onCloseBrowser: handleCloseBrowser,
+    onLaunchAndConnect: connectBrowser
   }
 
+  /*
+  ** session view props
+  */
   const sessionViewProps = {
     browserInstance,
-    isConnected
+    isConnected,
+    connectBrowser
   };
 
   return (
diff --git a/src/components/pages/HomeView.jsx b/src/components/pages/HomeView.jsx
index c2564a1..6063fa1 100644
--- a/src/components/pages/HomeView.jsx
+++ b/src/components/pages/HomeView.jsx
@@ -11,9 +11,9 @@ import SettingView from "../pages/SettingView";
 import PropTypes from "prop-types";
 
 HomeView.propTypes = {
+    currentBrowserPath: PropTypes.string.isRequired,
     isConnected: PropTypes.bool.isRequired,
     onLaunchAndConnect: PropTypes.func.isRequired,
-    onCloseBrowser: PropTypes.func.isRequired,
 }
 
 export default function HomeView(props) {
@@ -25,9 +25,9 @@ export default function HomeView(props) {
     } = useAppState();
 
     const {
+        currentBrowserPath,
         isConnected,
         onLaunchAndConnect,
-        onCloseBrowser,
     } = props;
     
     const [showSettingsMenu, setShowSettingsMenu] = useState(false);
@@ -37,13 +37,11 @@ export default function HomeView(props) {
     // placeholder username
     const username = "User";
 
-    // Improved click outside detection
+    // click outside detection
     useEffect(() => {
         function handleClickOutside(event) {
-            // Only run this if the menu is actually open
             if (!showSettingsMenu) return;
             
-            // Check if click is outside both menu and button
             if (
                 settingsMenuRef.current && 
                 !settingsMenuRef.current.contains(event.target) &&
@@ -53,17 +51,12 @@ export default function HomeView(props) {
             }
         }
         
-        // Use mousedown for more responsive clicking
         document.addEventListener("mousedown", handleClickOutside);
         
         return () => {
             document.removeEventListener("mousedown", handleClickOutside);
         };
     }, [showSettingsMenu]);
-
-    const handleProfileClick = () => {
-        setShowSettingsMenu(prev => !prev);
-    };
     
     // profile button
     const profileButton = {
@@ -72,7 +65,7 @@ export default function HomeView(props) {
             username,
             ref: profileIconRef 
         },
-        onClick: handleProfileClick
+        onClick: () => setShowSettingsMenu(prev => !prev)
     };
     
     const createSession = async (text) => {
@@ -87,11 +80,10 @@ export default function HomeView(props) {
     }
     
     const handleSubmit = async (text) => {
-        const newSession = await createSession(text); // fastapi /plan 
+        const newSession = await createSession(text); // fastapi /plan
         addSession(newSession);
         setActiveSessionId(newSession.id);
         openSession(newSession.id);
-
     }
 
     return (
@@ -105,9 +97,9 @@ export default function HomeView(props) {
             <div className="absolute top-12 left-0 right-0 flex flex-col w-full items-center justify-center">
                 <div className="flex items-center pl-3.5 w-full h-12">
                     <BrowserSelection 
+                        currentBrowserPath={currentBrowserPath}
                         isConnected={isConnected} 
                         onLaunchAndConnect={onLaunchAndConnect}
-                        onCloseBrowser={onCloseBrowser}
                     />
                 </div>
             </div>
diff --git a/src/components/pages/SessionView.jsx b/src/components/pages/SessionView.jsx
index 242f5ef..577d4a3 100644
--- a/src/components/pages/SessionView.jsx
+++ b/src/components/pages/SessionView.jsx
@@ -1,4 +1,3 @@
-// src/components/views/SessionView.tsx
 import React, { useState, useEffect, useRef, useCallback } from 'react';
 import PropTypes from 'prop-types';
 import { v4 as uuidv4 } from 'uuid';
@@ -11,8 +10,6 @@ import System from '../ui/System';
 import { useAppState }  from '../../hooks/useAppState';
 
 import { splitQuery } from '../../lib/query.llm';
-import { callLLM } from '../../lib/llm.engine';
-import { getFnCall } from '../../lib/task.execution.helpers';
 import { 
   buildHistoryDigest, 
   addPlanToTask, 
@@ -20,28 +17,79 @@ import {
 } from '../../lib/query.helpers';
 import { runWorkflow } from '../../lib/workflow.runner';
 import { taskEventEmitter } from '../../lib/emitters';
+import puppeteer from 'puppeteer-core/lib/esm/puppeteer/puppeteer-core-browser.js';
+import { createPagePool } from '../../lib/page.manager';
 
 SessionView.propTypes = {
   browserInstance: PropTypes.object,
-  isConnected: PropTypes.bool
+  isConnected: PropTypes.bool,
+  connectBrowser: PropTypes.func.isRequired
 };
 
-export default function SessionView({ browserInstance /* isConnected */ }) {
+export default function SessionView({ browserInstance, isConnected, connectBrowser }) {
 
   const {
     sessions, 
+    runtimeMode,
     activeSessionId, 
     openHome,
     addMessageToSession, 
-    getSessionMessages
+    getSessionMessages,
+    forgetBrowser,
+    getWsFor,
+    setBrowserInstance,
+    setIsConnected,
+    currentBrowserPath
   } = useAppState();
 
   const activeSession   = sessions.find(s => s.id === activeSessionId);
   const [messages, setMessages] = useState([]);
   const [isProcessing, setIsProcessing] = useState(false);
   const [historyReady, setHistoryReady] = useState(false);
+  const [isMessagesReady, setIsMessagesReady] = useState(false);
 
   const messagesEndRef  = useRef(null);
+  const cancelRef = useRef({ cancelled: false });
+
+  /*
+  ** reattach if we lost the in memory handle
+  */
+  const reconnectIfNeeded = async (maxRetries = 3) => {
+
+    if (browserInstance) return browserInstance;
+
+    /*
+    ** try to reconnect to the browser
+    */
+    let attempts = 0;
+    while (attempts <= maxRetries) {
+      const ws = getWsFor(currentBrowserPath);
+
+      if (ws) {
+        try {
+          const br = await puppeteer.connect({
+            browserWSEndpoint: ws,
+            defaultViewport: null
+          })
+
+          setBrowserInstance(br);
+          setIsConnected(true);
+          return br;
+        } catch (error) {
+          /*
+          ** if the ws is stale, drop it
+          */
+          console.log("stale ws, dropping it", error.message);
+          forgetBrowser(currentBrowserPath);
+        }
+      }
+      await connectBrowser(currentBrowserPath);
+
+      attempts += 1;
+    }
+
+    return null;
+  }
 
   /*
   * add a new message to the messages array and the session
@@ -59,48 +107,51 @@ export default function SessionView({ browserInstance /* isConnected */ }) {
 
 
   /*
-  * load the session history on mount / switch
+  ** load the session history on mount, and take the current query
   */
   useEffect(() => {
     if (!activeSessionId) {
       setMessages([]);
       setHistoryReady(false);
+      setIsMessagesReady(false);
       return;
     }
 
     const stored = getSessionMessages(activeSessionId) ?? [];
     setMessages(stored);
     setHistoryReady(true);
-
+    setIsMessagesReady(true);
   }, [activeSessionId]);
 
+
   /*
-  ** ensure sys message is the right one for taskId
+  ** handle new query
   */
-  function ensureSysMessage(clone, taskId, initialAction) {
-    let idx = clone.findIndex(
-      m =>
-        m.type === 'system' &&
-        Array.isArray(m.tasks) &&
-        m.tasks.some(t => t.taskId === taskId)
-    );
+  useEffect(() => {
+    if (!isMessagesReady) return;
 
-    if (idx === -1) {
-      const newMsg = {
-        id: uuidv4(),
-        type: 'system',
-        text: '',
-        action: initialAction,
-        timestamp: new Date().toISOString(),
-        status: 'pending',
-        tasks: [{ taskId, tabs: [], plans: [] }],
+    if (
+      activeSessionId &&
+      messages.length === 0 &&
+      activeSession.title 
+    ) {
+
+      const handleInitialQuery = async () => {
+
+        /*
+        ** save the message
+        */
+        addNewMessage({ type: 'user', text: activeSession.title });
+
+        /*
+        ** execute the query
+        */
+        executeQuery(activeSession.title);
       };
-      clone.push(newMsg);
-      idx = clone.length - 1;
-    }
 
-    return idx;
-  }
+      handleInitialQuery();
+    }
+  }, [activeSessionId, messages.length, activeSession.title, isMessagesReady]);
 
   /*
   * handle workflow updates (create, update, complete "plan" of sub tasks)
@@ -112,7 +163,6 @@ export default function SessionView({ browserInstance /* isConnected */ }) {
     ** handle task updates
     */
     const handleTaskUpdate = ({ taskId, action, speakToUser, status, error}) => {
-
       setMessages(prev => {
         /* 
         ** create a new system message
@@ -147,7 +197,8 @@ export default function SessionView({ browserInstance /* isConnected */ }) {
     /*
     ** starting the action (puppeteer or llm)
     */
-    const handleActionStart = ({ taskId, action, speakToUser, status, actionId }) => {
+    const handleActionStart = ({ taskId, action, speakToUser, status, actionId, url }) => {
+
       setMessages(prev => {
         const clone = [...prev];
 
@@ -162,7 +213,7 @@ export default function SessionView({ browserInstance /* isConnected */ }) {
           timestamp: new Date().toISOString(),
         };
 
-        sysMsg.tasks = addPlanToTask({tasks: sysMsg.tasks, taskId, newPlan: newPlanStep});
+        sysMsg.tasks = addPlanToTask({tasks: sysMsg.tasks, taskId, newPlan: newPlanStep, action, url});
 
         clone[sysIndex] = sysMsg;
         setTimeout(() => {
@@ -211,13 +262,43 @@ export default function SessionView({ browserInstance /* isConnected */ }) {
       taskEventEmitter.off('task_action_complete', handleActionDone);
       taskEventEmitter.off('task_action_error', handleActionDone); 
     };
-  }, [activeSessionId, isProcessing]);
+  }, [activeSessionId]); // removed isProcessing
+
+  /*
+  ** ensure sys message is the right one for taskId
+  */
+  function ensureSysMessage(clone, taskId, initialAction) {
+    let idx = clone.findIndex(
+      m =>
+        m.type === 'system' &&
+        Array.isArray(m.tasks) &&
+        m.tasks.some(t => t.taskId === taskId)
+    );
+
+    if (idx === -1) {
+      const newMsg = {
+        id: uuidv4(),
+        type: 'system',
+        text: '',
+        action: initialAction,
+        timestamp: new Date().toISOString(),
+        status: 'pending',
+        tasks: [{ taskId, tabs: [], plans: [] }],
+      };
+      clone.push(newMsg);
+      idx = clone.length - 1;
+    }
+
+    return idx;
+  }
 
   /*
   * execute a query
   */
   const executeQuery = async (rawText) => {
+
     setIsProcessing(true);
+    cancelRef.current.cancelled = false;
 
     try {
       /* 
@@ -227,21 +308,45 @@ export default function SessionView({ browserInstance /* isConnected */ }) {
       const historyDigest = buildHistoryDigest(fullHistory);
       console.log("historyDigest", historyDigest);
 
-      const resp  = await splitQuery({query: rawText, history: historyDigest});
-      const { queries, dependencies, researchFlags } = resp;
+      const resp  = await splitQuery({query: rawText, history: historyDigest, runtimeMode});
+      console.log("kind", resp.kind);
+
+      if (resp.kind === "small_talk") {
+        const { reply } = resp;
+        addNewMessage({ type:'system', text: reply });
+        return;
+      }
 
       /*
       * run the workflow
       */
+      let browser = browserInstance;
+      if (!browser) {
+        browser = await reconnectIfNeeded();
+        // console.log("browser after reconnect", browser);
+      }
+
+      if (!browser) {
+        addNewMessage({ 
+          type:'system', 
+          text: "Ops, something went wrong, please relaunch runtime",
+          isError: true
+        });
+        return;
+      }
+
       await runWorkflow({
+        mode: resp.kind,
         originalQuery: rawText,
         sessionId: activeSessionId,
-        queries,
-        dependencies,
-        researchFlags,
-        browserInstance,
-        onDone: (text) => addNewMessage({ type:'system', text }),
-        onError: (err) => addNewMessage({ type:'system', text: err, isError: true })
+        queries: resp.queries,
+        dependencies: resp.dependencies,
+        researchFlags: resp.researchFlags,
+        steps: resp.steps,
+        browserInstance: browser,
+        cancelRef,
+        // onDone: (text) => addNewMessage({ type:'system', text }),
+        // onError: (err) => addNewMessage({ type:'system', text: err, isError: true })
       })
     } catch (error) {
       addNewMessage({ type:'system', text: error.message, isError:true });
@@ -253,13 +358,31 @@ export default function SessionView({ browserInstance /* isConnected */ }) {
   /*
   * handle the submit of a new message
   */
-  const handleSubmit = (text) => {
+  const handleSubmit = async (text) => {
     if (!text.trim() || isProcessing) return;
 
+    // run puppeteer function to test, i will a function here
+    if (!browserInstance) {
+      browserInstance = await reconnectIfNeeded();
+    }
+
+    // use page manager
+    const pageManager = createPagePool({ browser: browserInstance });
+
+    const page = await pageManager();
+
     addNewMessage({ type:'user', text: text.trim() });
     executeQuery(text.trim());
   };
 
+  /*
+  ** handle stop
+  */
+  const handleStop = () => {
+    cancelRef.current.cancelled = true;
+    setIsProcessing(false);
+  }
+
   /*
   * if no active session, show a loading message
   */
@@ -277,8 +400,8 @@ export default function SessionView({ browserInstance /* isConnected */ }) {
       <div className="flex-grow overflow-y-auto p-4 space-y-2 no-scrollbar pb-24">
         {messages.map(m =>
           m.type === 'user'
-            ? <User key={m.id}    message={m} />
-            : <System key={m.id}  message={m}
+            ? <User key={m.id} message={m} />
+            : <System key={m.id} message={m}
                       isError={m.isError}
                       isComplete={m.isComplete}
               />
@@ -301,7 +424,9 @@ export default function SessionView({ browserInstance /* isConnected */ }) {
             "Send a message to Runtime…"
           }
           onSubmit={handleSubmit}
-          disabled={isProcessing || !historyReady}
+          disabled={!historyReady}
+          isProcessing={isProcessing}
+          onStop={handleStop}
         />
       </div>
     </div>
diff --git a/src/components/ui/ActionBar.jsx b/src/components/ui/ActionBar.jsx
new file mode 100644
index 0000000..15ce8cc
--- /dev/null
+++ b/src/components/ui/ActionBar.jsx
@@ -0,0 +1,217 @@
+import React, { useState } from 'react';
+import { invoke } from '@tauri-apps/api/core';
+import { TbThumbUp, TbThumbDown, TbCopy, TbPlus, TbRotateClockwise } from "react-icons/tb";
+import AppActionMenu from './AppActionMenu';
+import PropTypes from 'prop-types';
+import { mdToHtml } from '../../lib/utils';
+
+ActionBar.propTypes = {
+  text: PropTypes.string,
+  onThumbsUp: PropTypes.func,
+  onThumbsDown: PropTypes.func,
+  onCopy: PropTypes.func,
+  onNotion: PropTypes.func,
+  onAppleNote: PropTypes.func
+};
+
+export default function ActionBar({ 
+  text = "", 
+  onThumbsUp,
+  onThumbsDown,
+  onCopy,
+}) {
+  const [hoveredButton, setHoveredButton] = useState(null);
+  const [openMenuFor, setOpenMenuFor] = useState(null);
+
+  const handleThumbsUp = () => {
+    if (onThumbsUp) {
+      onThumbsUp();
+    } else {
+      console.log('Thumbs up clicked');
+    }
+  };
+
+  const handleThumbsDown = () => {
+    if (onThumbsDown) {
+      onThumbsDown();
+    } else {
+      console.log('Thumbs down clicked');
+    }
+  };
+
+  const handleCopy = () => {
+    if (onCopy) {
+      onCopy(text);
+    } else {
+      navigator.clipboard.writeText(text);
+      console.log('Text copied to clipboard');
+    }
+  };
+
+  const handleNotion = () => {
+    if (onNotion) {
+      onNotion(text);
+    } else {
+      console.log('Send to Notion clicked');
+    }
+  };
+
+  const handleAppleNote = () => {
+    setOpenMenuFor('apple');
+  };
+
+  /* ===== menus for each icon ===== */
+  const appleNoteItems = [
+    {
+      id: 'new',
+      label: 'Add to a new note',
+      icon: <TbPlus size={16} className="opacity-70 text-[#a4a4a8]" />,
+      onClick: async () => {
+        setOpenMenuFor(null);
+        console.log('Adding to new note: ', text);
+        await invoke('call_app', {
+          func: 'create_note',
+          args: ['Note from Runtime', mdToHtml(text)]
+        });
+      }
+    },
+    {
+      id: 'append',
+      label: 'Add to the last note',
+      icon: (
+        <svg
+          xmlns="http://www.w3.org/2000/svg"
+          width={16}
+          height={16}
+          fill="#a4a4a8"
+          viewBox="0 0 256 256"
+          className="opacity-70"
+        >
+          <path d="M136,80v43.47l36.12,21.67a8,8,0,0,1-8.24,13.72l-40-24A8,8,0,0,1,120,128V80a8,8,0,0,1,16,0Zm-8-48A95.44,95.44,0,0,0,60.08,60.15C52.81,67.51,46.35,74.59,40,82V64a8,8,0,0,0-16,0v40a8,8,0,0,0,8,8H72a8,8,0,0,0,0-16H49c7.15-8.42,14.27-16.35,22.39-24.57a80,80,0,1,1,1.66,114.75,8,8,0,1,0-11,11.64A96,96,0,1,0,128,32Z"></path>
+        </svg>
+      ),
+      onClick: async () => {
+        setOpenMenuFor(null);
+        console.log('Adding to last note: ', text);
+        await invoke('call_app', {
+          func: 'append_note',
+          args: [mdToHtml(text)]
+        });
+      }
+    }
+  ];
+
+  return (
+    <div className="flex items-center justify-between mt-2">
+      {/* Left side buttons */}
+      <div className="flex items-center gap-2">
+        <div className="relative">
+          <button
+            onClick={handleNotion}
+            className="p-1 rounded hover:bg-[#484848]/20 transition-colors duration-200 cursor-pointer"
+            onMouseEnter={() => setHoveredButton('notion')}
+            onMouseLeave={() => setHoveredButton(null)}
+          >
+            <img 
+              src="/tools/notion.svg" 
+              alt="Notion" 
+              className="w-3.5 h-3.5 opacity-40 hover:opacity-100 transition-opacity duration-200"
+            />
+          </button>
+          {hoveredButton === 'notion' && (
+            <div className="absolute bottom-full mb-1 left-[40px] transform -translate-x-1/2 bg-black text-white text-xs px-2 py-1 rounded whitespace-nowrap">
+              Send to Notion
+            </div>
+          )}
+        </div>
+        
+        <div className="relative">
+          <button
+            onClick={handleAppleNote}
+            className="p-1 rounded hover:bg-[#484848]/20 transition-colors duration-200 cursor-pointer"
+            onMouseEnter={() => setHoveredButton('appleNote')}
+            onMouseLeave={() => setHoveredButton(null)}
+          >
+            <img 
+              src="/tools/apple_note.svg" 
+              alt="Apple Notes" 
+              className="w-3.5 h-3.5 opacity-40 hover:opacity-100 transition-opacity duration-200"
+            />
+          </button>
+          {hoveredButton === 'appleNote' && (
+            <div className="absolute bottom-full mb-1 left-[40px] -translate-x-1/2
+                            bg-black text-white text-xs px-2 py-1 rounded whitespace-nowrap">
+              Send to Apple Notes
+            </div>
+          )}
+
+          <AppActionMenu
+            visible={openMenuFor === 'apple'}
+            onRequestClose={() => setOpenMenuFor(null)}
+            items={appleNoteItems}
+          />
+        </div>
+      </div>
+
+      {/* Right side buttons */}
+      <div className="flex items-center gap-2">
+        <div className="relative">
+          <button
+            onClick={handleThumbsUp}
+            className="p-1 rounded hover:bg-[#484848]/20 transition-colors duration-200 cursor-pointer"
+            onMouseEnter={() => setHoveredButton('helpful')}
+            onMouseLeave={() => setHoveredButton(null)}
+          >
+            <TbThumbUp 
+              size={14} 
+              className="text-[#a4a4a8]/40 hover:text-[#a4a4a8] transition-colors duration-200" 
+            />
+          </button>
+          {hoveredButton === 'helpful' && (
+            <div className="absolute bottom-full mb-1 left-1/2 transform -translate-x-1/2 bg-black text-white text-xs px-2 py-1 rounded whitespace-nowrap">
+              Helpful
+            </div>
+          )}
+        </div>
+        
+        <div className="relative">
+          <button
+            onClick={handleThumbsDown}
+            className="p-1 rounded hover:bg-[#484848]/20 transition-colors duration-200 cursor-pointer"
+            onMouseEnter={() => setHoveredButton('notHelpful')}
+            onMouseLeave={() => setHoveredButton(null)}
+          >
+            <TbThumbDown 
+              size={14} 
+              className="text-[#a4a4a8]/40 hover:text-[#a4a4a8] transition-colors duration-200" 
+            />
+          </button>
+          {hoveredButton === 'notHelpful' && (
+            <div className="absolute bottom-full mb-1 left-1/2 transform -translate-x-1/2 bg-black text-white text-xs px-2 py-1 rounded whitespace-nowrap">
+              Not helpful
+            </div>
+          )}
+        </div>
+        
+        <div className="relative">
+          <button
+            onClick={handleCopy}
+            className="p-1 rounded hover:bg-[#484848]/20 transition-colors duration-200 cursor-pointer"
+            onMouseEnter={() => setHoveredButton('copy')}
+            onMouseLeave={() => setHoveredButton(null)}
+          >
+            <TbCopy 
+              size={14} 
+              className="text-[#a4a4a8]/40 hover:text-[#a4a4a8] transition-colors duration-200" 
+            />
+          </button>
+          {hoveredButton === 'copy' && (
+            <div className="absolute bottom-full mb-1 left-1/2 transform -translate-x-1/2 bg-black text-white text-xs px-2 py-1 rounded whitespace-nowrap">
+              Copy
+            </div>
+          )}
+        </div>
+      </div>
+    </div>
+  );
+} 
\ No newline at end of file
diff --git a/src/components/ui/ActionIcons.jsx b/src/components/ui/ActionIcons.jsx
deleted file mode 100644
index 3b96266..0000000
--- a/src/components/ui/ActionIcons.jsx
+++ /dev/null
@@ -1,105 +0,0 @@
-import React, { useState } from 'react';
-import { TbThumbUp, TbThumbDown, TbCopy } from "react-icons/tb";
-import PropTypes from 'prop-types';
-
-ActionIcons.propTypes = {
-  text: PropTypes.string.isRequired,
-  onThumbsUp: PropTypes.func,
-  onThumbsDown: PropTypes.func,
-  onCopy: PropTypes.func
-};
-
-export default function ActionIcons({ 
-  text, 
-  onThumbsUp,
-  onThumbsDown,
-  onCopy
-}) {
-  const [hoveredButton, setHoveredButton] = useState(null);
-
-  const handleThumbsUp = () => {
-    if (onThumbsUp) {
-      onThumbsUp();
-    } else {
-      console.log('Thumbs up clicked');
-    }
-  };
-
-  const handleThumbsDown = () => {
-    if (onThumbsDown) {
-      onThumbsDown();
-    } else {
-      console.log('Thumbs down clicked');
-    }
-  };
-
-  const handleCopy = () => {
-    if (onCopy) {
-      onCopy(text);
-    } else {
-      navigator.clipboard.writeText(text);
-      console.log('Text copied to clipboard');
-    }
-  };
-
-  return (
-    <div className="flex items-center gap-2 mt-2 justify-end">
-      <div className="relative">
-        <button
-          onClick={handleThumbsUp}
-          className="p-1 rounded hover:bg-[#484848]/20 transition-colors duration-200 cursor-pointer"
-          onMouseEnter={() => setHoveredButton('helpful')}
-          onMouseLeave={() => setHoveredButton(null)}
-        >
-          <TbThumbUp 
-            size={14} 
-            className="text-[#a4a4a8]/40 hover:text-[#a4a4a8] transition-colors duration-200" 
-          />
-        </button>
-        {hoveredButton === 'helpful' && (
-          <div className="absolute bottom-full mb-1 left-1/2 transform -translate-x-1/2 bg-black text-white text-xs px-2 py-1 rounded whitespace-nowrap">
-            Helpful
-          </div>
-        )}
-      </div>
-      
-      <div className="relative">
-        <button
-          onClick={handleThumbsDown}
-          className="p-1 rounded hover:bg-[#484848]/20 transition-colors duration-200 cursor-pointer"
-          onMouseEnter={() => setHoveredButton('notHelpful')}
-          onMouseLeave={() => setHoveredButton(null)}
-        >
-          <TbThumbDown 
-            size={14} 
-            className="text-[#a4a4a8]/40 hover:text-[#a4a4a8] transition-colors duration-200" 
-          />
-        </button>
-        {hoveredButton === 'notHelpful' && (
-          <div className="absolute bottom-full mb-1 left-1/2 transform -translate-x-1/2 bg-black text-white text-xs px-2 py-1 rounded whitespace-nowrap">
-            Not helpful
-          </div>
-        )}
-      </div>
-      
-      <div className="relative">
-        <button
-          onClick={handleCopy}
-          className="p-1 rounded hover:bg-[#484848]/20 transition-colors duration-200 cursor-pointer"
-          onMouseEnter={() => setHoveredButton('copy')}
-          onMouseLeave={() => setHoveredButton(null)}
-        >
-          <TbCopy 
-            size={14} 
-            className="text-[#a4a4a8]/40 hover:text-[#a4a4a8] transition-colors duration-200" 
-          />
-        </button>
-        {hoveredButton === 'copy' && (
-          <div className="absolute bottom-full mb-1 left-1/2 transform -translate-x-1/2 bg-black text-white text-xs px-2 py-1 rounded whitespace-nowrap">
-            Copy
-          </div>
-        )}
-      </div>
-    </div>
-  );
-} 
\ No newline at end of file
diff --git a/src/components/ui/AppActionMenu.jsx b/src/components/ui/AppActionMenu.jsx
new file mode 100644
index 0000000..0eb8317
--- /dev/null
+++ b/src/components/ui/AppActionMenu.jsx
@@ -0,0 +1,58 @@
+import React, { useEffect, useRef } from 'react';
+import PropTypes from 'prop-types';
+
+AppActionMenu.propTypes = {
+  visible: PropTypes.bool,
+  onRequestClose: PropTypes.func,
+  items: PropTypes.arrayOf(PropTypes.shape({
+    id: PropTypes.string,
+    label: PropTypes.string,
+    icon: PropTypes.node,
+    onClick: PropTypes.func
+  }))
+}
+
+export default function AppActionMenu({ visible, onRequestClose, items }) {
+  const menuRef = useRef(null);
+
+  useEffect(() => {
+    if (!visible) return;
+
+    const handleClickOutside = (event) => {
+      if (menuRef.current && !menuRef.current.contains(event.target)) {
+        onRequestClose();
+      }
+    };
+
+    document.addEventListener('mousedown', handleClickOutside);
+    return () => document.removeEventListener('mousedown', handleClickOutside);
+  }, [visible, onRequestClose]);
+
+  if (!visible) return null;
+
+  return (
+    <div
+      ref={menuRef}
+      className="absolute bottom-full mb-2 
+                 w-48 rounded-lg border border-[#484848]/60
+                 bg-[#1e1e1e]/80 backdrop-blur p-1 flex flex-col shadow-lg"
+    >
+      {items.map((item, idx) => (
+        <div key={item.id}>
+          <button
+            onClick={item.onClick}
+            className="flex items-center gap-2 px-2 py-1 rounded-md w-full
+                       hover:bg-[#ffffff]/5 transition-colors text-left"
+          >
+            {item.icon}
+            <span className="text-xs leading-none text-[#a4a4a8]">{item.label}</span>
+          </button>
+
+          {idx < items.length - 1 && (
+            <div className="h-px mx-2 bg-[#484848]/40" />
+          )}
+        </div>
+      ))}
+    </div>
+  );
+}
diff --git a/src/components/ui/BrowserSelection.jsx b/src/components/ui/BrowserSelection.jsx
index d9bd4a6..9ff7181 100644
--- a/src/components/ui/BrowserSelection.jsx
+++ b/src/components/ui/BrowserSelection.jsx
@@ -1,141 +1,118 @@
-import React, { useState, useRef, useEffect } from 'react';
-import { HiChevronRight } from 'react-icons/hi';
-import { useAppState } from '../../hooks/useAppState';
-import { browserIcons } from '../../lib/utils';
-import PropTypes from 'prop-types';
+import React, { useEffect, useState } from "react";
+import PropTypes from "prop-types";
+import clsx from "clsx";
+import { useAppState } from "../../hooks/useAppState";
+import { browserIcons } from "../../lib/utils";
 
 BrowserSelection.propTypes = {
-    isConnected: PropTypes.bool.isRequired,
-    onLaunchAndConnect: PropTypes.func.isRequired,
-    onCloseBrowser: PropTypes.func.isRequired,
-}
+  onLaunchAndConnect: PropTypes.func.isRequired,
+  isConnected: PropTypes.bool.isRequired,
+};
 
-export default function BrowserSelection(props) {
-    const { 
-        availableBrowsers, 
-        selectedBrowserPath, 
-        setSelectedBrowserPath 
-    } = useAppState();
-    const [isOpen, setIsOpen] = useState(false);
-    const modalRef = useRef(null);
-    const browserButtonRef = useRef(null);
+export default function BrowserSelection({
+  isConnected,
+  onLaunchAndConnect,
+}) {
+  /*
+  ** global state
+  */
+  const [isBrowserLaunching, setIsBrowserLaunching] = useState(false);
+  const {
+    availableBrowsers,
+    currentBrowserPath,
+    setCurrentBrowserPath,
+    loadAvailableBrowsers,
+  } = useAppState();
 
-    const { isConnected, onLaunchAndConnect, onCloseBrowser } = props;
-    
-    const selectedBrowser = availableBrowsers.find(browser => 
-        browser.path === selectedBrowserPath
-    ) || { id: 'default', name: 'Browser' };
-    
-    useEffect(() => {
-        function handleClickOutside(event) {
-            if (!isOpen) return;
-            
-            if ( modalRef.current && 
-                !modalRef.current.contains(event.target) &&
-                (!browserButtonRef.current || !browserButtonRef.current.contains(event.target))
-            ) {
-                setIsOpen(false);
-            }
-        }
-        
-        document.addEventListener("mousedown", handleClickOutside);
-        return () => document.removeEventListener("mousedown", handleClickOutside);
-    }, [isOpen]);
-  
-    const toggleModal = () => {
-        setIsOpen(prev => !prev);
-    };
-  
-    const selectBrowser = (browserPath) => {
-        setSelectedBrowserPath(browserPath);
-        setIsOpen(false);
-        
-        if (!isConnected) {
-            setTimeout(() => {
-                onLaunchAndConnect();
-            }, 100);
-        }
-    };
-  
-    const browserIcon = browserIcons[selectedBrowser.id] || browserIcons.default;
+  /*
+  ** fetch once on mount
+  */
+  useEffect(() => {
+    if (availableBrowsers.length === 0) {
+      loadAvailableBrowsers();
+    }
+  }, [availableBrowsers.length, loadAvailableBrowsers]);
 
-    const handleConnectDisconnect = () => {
-        if (isConnected) {
-            onCloseBrowser(true);
-        } else {
-            onLaunchAndConnect();
-        }
-    };
-  
+  /*
+  ** keep selection valid
+  */
+  useEffect(() => {
+    if (
+      availableBrowsers.length &&
+      !availableBrowsers.some(
+        (b) => b.path === currentBrowserPath)
+    ) {
+      setCurrentBrowserPath(availableBrowsers[0].path);
+    }
+  }, [availableBrowsers, currentBrowserPath]);
+
+  /*
+  ** watch whenever the browser is connected, then 
+  */
+  useEffect(() => {
+    if (isConnected) {
+      setIsBrowserLaunching(false);
+    }
+  }, [isConnected]);
+
+  /*
+  ** click handler
+  */
+  const pickBrowser = async (browser) => {
+    setCurrentBrowserPath(browser.path);
+    setIsBrowserLaunching(true);
+    await onLaunchAndConnect(browser.path);
+  };
+
+  /*
+  ** empty state
+  */
+  if (!availableBrowsers.length) {
     return (
-        <div className="relative flex items-center w-full">
-            <div 
-                ref={browserButtonRef}
-                className="flex items-center cursor-pointer gap-2"
-                onClick={toggleModal}
-            >
-                <div className="w-6 h-6 flex items-center justify-center">
-                    <img src={browserIcon} alt={selectedBrowser.name} className="w-5 h-5" />
-                </div>
-                <div className="flex items-center gap-1.5">
-                    <div className={`w-1.5 h-1.5 rounded-full ${isConnected ? 'bg-green-500' : 'bg-gray-500'}`}></div>
-                    <span className="text-white text-sm ml-1">{isConnected ? 'Connected' : 'Disconnected'}</span>
-                    <HiChevronRight className={`w-4 h-4 text-gray-400 transition-transform duration-200 ${isOpen ? 'rotate-90' : ''}`} />
-                </div>
-            </div>
-        
-        {isOpen && (
-            <div ref={modalRef}
-                className="absolute top-full left-0 right-0 mt-1 bg-[#242424] border border-[#484848]/70 rounded-md shadow-lg z-10"
-                >
-                <div className="px-4 py-3">
-                    <div className="flex items-center gap-2 mb-3">
-                        <div className={`w-1.5 h-1.5 rounded-full ${isConnected ? 'bg-green-500' : 'bg-gray-500'}`}></div>
-                        <span className="text-white text-sm">{isConnected ? 'Connected' : 'Disconnected'}</span>
-                    </div>
-                    
-                    <button onClick={handleConnectDisconnect}
-                        className={`w-full py-1 px-4 rounded-md text-white font-medium ${
-                            isConnected ? 'bg-[#323232] hover:bg-[#484848]' : 'bg-blue-600 hover:bg-blue-700'
-                        } transition-colors`}
-                        >
-                        {isConnected ? 'Disconnect' : 'Connect'}
-                    </button>
-                </div>
-            
-                <div className="h-px bg-[#484848]/70 mx-2"></div>
-            
-                <div className="py-2">
-                    <div className="px-4 mb-1 text-xs text-gray-400 uppercase">Available Browsers</div>
-                    { availableBrowsers.length > 0 ? (
-                        <div>
-                            {availableBrowsers.map(browser => (
-                            <div 
-                                key={browser.path}
-                                className="flex items-center gap-2 px-4 py-2 hover:bg-[#323232] cursor-pointer"
-                                onClick={() => selectBrowser(browser.path)}
-                            >
-                                {browser.path === selectedBrowserPath && ( <div className="w-1.5 h-1.5 rounded-full bg-green-500"></div> )}
-                                {browser.path !== selectedBrowserPath && ( <div className="w-1.5 h-1.5"></div> )}
-                                <div className="w-5 h-5 flex items-center justify-center">
-                                <img 
-                                    src={browserIcons[browser.id] || browserIcons.default} 
-                                    alt={browser.name} 
-                                    className="w-4 h-4" 
-                                />
-                                </div>
-                                <span className="text-white text-sm">{browser.name}</span>
-                            </div>
-                            ))}
-                        </div>
-                        ) : (
-                        <div className="px-4 py-2 text-gray-400 text-sm">
-                            No browsers available
-                        </div>
-                    )}
-                </div>
-            </div>
-        )}
-        </div>
+      <div className="px-3 py-1.5 rounded-md bg-[#3a3a3a] text-sm text-gray-400">
+        No browsers detected
+      </div>
     );
+  }
+
+  /*
+  ** UI
+  */
+  return (
+    <div className="flex gap-2 px-2 py-2 bg-[#303030] rounded-md border border-[#494949]/60 select-none mt-2">
+      {availableBrowsers.map((b) => {
+        const isSel = b.path === currentBrowserPath;
+        const showDot = isSel;
+        const isUp = isSel && isConnected;
+        return (
+          <button
+            key={b.path}
+            title={b.name}
+            onClick={() => pickBrowser(b)}
+            className={clsx(
+              "relative p-1 rounded-md hover:bg-[#3a3a3a] transition-colors",
+              isSel && "ring-2 ring-blue-500"
+            )}
+          >
+            <img
+              src={browserIcons[b.id] || browserIcons.default}
+              alt={b.name}
+              className={clsx(
+                "w-6 h-6",
+                isBrowserLaunching && isSel && "animate-spin"
+              )}
+            />
+            {showDot && (
+              <span
+                className={clsx(
+                  "absolute -bottom-0.5 -right-0.5 w-2 h-2 rounded-full",
+                  isUp ? "bg-green-500" : "bg-gray-500"
+                )}
+              />
+            )}
+          </button>
+        );
+      })}
+    </div>
+  );
 }
\ No newline at end of file
diff --git a/src/components/ui/Construction.jsx b/src/components/ui/Construction.jsx
index 5a4f13c..c16cd2d 100644
--- a/src/components/ui/Construction.jsx
+++ b/src/components/ui/Construction.jsx
@@ -68,6 +68,14 @@ export default function Construction({ activeTab, tasks = [] }) {
     return () => clearTimeout(id);
   }, [tasks, appearedPlans]);
 
+  /*
+  ** parse link to domain name
+  */
+  const parseLinkToDomain = (link) => {
+    const url = new URL(link);
+    return url.hostname;
+  }
+
   /*
   ** get the status icon
   */
@@ -167,46 +175,33 @@ export default function Construction({ activeTab, tasks = [] }) {
   ** render the tab card
   */
   const allTabs = tasks.flatMap((t) => t.tabs || []);
-  const renderTabCard = (tab, idx) => (
+  const renderTabCard = (url, idx) => (
     <div
-      className="flex flex-col bg-zinc-800/20 gap-1 border border-zinc-700/20 hover:border-zinc-700/60 hover:bg-zinc-700/10 transition-colors rounded-md py-3 pl-3 pr-5 relative"
-      key={tab.id}
-      onClick={() => linkRefs.current[tab.id]?.click()}
+      className="flex flex-col bg-zinc-800/20 gap-1 border border-zinc-700/20 hover:border-blue-400/25 
+                 hover:bg-zinc-700/10 transition-colors rounded-md py-1 pl-3 pr-5 relative hover:cursor-pointer"
+      key={idx}
+      onClick={() => linkRefs.current[url]?.click()}
     >
-      <div className="absolute top-0 left-0 flex items-center justify-between w-full h-5 px-1">
-        <span className="text-xs font-medium text-zinc-400">{idx + 1}</span>
-        {tab.status && (
-          <span className="flex items-center gap-1">
-            {statusIcon(tab.status)}
-            <span className="text-xs text-zinc-500">{tab.status}</span>
-          </span>
-        )}
-      </div>
 
-      <div className="flex items-center gap-2 mt-2">
+      <div className="flex items-center gap-2">
         <div className="w-7 h-7 flex items-center justify-center rounded-full overflow-hidden">
-          {tab.icon ? (
-            <img src={tab.icon} alt={tab.title} className="w-full h-full" />
-          ) : (
-            <HiOutlineGlobeAlt className="w-7 h-7 text-zinc-400/70" />
-          )}
+          <HiOutlineGlobeAlt className="w-7 h-7 text-zinc-400/70" />
         </div>
 
-        <div className="flex items-center grow max-w-[80%] relative">
-          <h3 className="text-sm font-medium text-zinc-300 truncate mb-1">
-            {tab.title}
+        <div className="flex items-center grow max-w-[80%] h-full relative -mt-3">
+          <h3 className="text-md font-medium text-zinc-300 truncate">
+            {parseLinkToDomain(url)}
           </h3>
 
-          <div className="flex items-center w-full absolute -bottom-3">
-            <FiArrowUpRight className="w-[14px] h-[14px] text-zinc-400" />
+          <div className="flex items-center w-full absolute -bottom-4">
             <a
-              ref={(el) => (linkRefs.current[tab.id] = el)}
-              href={tab.url}
+              ref={(el) => (linkRefs.current[url] = el)}
+              href={url}
               target="_blank"
               rel="noopener noreferrer"
-              className="text-xs text-zinc-400 hover:text-zinc-300 truncate"
+              className="text-[10px] text-zinc-400 truncate"
             >
-              {tab.url}
+              {url}
             </a>
           </div>
         </div>
@@ -232,12 +227,11 @@ export default function Construction({ activeTab, tasks = [] }) {
           </div>
         ) : (
           <div className="flex items-center gap-2 text-xs text-zinc-400">
-            <BiLoader className="w-3 h-3 animate-spin" />
-            <span>Preparing execution plan…</span>
           </div>
         )
       ) : allTabs.length ? (
-        <div className="flex flex-col gap-1 mb-4">
+        <div className="max-h-[200px] overflow-y-auto flex flex-col gap-1 mb-4 border
+                        border-zinc-700/40 rounded-[14px] px-2 py-1">
           {allTabs.map(renderTabCard)}
         </div>
       ) : (
diff --git a/src/components/ui/PromptInput.jsx b/src/components/ui/PromptInput.jsx
index dc95f5f..d803f0a 100644
--- a/src/components/ui/PromptInput.jsx
+++ b/src/components/ui/PromptInput.jsx
@@ -1,10 +1,15 @@
 import { useState, useRef, useEffect } from "react";
-import { HiArrowUp } from "react-icons/hi2";
+import { HiArrowUp, HiStop } from "react-icons/hi2";
 import { useAppState } from "../../hooks/useAppState";
 
-export default function PromptInput({ placeholder, onSubmit }) {
+export default function PromptInput({ 
+    placeholder, 
+    onSubmit,
+    onStop,
+    isProcessing
+}) {
     const [text, setText] = useState("");
-    const { setCurrentQuery } = useAppState();
+    const { setCurrentQuery, runtimeMode, setRuntimeMode } = useAppState();
     const textareaRef = useRef(null);
 
     const handleSend = () => {
@@ -14,6 +19,10 @@ export default function PromptInput({ placeholder, onSubmit }) {
         setText("");
     }
 
+    const handleStop = () => {
+        onStop();
+    }
+
     useEffect(() => {
         const textarea = textareaRef.current;
         if (textarea) {
@@ -30,30 +39,70 @@ export default function PromptInput({ placeholder, onSubmit }) {
                 Control your browser
             </div> */}
             <div className="flex items-center w-full rounded-[12px] bg-[#323232] gap-1 p-1 border border-[#484848]/70">
-                <textarea
-                    ref={textareaRef}
-                    className="flex-grow resize-none bg-transparent rounded p-2 text-base no-scrollbar 
-                            focus:outline-none min-h-[40px] max-h-[100px]"
-                    rows={1}
-                    placeholder={placeholder}
-                    value={text}
-                    onChange={e => setText(e.target.value)}
-                    onKeyDown={e => (e.key === 'Enter' && !e.shiftKey ? (e.preventDefault(), handleSend()) : null)}
-                />
+                <div className="flex flex-col w-full gap-1 ">
+                    <textarea
+                        ref={textareaRef}
+                        className="flex-grow resize-none bg-transparent rounded p-2 text-base no-scrollbar 
+                                focus:outline-none min-h-[40px] max-h-[100px]"
+                        rows={1}
+                        placeholder={placeholder}
+                        value={text}
+                        onChange={e => setText(e.target.value)}
+                        onKeyDown={e => (e.key === 'Enter' && !e.shiftKey ? (e.preventDefault(), handleSend()) : null)}
+                    />
 
-                <div className="flex items-end self-stretch pb-[5px]">
-                    <div 
-                        onClick={handleSend}
-                        className={`h-8 w-8 mr-1 rounded-full flex items-center justify-center duration-200 transition-colors ${
-                            text.trim() ? 'bg-white cursor-pointer' : 'bg-gray-400/20'
-                        }`}
-                    >
-                        <HiArrowUp 
-                            strokeWidth={2}
-                            className={`w-4 h-4 duration-300 transition-colors ${text.trim() ? 'text-zinc-800' : 'text-gray-400'}`} 
-                        />
+                    <div className="flex items-center justify-between self-stretch">
+                        <div className="flex items-center gap-1"> 
+                            <div 
+                                onClick={() => setRuntimeMode("research")}
+                                className={`flex items-center gap-1.5 cursor-pointer rounded-lg py-1 px-1.5 border transition-colors ${
+                                    runtimeMode === "research" 
+                                        ? 'border-blue-500 bg-blue-500/20' 
+                                        : 'border-[#404144] hover:bg-[#404144]'
+                                }`}
+                            >
+                                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill={runtimeMode === "research" ? "#60a5fa" : "#969696"} viewBox="0 0 256 256" className="w-3 h-3">
+                                    <path d="M229.66,218.34l-50.07-50.06a88.11,88.11,0,1,0-11.31,11.31l50.06,50.07a8,8,0,0,0,11.32-11.32ZM40,112a72,72,0,1,1,72,72A72.08,72.08,0,0,1,40,112Z"></path>
+                                </svg>
+                                <span className={`text-xs mr-1 ${runtimeMode === "research" ? 'text-blue-400' : 'text-white/70'}`}>research</span>
+                            </div>
+                            <div 
+                                onClick={() => setRuntimeMode("action")}
+                                className={`flex items-center gap-1.5 cursor-pointer rounded-lg py-1 px-1.5 border transition-colors ${
+                                    runtimeMode === "action" 
+                                        ? 'border-blue-500 bg-blue-500/20' 
+                                        : 'border-[#404144] hover:bg-[#404144]'
+                                }`}
+                            >
+                                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill={runtimeMode === "action" ? "#60a5fa" : "#969696"} viewBox="0 0 256 256" className="w-3 h-3">
+                                    <path d="M237.33,106.21,61.41,41l-.16-.05A16,16,0,0,0,40.9,61.25a1,1,0,0,0,.05.16l65.26,175.92A15.77,15.77,0,0,0,121.28,248h.3a15.77,15.77,0,0,0,15-11.29l.06-.2,21.84-78,78-21.84.2-.06a16,16,0,0,0,.62-30.38ZM149.84,144.3a8,8,0,0,0-5.54,5.54L121.3,232l-.06-.17L56,56l175.82,65.22.16.06Z"></path>
+                                </svg>
+                                <span className={`text-xs mr-1 ${runtimeMode === "action" ? 'text-blue-400' : 'text-white/70'}`}>action</span>
+                            </div>
+                        </div>
+                        <div 
+                            onClick={isProcessing ? handleStop : handleSend}
+                            className={`h-6 w-6 mr-1 rounded-full flex items-center justify-center duration-200 transition-colors ${
+                                isProcessing
+                                    ? 'bg-white cursor-pointer'
+                                    : (text.trim() ? 'bg-white cursor-pointer' : 'bg-gray-400/20')
+                            }`}
+                        >
+                            {isProcessing ? (
+                                <HiStop 
+                                    strokeWidth={2}
+                                    className={`w-3 h-3 duration-300 transition-colors text-gray-400 ${text.trim() ? 'text-zinc-800' : 'text-zinc-800'}`} 
+                                />
+                            ) : (
+                                <HiArrowUp 
+                                    strokeWidth={2}
+                                    className={`w-3 h-3 duration-300 transition-colors ${text.trim() ? 'text-zinc-800' : 'text-gray-400'}`} 
+                                />
+                            )}
+                        </div>
                     </div>
                 </div>
+                
             </div>
         </div>
     )
diff --git a/src/components/ui/System.jsx b/src/components/ui/System.jsx
index 9820994..6e743d9 100644
--- a/src/components/ui/System.jsx
+++ b/src/components/ui/System.jsx
@@ -6,7 +6,7 @@ import PropTypes from 'prop-types';
 
 import Construction from './Construction';
 import ParseText from './ParseText';
-import ActionIcons from './ActionIcons';
+import ActionBar from './ActionBar';
 
 System.propTypes = {
   message: PropTypes.object.isRequired,
@@ -70,11 +70,15 @@ export default function System({
           
           {/* Back to simple text display */}
           <div className="mt-3 relative">
-            {/* <p className="text-sm text-[#a4a4a8] font-[500]">{message.text}</p> */}
-            <ParseText text={message.text} />
+            {
+              message.text && (
+                <>
+                  <ParseText text={message.text} />
+                  <ActionBar text={message.text} />
+                </>
+              )
+            }
             
-            {/* Action icons */}
-            <ActionIcons text={message.text} />
           </div>
         </div>
       )}
diff --git a/src/hooks/useAppState.js b/src/hooks/useAppState.js
index 3cb2fad..502f14c 100644
--- a/src/hooks/useAppState.js
+++ b/src/hooks/useAppState.js
@@ -10,42 +10,37 @@ export const useAppState = create(
             sessions: [],      // [{id, title, lastUpdated, createdAt, isActive, ...}]
             sessionMessages: {}, // {sessionId: [messages]}
             activeSessionId: null, // UUID or null
-            currrentQuery: "",
+            currentQuery: "",
             availableBrowsers: [], // [{id, name, path}]
-            selectedBrowserPath: null,
-            savedWsEndpoint: null, // currentws endpoint
-            isLoadingBrowsers: false,
-            errorLoadingBrowsers: null,
-            lastBackgroundUpdateAttempt: 0,
+            runtimeMode: "research", // "research" | "action"
+
+            currentBrowserPath: null,
+            browserPool: {}, // {[path]: wsEndpoint}
+
             browserInstance: null,
             pageInstance: null,
-            logs: [],
             isConnected: false,
 
             // actions
             setActiveSessionId: (id) => set({activeSessionId: id}),
-            setCurrentQuery: (query) => set({currrentQuery: query}),
+            /*
+            ** query handling
+            */
+            setCurrentQuery: (query) => set({currentQuery: query}),
+            setRuntimeMode: (mode) => set({runtimeMode: mode}),
+            /*
+            ** view handling
+            */
             openHome: () => set({view: "home", activeSessionId: null}),
             openHistory: () => set({view: "history"}),
             openSession: (id) => set({view: "session", activeSessionId: id}),
             openProfile: () => set({view: "profile"}),
-            totalSessions: () => get().sessions.length,
-            addSession: session => 
-                set(state => ({sessions: [session, ...state.sessions]})),
-            removeSession: (id) =>
-                set(state => ({
-                    sessions: state.sessions.filter(session => session?.id !== id),
-                    sessionMessages: {...state.sessionMessages, [id]: undefined}
-                })),
+            /*
+            ** session handling
+            */
+            addSession: session =>  set(state => ({sessions: [session, ...state.sessions]})),
             clearSessions: () => set({ sessions: [], sessionMessages: {} }),
             getSessionMessages: (sessionId) => get().sessionMessages[sessionId] || [],
-            saveSessionMessages: (sessionId, messages) => 
-                set(state => ({
-                    sessionMessages: {
-                        ...state.sessionMessages,
-                        [sessionId]: messages
-                    }
-                })),
             addMessageToSession: (sessionId, message, update = false) =>
                 set(state => {
                     const currentMessages = state.sessionMessages[sessionId] || [];
@@ -75,46 +70,24 @@ export const useAppState = create(
                         };
                     }
             }),
-            fetchBrowsers: async (isBackgroundUpdate = false) => {
-                const now = Date.now();
-                if (isBackgroundUpdate && (now - get().lastBackgroundUpdateAttempt) < 10000) { return; }
-
-                if (!isBackgroundUpdate) {
-                    set({isLoadingBrowsers: true, errorLoadingBrowsers: null});
-                } else {
-                    set({lastBackgroundUpdateAttempt: now});
-                }
-
-                try {
-                    const browsers = await invoke("fetch_available_browsers");
-                    set({
-                        availableBrowsers: browsers,
-                        isLoadingBrowsers: false,
-                        errorLoadingBrowsers: null
-                    });
-
-                    const currentSelectedPath = get().selectedBrowserPath;
-                    const selectedStillExists = browsers.some(b => b.path === currentSelectedPath)
-
-                    if (browsers.length > 0 && (!currentSelectedPath || !selectedStillExists)) {
-                        set({ selectedBrowserPath: browsers[0].path });
-                    } else if (browsers.length === 0) {
-                        set({ selectedBrowserPath: null}); // no browser found
-                    }
-                } catch (error) {
-                    const errorMessage = error.message || (typeof error === 'string' ? error : "Failed to fetch browsers")
-                    console.error("Failed to fetch browsers:", errorMessage);
-                    if (!isBackgroundUpdate) {
-                        set({errorLoadingBrowsers: errorMessage, isLoadingBrowsers: false});
-                    } else {
-                        console.warn("Background browser fetch failed:", errorMessage);
-                    }
-                }
+            setCurrentBrowserPath: (path) => set({currentBrowserPath: path}),
+            /*
+            ** add/update one entry
+            */
+            rememberBrowser: (path, ws) => {
+                set(s => ({ browserPool: {...s.browserPool, [path]: ws}}))
             },
-
-            setSelectedBrowserPath: (path) => set({selectedBrowserPath: path}),
-            setSavedWsEndpoint: (endpoint) => set({savedWsEndpoint: endpoint}),
-            clearSavedWsEndpoint: () => set({savedWsEndpoint: null}),
+            /*
+            ** drop one entry (only when it is actively closed)
+            */
+            forgetBrowser: (path) => {
+                set(s => { 
+                    const pool = {...s.browserPool};
+                    delete pool[path];
+                    return { browserPool: pool }
+                })
+            },
+            getWsFor: path => get().browserPool[path] || null,
             
             /*
             ** browser-related actions
@@ -122,20 +95,26 @@ export const useAppState = create(
             setBrowserInstance: (instance) => set({browserInstance: instance}),
             setPageInstance: (instance) => set({pageInstance: instance}),
             setIsConnected: (connected) => set({isConnected: connected}),
-            addLog: (message, type = "info") => 
-                set(state => ({
-                    logs: [`[${new Date().toLocaleTimeString()}] [${type.toUpperCase()}] ${message}`, ...state.logs.slice(0, 29)]
-                })),
-            clearLogs: () => set({logs: []}),
+            loadAvailableBrowsers: async () => {
+                try {
+                    const browsers = await invoke("fetch_available_browsers");
+                    set({availableBrowsers: browsers});
+                } catch (error) {
+                    console.error("Failed to load available browsers:", error);
+                    set({availableBrowsers: []});
+                }
+            }
         }),
         {
             name: "app-sessions",
             storage: createJSONStorage(() => localStorage),
             partialize: (state) => ({ 
                 sessions: state.sessions,
-                selectedBrowserPath: state.selectedBrowserPath,
+                currentBrowserPath: state.currentBrowserPath,
                 sessionMessages: state.sessionMessages,
-                savedWsEndpoint: state.savedWsEndpoint
+                browserPool: state.browserPool,
+                isConnected: state.isConnected,
+                runtimeMode: state.runtimeMode,
             }),
         }
     )
diff --git a/src/lib/dom.ts b/src/lib/dom.ts
index 8d803b5..1c8d3cc 100644
--- a/src/lib/dom.ts
+++ b/src/lib/dom.ts
@@ -12,6 +12,7 @@ export class DomService {
         this.page = page;
     }
 
+    // legacy
     // get clickable elements with indices
     async getClickableElementsWithIndices(options: {
         highlightElements?: boolean;
@@ -27,17 +28,6 @@ export class DomService {
         } = options;
 
         const result = await this.page.evaluate((opts) => {
-            // remove any existing highlights first
-            document.querySelectorAll('[data-runtime-highlight]').forEach(el => {
-                if (el.getAttribute('data-runtime-highlight')?.startsWith('label-')) {
-                    el.remove();
-                } else {
-                    el.removeAttribute('data-runtime-highlight');
-                    (el as HTMLElement).style.removeProperty('outline');
-                    (el as HTMLElement).style.removeProperty('outline-offset');
-                    (el as HTMLElement).style.removeProperty('box-shadow');
-                }
-            });
 
             // define clickable selectors
             const clickableSelectors = [
@@ -75,16 +65,6 @@ export class DomService {
             // remove duplicates
             const uniqueElements = [...new Set(elements)];
 
-            /*
-            ** filter out parent elements that contain other interactive elements in order to select leaf elements for an action
-            */
-           const finalElements = uniqueElements.filter(element => {
-                for (const otherEl of uniqueElements) {
-                    if (element !== otherEl && (element as HTMLElement).contains(otherEl as HTMLElement)) return false;
-                }
-                return true;
-            });
-
             const elementMap = {};
             let index = 0;
 
@@ -192,42 +172,6 @@ export class DomService {
 
                 elementMap[index] = elementInfo;
 
-                // add highlighting if requested
-                if (opts.highlightElements && isVisible) {
-                    element.setAttribute('data-runtime-highlight', index.toString());
-                    
-                    const isFocused = index === opts.focusElement;
-                    const color = isFocused ? '#ff0000' : `hsl(${(index * 137) % 360}, 70%, 50%)`;
-                    const outlineWidth = isFocused ? '2px' : '1px';
-                    
-                    (element as HTMLElement).style.outline = `${outlineWidth} solid ${color}`;
-                    (element as HTMLElement).style.outlineOffset = '1px';
-                    (element as HTMLElement).style.boxShadow = `0 0 0 1px ${color}`;
-                    
-                    // add index label with lower z-index to ensure overlay can cover it
-                    const label = document.createElement('div');
-                    label.textContent = index.toString();
-                    label.style.cssText = `
-                        position: absolute;
-                        top: ${rect.top + window.scrollY - 20}px;
-                        left: ${rect.left + window.scrollX}px;
-                        background: ${color};
-                        color: white;
-                        padding: 1px 4px;
-                        border-radius: 3px;
-                        font-size: 11px;
-                        font-weight: bold;
-                        z-index: 10000;
-                        pointer-events: none;
-                        font-family: monospace;
-                        line-height: 1;
-                        min-width: 16px;
-                        text-align: center;
-                    `;
-                    label.setAttribute('data-runtime-highlight', `label-${index}`);
-                    document.body.appendChild(label);
-                }
-
                 index++;
             });
 
@@ -253,22 +197,6 @@ export class DomService {
         return result;
     }
 
-    // remove highlights
-    async removeHighlights() {
-        await this.page.evaluate(() => {
-            document.querySelectorAll('[data-runtime-highlight]').forEach(el => {
-                if (el.getAttribute('data-runtime-highlight')?.startsWith('label-')) {
-                    el.remove();
-                } else {
-                    el.removeAttribute('data-runtime-highlight');
-                    (el as HTMLElement).style.removeProperty('outline');
-                    (el as HTMLElement).style.removeProperty('outline-offset');
-                    (el as HTMLElement).style.removeProperty('box-shadow');
-                }
-            });
-        });
-    }
-
     // click element by index
     async clickElementByIndex(index: number, elementMap: Record<number, any>) {
         if (!elementMap[index]) {
@@ -316,32 +244,175 @@ export class DomService {
         return { success: true, message: `Clicked element ${index} and navigated` };
     }
 
-    // get accessibility tree snapshot
-    async getAccessibilityTree() {
-        try {
-            const snapshot = await this.page.accessibility.snapshot({ interestingOnly: true });
+    // new
+    async clickElement({targetIndex, elements}: { targetIndex: number, elements: any[]}) {
+        const elInfo = elements.find(e => e.index === targetIndex);
+        if (!elInfo) throw new Error(`Element with index ${targetIndex} not found`);
+      
+        const [nav, click] = await Promise.allSettled([
+          this.page.waitForNavigation({ waitUntil: 'domcontentloaded', timeout: 10_000 }),
+          this.page.evaluate(xp => {
+            const node = document.evaluate(
+              xp,
+              document,
+              null,
+              XPathResult.FIRST_ORDERED_NODE_TYPE,
+              null
+            ).singleNodeValue as HTMLElement | null;
+      
+            if (!node) throw new Error('Element not found by XPath');
+      
+            node.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' });
+      
+            if ((node as HTMLInputElement).type === 'file') {
+              return { isFileInput: true };
+            }
+      
+            node.click();
+            return { clicked: true };
+          }, elInfo.xpath)
+        ]);
+      
+        // special case: <input type="file">
+        if ((click as PromiseFulfilledResult<any>).value?.isFileInput) {
+          return { isFileInput: true, message: 'File input detected - use upload_file instead' };
+        }
+      
+        // navigation is optional
+        if (nav.status === 'rejected') {
+          return { success: true, message: `Clicked element ${targetIndex} (no navigation)` };
+        }
+      
+        return { success: true, message: `Clicked element ${targetIndex} and navigated` };
+    }
+
+    // new
+    async collectIntertiveElements() {
+        return await this.page.evaluate(() => {
+            const gather = (root: ParentNode, out: HTMLElement[] = []) => {
+                const sel = [
+                'a[href]:not([href=""])',
+                'button:not([disabled])',
+                'input:not([type=hidden]):not([disabled])',
+                'textarea:not([disabled])',
+                'select:not([disabled])',
+                '[role="button"]',
+                '[role="link"]',
+                '[role="textbox"]',
+                '[role="checkbox"]',
+                ].join(',');
+                root.querySelectorAll<HTMLElement>(sel).forEach(el => {
+                /* skip container elements that contain another candidate */
+                if (![...el.querySelectorAll(sel)].some(c => c !== el)) out.push(el);
+                });
+        
+                /* dive into any open shadow roots */
+                (root as any).querySelectorAll?.('*')?.forEach((n: any) => {
+                if (n.shadowRoot) gather(n.shadowRoot, out);
+                });
+                return out;
+            };
             
-            const flattenTree = (node: any, depth = 0, result: string[] = []) => {
-                if (node.role && node.name) {
-                    result.push(`${'  '.repeat(depth)}${node.role} ${node.name}`);
+            const trim = (t: string | null) => (t && t.trim() ? t.trim() : undefined);
+
+            /* use the ARIA algorithm to approximate accessible-name */
+            const accName = (el: HTMLElement) => {
+                const lbl = (el as any).ariaLabel || el.getAttribute?.('aria-label');
+                if (lbl) return trim(lbl);
+
+                const labelledBy = el.getAttribute?.('aria-labelledby');
+                if (labelledBy) {
+                    const txt = labelledBy
+                                .split(/\s+/)
+                                .map(id => document.getElementById(id)?.textContent ?? '')
+                                .join(' ')
+                    return trim(txt);
                 }
-                
-                if (node.children) {
-                    for (const child of node.children) {
-                        flattenTree(child, depth + 1, result);
-                    }
-                }
-                
-                return result;
+                    
+                return (
+                    trim(el.getAttribute?.('title')) ||
+                    trim((el as HTMLInputElement).placeholder) ||
+                    trim(el.textContent)
+                )
             };
 
-            const lines = flattenTree(snapshot);
-            return lines.join('\n');
-        } catch (error) {
-            return `Error getting accessibility tree: ${error.message}`;
-        }
+            const buildXPath = (el: Element): string => {
+                if (el.id) return `//*[@id="${el.id}"]`;
+                if (el === document.body) return '/html/body';
+                const sameTagSiblings = Array.from(el.parentNode!.children).filter(
+                sib => sib.tagName === el.tagName
+                );
+                const idx = sameTagSiblings.indexOf(el) + 1;
+                return buildXPath(el.parentNode as Element) + '/' + el.tagName.toLowerCase() + `[${idx}]`;
+            };
+
+            const result: any[] = [];
+            const leaves = gather(document);
+
+            leaves.forEach((el, i) => {
+                const name = accName(el);
+                if (!name) return;
+
+                result.push({
+                    index : i,
+                    tag   : el.tagName.toLowerCase(),
+                    accessibleName : name,
+                    href : el.tagName.toLowerCase() === 'a' ? (el as HTMLAnchorElement).href : "",
+                    xpath : buildXPath(el)
+                })
+            })
+
+            return result;
+        })
     }
 
+    // new
+    async typeTextByIndex(
+        targetIndex: number,
+        elements: Array<{ index: number; xpath: string }>,
+        text: string
+      ) {
+        const elInfo = elements.find(e => e.index === targetIndex);
+        if (!elInfo) throw new Error(`Element with index ${targetIndex} not found`);
+      
+        return await this.page.evaluate(
+          (xp, value) => {
+            const node = document.evaluate(
+              xp,
+              document,
+              null,
+              XPathResult.FIRST_ORDERED_NODE_TYPE,
+              null
+            ).singleNodeValue as HTMLElement | null;
+      
+            if (!node) throw new Error('Input element not found');
+      
+            node.scrollIntoView({ behavior: 'smooth', block: 'center' });
+            node.focus();
+      
+            if ('value' in node) {
+              (node as HTMLInputElement | HTMLTextAreaElement).value = value;
+            } else {
+              node.textContent = value;           // contenteditable, etc.
+            }
+      
+            node.dispatchEvent(new Event('input', { bubbles: true }));
+            node.dispatchEvent(new Event('change', { bubbles: true }));
+            return { success: true, inputText: value };
+          },
+          elInfo.xpath,
+          text
+        );
+    }
+
+    // new
+    async getInteractiveElements() {
+        const list = await this.collectIntertiveElements();
+        return list.slice(0, 10000);
+    }
+
+
+
     async getVisibleText(selector = 'body') {
         try {
             const ok = await this.waitUntilBodyNotBlank(7_000);
diff --git a/src/lib/legacy/task.browser.helpers.ts b/src/lib/legacy/task.browser.helpers.ts
new file mode 100644
index 0000000..efaa785
--- /dev/null
+++ b/src/lib/legacy/task.browser.helpers.ts
@@ -0,0 +1,355 @@
+import { v4 as uuidv4 } from 'uuid';
+
+import { 
+    BrowserStepBreakdownDeclaration,
+    BrowserPlanRefineDeclaration,
+    BrowserMacroRepairDeclaration,
+    BrowserMicroRepairDeclaration,
+    EvaluateMacroTool
+ } from './task.browser.tools';
+import { callLLM } from '../llm.engine';
+import { getFnCall } from '../task.execution.helpers';
+import { stepTranslator } from '../plan.orchestrator';
+import { handlePuppeteerAction } from '../pptr';
+import { emit, pushHistory, truncate } from "../task.execution.helpers"
+import { DomService } from '../dom';
+import { EvaluateMacroArgs } from './task.browser.schemas';
+
+/*
+** generate a macro browsing plan from a high-level goal
+*/
+export async function generateMicroSteps({ sentence, pageURL }: { sentence: string, pageURL: string }) {
+  const prompt = `You translate a single *macro* instruction into low-level browser actions.
+Allowed low-level action on the browser are:
+- go to a url
+- click on an element by index
+- input text into an element by index
+- send keys to an element by index:
+  • Form navigation: Enter, Tab, Shift+Tab, Escape
+  • Text editing: ArrowUp, ArrowDown, ArrowLeft, ArrowRight, Backspace, Delete, Home, End
+  • Page navigation: Space, PageUp, PageDown
+  • Common shortcuts: Ctrl+A, Ctrl+C, Ctrl+V, Ctrl+Z, Cmd+A, Cmd+C, Cmd+V, Cmd+Z
+- scroll up
+- scroll down
+- wait for a selector to be visible
+- get the visible text of an element by index
+- get the current page structure to extract interactive elements
+- go back
+- go forward
+- refresh the page
+- mark the end of a macro step
+
+# Notes
+Return the minimal ordered list of micro-steps needed to fulfill the goal.
+
+# Examples
+
+Example 1:
+MACRO: "Go to Youtube home page"
+PAGE: "about:blank"
+OUT:
+- go to https://www.youtube.com/
+- wait for the page to load
+- finish the macro step
+
+Example 2:
+MACRO: "Search for 'Elon Musk' and sort result by upload date"
+PAGE: "https://www.youtube.com/"
+OUT:
+- get the current page structure to extract interactive elements
+- locate the search input by index
+- input "Elon Musk" into the search input by index
+- click the search button by index or send keys Enter to submit the search
+- wait for the search results to load
+- sort the search results by upload date
+- finish the macro step
+
+
+# Context
+Current page: ${truncate(pageURL, 200)}
+Macro goal: "${sentence}"
+
+Note: 
+- Do not use the name of tools, but always use the action like for referencing go_to_url could be navigate to.
+- If you have already input text for example "Elon Musk" into an input field, do not send keys "Elon Musk" again, but use the action to send keys Enter to submit the search.
+`;
+  
+    const resp = await callLLM({
+        modelId: "gemini-2.5-flash",
+        contents: [{ role: "user", parts: [{ text: prompt }] }],
+        config: {
+            temperature: 0,
+            maxOutputTokens: 2048,
+            mode:  "ANY",
+            tools: [{ functionDeclarations: [BrowserStepBreakdownDeclaration] }]
+        },
+        ignoreFnCallCheck: true
+    });
+  
+    const fc = getFnCall(resp);
+    return fc?.args?.micro_steps ?? [sentence];
+}
+
+/*
+** refine entire macro plan
+*/
+export async function refineMacroPlan(
+    {originalSteps, userQuery}: 
+    {originalSteps: string[], userQuery: string}
+) {
+    const prompt = `You are a task-planner for a browser automation agent.
+
+# Goal
+Rewrite the agent's high-level steps so they are:
+- strictly sequential
+- actionable (no vague wording)
+- minimal (no redundant work)
+
+# Examples
+Example 1:
+User query: "Book the cheapest flight from NYC to Toronto for the next Friday"
+ORIGINAL STEPS:
+- open booking site
+- search flight
+- pick cheapest
+- checkout
+CORRECTED STEPS:
+- Navigate to online flight aggregator
+- Type "NYC -> Toronto" in the search input in departure field and arrival field respectively
+- Sort by price ascending
+- open the details page of the top-priced flight
+- proceed to checkout until the payment page is displayed (do NOT pay)
+
+Example 2:
+User query: "Find elon musk's latest popular video and read angry comments"
+ORIGINAL STEPS:
+- open youtube
+- search elon musk
+- pick the most popular video
+- read comments
+CORRECTED STEPS:
+- Navigate to youtube home page
+- Type "elon musk" in the search input
+- Sort result by upload date
+- Open the first video that has the most views (100 k+ views)
+- Scroll to the comments section and collect the 50 newest comments
+- Filter comments with negative or angry sentiment and summarise them
+
+# Context
+User query: "${userQuery}"
+ORIGINAL STEPS:
+${originalSteps.map((s) => `- ${s}`).join("\n")}
+
+# Return
+Return the minimal ordered list of micro-steps needed to fulfil the goal.
+Improve it if needed (order, clarity, remove useless steps, add missing steps).
+And be very flexible with steps for browser automation. Avoid producing few steps, it should be atomic.
+`;
+    const resp = await callLLM({
+        modelId: "gemini-2.5-flash",
+        contents: [{ role: "user", parts: [{ text: prompt }] }],
+        config: {
+            temperature: 0,
+            maxOutputTokens: 1024,
+            mode: "ANY",
+            tools: [{ functionDeclarations: [BrowserPlanRefineDeclaration] }]
+        },
+        ignoreFnCallCheck: true
+    });
+    const fc = getFnCall(resp);
+    return fc?.args?.corrected_steps ?? originalSteps;
+}
+
+/*
+** repair one failing macro step
+*/
+export async function repairMacroStep(
+    {failingStep, lastError, pageURL}:
+    {failingStep: string, lastError: string, pageURL: string}
+) {
+    const prompt = `You are a recovery agent. Propose ONE alternative macro step that could achieve the same high-level goal, given the page context and last error.
+
+# Examples
+Example 1:
+FAILING_MACRO: "Open the cheapest flight details page"
+PAGE_URL: "https://www.skyscanner.com/..." <- the page URL
+ERROR: "Selector .price-row not found" <- the last error
+RECOVERY: "Open the first search-result row that shows a price tag" <- the recovery
+
+### CONTEXT
+FAILING_MACRO: "${failingStep}"
+PAGE_URL: ${pageURL}
+ERROR: "${lastError}"
+
+### RECOVERY
+`;
+    const resp = await callLLM({
+        modelId: "gemini-2.5-flash",
+        contents: [{ role: "user", parts: [{ text: prompt }] }],
+        config: {
+            temperature: 0,
+            maxOutputTokens: 256,
+            mode: "ANY",
+            tools: [{ functionDeclarations: [BrowserMacroRepairDeclaration] }]
+        },
+        ignoreFnCallCheck: true
+    });
+    const fc = getFnCall(resp);
+    return fc?.args?.recovery ?? failingStep;
+}
+
+/*
+** repair one failing micro-step
+*/
+export async function repairMicroStep(
+    {failing, lastError, pageURL}:
+    {failing: string, lastError: string, pageURL: string}
+  ) {
+    const prompt = `
+You are a recovery agent. Suggest a single replacement low-level action for the failed step,
+using the same tool grammar as before. Base your fix on the error below.
+
+# Context
+FAILING_MICRO: "${failing}"
+PAGE_URL: ${pageURL}
+ERROR: "${lastError}"
+
+# Return
+Return the recovery as a single low-level action.
+
+# Examples
+Example 1:
+FAILING_MICRO: "Click on the button add to cart"
+PAGE_URL: "https://www.amazon.com/..."
+ERROR: "Timeout 5000ms"
+RECOVERY: "Click on input with the value add to cart"
+`;
+    const resp = await callLLM({
+      modelId: "gemini-2.5-flash",
+      contents: [{ role: "user", parts: [{ text: prompt }] }],
+      config: {
+        temperature: 0,
+        maxOutputTokens: 256,
+        mode: "ANY",
+        tools: [{ functionDeclarations: [BrowserMicroRepairDeclaration] }]
+      },
+      ignoreFnCallCheck: true
+    });
+    const fc = getFnCall(resp);
+    return fc?.args?.recovery ?? failing;
+}
+
+/*
+** execute one micro-step
+*/
+export async function executeMicroStep(
+    {sentence, taskId, page, browser, history}:
+    {sentence: string, taskId: string, page: any, browser: any, history: any[]}
+  ) {
+    /* translate sentence → low-level toolCall (e.g. go_to_url, click) */
+    // ctx is the structure of the page
+    const domService = new DomService(page);
+    const ctx = await domService.getInteractiveElements();
+    const toolCall = await stepTranslator(sentence, history, ctx);
+
+    console.log("translated micro-step", sentence, toolCall);
+    const actionId = uuidv4();
+  
+    emit("task_action_start", {
+      taskId,
+      action: toolCall?.name,
+      speakToUser:  sentence,
+      status: "running",
+      actionId,
+      url: page.url()
+    });
+  
+    /* run once ------------------------------------------------------- */
+    const res = await handlePuppeteerAction({
+      actionDetails: {
+        action: toolCall.name,
+        parameters: toolCall.args,
+        taskId
+      },
+      currentPage: page,
+      browserInstance: browser
+    });
+    console.log("executed micro-step", sentence, res);
+  
+    emit("task_action_complete", {
+      taskId,
+      action: toolCall?.name,
+      speakToUser: sentence,
+      status: res.success ? "success" : "failed",
+      error: res.success ? undefined : res.error,
+      actionId
+    });
+  
+    /* context trimming ------------------------------------------------*/
+    pushHistory(history, toolCall.name, toolCall.args, res.data);
+    if (history.length > 20) history.splice(0, history.length - 20);
+  
+    return {
+      success: res.success,
+      error: res.error,
+      toolCall: toolCall?.name,
+      data: res.data,
+      ctx
+    }
+}
+
+
+const MACRO_EVAL_FEW_SHOT = `
+### EXAMPLE 1
+User Query:     "Add a USB-C cable with at least 4★ reviews to my Amazon cart"
+Macro Step:     "Navigate to Amazon homepage"
+Current URL:    "https://www.amazon.com/"
+Outcome:        Success
+Assistant:
+<evaluate_macro {"complete": true, "feedback": ""}>
+
+### EXAMPLE 2
+User Query:     "Play the newest MrBeast video on YouTube"
+Macro Step:     "Click the first video result"
+Current URL:    "https://www.youtube.com/results?search_query=MrBeast"
+Outcome:        Click failed - element not found
+Assistant:
+<evaluate_macro {"complete": false, "feedback": "Could not locate the first video result (element not visible)."}>
+`;
+
+/*
+** eval engine for macro
+*/
+export async function evaluateMacro (
+  { macroStep, pageURL, ctx }: EvaluateMacroArgs
+): Promise<{ complete: boolean; feedback: string }> {
+
+  const prompt = `You are a helpful assistant that evaluates the success of a macro step.
+Given a macro step and the current page URL are provided, you evaluate the macro step completeness based on the current page URL and page content;
+
+${MACRO_EVAL_FEW_SHOT}
+
+Macro Step:  "${macroStep}"
+Current URL: "${pageURL}"
+Page Context:
+${JSON.stringify(ctx, null, 2)}
+
+Respond ONLY with an evaluate_macro function call.
+`;
+
+  const resp = await callLLM({
+    modelId : 'gemini-2.5-flash',
+    contents: [{ role:'user', parts:[{ text: prompt }] }],
+    config  : {
+      temperature     : 0.0,
+      maxOutputTokens : 1024,
+      mode            : 'ANY',
+      tools           : [{ functionDeclarations: [EvaluateMacroTool] }]
+    },
+    ignoreFnCallCheck: true
+  });
+
+  const fn = getFnCall(resp);
+  const out = fn?.args ?? { complete:false, feedback:'unknown error' };
+  return { complete: out.complete, feedback: out.feedback };
+}
\ No newline at end of file
diff --git a/src/lib/legacy/task.browser.legacy.ts b/src/lib/legacy/task.browser.legacy.ts
new file mode 100644
index 0000000..c828fef
--- /dev/null
+++ b/src/lib/legacy/task.browser.legacy.ts
@@ -0,0 +1,186 @@
+
+import { handlePuppeteerAction } from '../pptr';
+import { emit } from "../task.execution.helpers"
+import { BrowserActionOptions } from './task.browser.schemas';
+import { 
+    refineMacroPlan, 
+    repairMacroStep,
+    repairMicroStep,
+    executeMicroStep,
+    generateMicroSteps,
+    evaluateMacro
+} from './task.browser.helpers';
+
+export async function runBrowserAction(opts: BrowserActionOptions) {
+    const {
+        taskId,
+        sessionId,
+        steps,
+        originalQuery,
+        pageManager,
+        browserInstance
+    } = opts;
+
+    const page = await pageManager();
+
+    /*
+    ** show overlay
+    */
+    await handlePuppeteerAction({
+        actionDetails: { action: "show_mesh_overlay", parameters: {}, taskId },
+        browserInstance,
+        currentPage: page
+    });
+
+    /*
+    ** global refinement
+    */
+    let hiSteps: string[] = await refineMacroPlan({originalSteps: steps, userQuery: originalQuery});
+    console.log("hiSteps", hiSteps);
+
+    /*
+    ** execute the macro plan
+    */
+   let macroIdx = 0;
+    while (macroIdx < hiSteps.length) {
+
+        let macro = hiSteps[macroIdx]; 
+        let macroRetries = 0
+        let macroDone = false;
+        let lastMacroErr = "";
+        let macroCtx: any = {};
+
+        while (!macroDone && macroRetries < 3) {
+            /**************************************************
+            ** generate micro-steps for current macro
+            */
+            const microSteps = await generateMicroSteps({ sentence: macro, pageURL: page.url() });
+            console.log("Macro", macro, microSteps);
+            
+            let microIdx = 0;
+            let microOk = true;
+            let history: any[] = [];
+
+
+            /**************************************************
+            ** execute micro-steps (with self-repair)
+            */
+            while (microIdx < microSteps.length && microOk) {
+                let micro = microSteps[microIdx];
+                let microRetry = 0;
+                let microDone = false;
+                let lastMicroErr = "";
+                let accumMicroErrors: string[] = [];
+
+                while (!microDone && microRetry < 3) {
+
+                    const execResult = await executeMicroStep({
+                        sentence: micro,
+                        taskId,
+                        page,
+                        browser: browserInstance,
+                        history
+                    });
+
+                    if (execResult.success) {
+                        microDone = true;
+                        microIdx += 1;
+                        macroCtx = execResult.ctx;
+                    } else {
+                        lastMicroErr = execResult.error || "unknown error";
+                        accumMicroErrors.push(lastMicroErr);
+                        microRetry += 1;
+
+                        if (microRetry < 3) {
+                            /* 
+                            ** adapt to an alternative instruction
+                            */
+                            micro = await repairMicroStep({
+                                failing: micro,
+                                lastError: lastMicroErr,
+                                pageURL: page.url()
+                            });
+                        }
+                    }   
+                }
+
+                /*
+                ** still failing after 3 attempts -> macro fails too (and go and repair the macro)
+                */
+                if (!microDone) {
+                    microOk = false;
+                    lastMacroErr = accumMicroErrors.join("\n");
+                }
+            } // micro-steps loop
+            
+            /*
+            ** evaluate or repair the macro
+            */
+            if (microOk) {
+                const { complete, feedback } = await evaluateMacro({
+                    macroStep: macro,
+                    pageURL: page.url(),
+                    ctx: macroCtx
+                });
+                console.log("Evaluate Macro", macro, "complete", complete, "feedback", feedback);
+
+                if (complete) {
+                    macroDone = true;
+                    macroIdx += 1;
+                } else {
+                    /*
+                    ** macro failed, then repair it
+                    */
+                    macroRetries += 1;
+                    if (macroRetries < 3) {
+                    macro = await repairMacroStep({
+                        failingStep: macro,
+                        lastError: feedback,
+                        pageURL: page.url()
+                    });
+                    hiSteps[macroIdx] = macro;
+                    } else {
+                        /*
+                        ** macro failed 3 times -> skip to next to avoid infinite loop
+                        */
+                        macroIdx += 1;
+                    }
+                }
+            } else {
+                /*
+                ** macro failed, then repair it
+                */
+                macroRetries += 1;
+                if (macroRetries < 3) {
+                    macro = await repairMacroStep({
+                        failingStep: macro,
+                        lastError: lastMacroErr,
+                        pageURL: page.url()
+                    });
+                    hiSteps[macroIdx] = macro;
+                } else {
+                    /*
+                    ** macro failed 3 times -> skip to next to avoid infinite loop
+                    */
+                    macroIdx += 1;
+                }
+            }
+        } // macro-steps loop
+    } // macro-steps loop
+
+    /* clean up */
+    await handlePuppeteerAction({
+        actionDetails: { action: "hide_mesh_overlay", parameters: {}, taskId },
+        browserInstance,
+        currentPage: page
+    });
+
+    emit("workflow_update", {
+        taskId,
+        action: "done",
+        status: "completed",
+        speakToUser: "Browser action finished",
+        error: null
+    });
+    
+}
diff --git a/src/lib/legacy/task.browser.schemas.ts b/src/lib/legacy/task.browser.schemas.ts
new file mode 100644
index 0000000..6de1251
--- /dev/null
+++ b/src/lib/legacy/task.browser.schemas.ts
@@ -0,0 +1,14 @@
+export interface BrowserActionOptions {
+    taskId: string;
+    sessionId: string;
+    steps: string[];
+    originalQuery: string;
+    pageManager: any;
+    browserInstance: any;
+}
+
+export interface EvaluateMacroArgs {
+    macroStep : string;
+    pageURL : string;
+    ctx: any;
+}
\ No newline at end of file
diff --git a/src/lib/legacy/task.browser.tools.ts b/src/lib/legacy/task.browser.tools.ts
new file mode 100644
index 0000000..0d4d85b
--- /dev/null
+++ b/src/lib/legacy/task.browser.tools.ts
@@ -0,0 +1,92 @@
+import { Type } from "@google/genai";
+
+export const BrowserStepBreakdownDeclaration = {
+    name: "browser_action_breakdown",
+    description: `
+  Decompose ONE high-level browsing goal into an ordered list of granular
+  page-interaction steps.  
+  Allowed verbs: go_to_url, click, search_google, go_back, go_forward, refresh_page, 
+  input_text, scroll_up, scroll_down, wait_for_selector, wait, get_visible_text, 
+  get_simplified_page_context, done, click_element_by_index, send_keys, scroll_to_text`,
+    parameters: {
+      type: Type.OBJECT,
+      properties: {
+        micro_steps: {
+          type: Type.ARRAY,
+          description: "Concrete UI actions in execution order.",
+          items: { type: Type.STRING }
+        }
+      },
+      required: ["micro_steps"]
+    }
+};
+
+/*
+** refine the whole macro plan
+*/
+export const BrowserPlanRefineDeclaration = {
+  name: "browser_plan_refine",
+  description: `Review and improve a list of high-level browsing steps so
+they are minimal, ordered and unambiguous to achieve the high-level goal.`,
+  parameters: {
+    type: Type.OBJECT,
+    properties: {
+      corrected_steps: {
+        type: Type.ARRAY,
+        items: { type: Type.STRING }
+      }
+    },
+    required: ["corrected_steps"]
+  }
+};
+
+/*
+** repair one failing macro step
+*/
+export const BrowserMacroRepairDeclaration = {
+  name: "browser_macro_repair",
+  description:
+    `Given a failing macro step, the error message and current page URL,
+return ONE replacement macro step that is likely to succeed.`,
+  parameters: {
+    type: Type.OBJECT,
+    properties: {
+      recovery: { type: Type.STRING }
+    },
+    required: ["recovery"]
+  }
+};
+
+/*
+** repair one failing micro-step
+*/
+export const BrowserMicroRepairDeclaration = {
+  name: "browser_micro_repair",
+  description: `Given ONE failing micro instruction, the error message and the
+current page URL, return ONE alternative micro instruction that is likely to
+succeed. Keep it short and actionable.`,
+  parameters: {
+    type: Type.OBJECT,
+    properties: {
+      recovery: { type: Type.STRING }
+    },
+    required: ["recovery"]
+  }
+};
+
+/*
+** eval macro tool
+*/
+export const EvaluateMacroTool = {
+  name : 'evaluate_macro',
+  description: `Judge if the most-recent macro step achieved the user's goal **given the current page state**. 
+  Return complete=true only when the goal is fully satisfied. If not complete, give a short feedback sentence that explains what is still missing or what went wrong.`,
+  parameters : {
+    type       : Type.OBJECT,
+    properties : {
+      complete : { type: Type.BOOLEAN, description: 'Did the macro succeed?' },
+      feedback : { type: Type.STRING,  description: 'Why not / what is missing?' }
+    },
+    required   : ['complete','feedback']
+  }
+};
\ No newline at end of file
diff --git a/src/lib/llm.engine.js b/src/lib/llm.engine.js
index 4a6c6f0..1b8d5fe 100644
--- a/src/lib/llm.engine.js
+++ b/src/lib/llm.engine.js
@@ -65,8 +65,8 @@ export async function callLLM(props) {
             /*
             ** exponential backoff (1s, 2s, 4s, 8s, 16s)
             */
-            const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
-            await new Promise(resolve => setTimeout(resolve, delay));
+            // const delay = Math.min(10 * Math.pow(2, attempt - 1), 10000);
+            // await new Promise(resolve => setTimeout(resolve, delay));
         }
     }
     
diff --git a/src/lib/page.manager.js b/src/lib/page.manager.js
index ffeeed4..06e1361 100644
--- a/src/lib/page.manager.js
+++ b/src/lib/page.manager.js
@@ -8,6 +8,11 @@ export const createPagePool = ({ browser, maxTabs = 10}) => {
   * acquire a page from the pool
   */
   async function acquire() {
+    const existingPages = await browser.pages();
+
+    if (existingPages.length > 0) {
+      return getActivePage(browser);
+    }
     if (active >= maxTabs) {
       await new Promise(function(res) { waiters.push(res); });
     }
@@ -29,11 +34,32 @@ export const createPagePool = ({ browser, maxTabs = 10}) => {
   * return a page from the pool
   */
    return async function pageManager() {
+    const existingPages = await browser.pages();
+    const isReusedPage = existingPages.length > 0;
+    
+
     const page = await acquire();
+    
     /*
     * auto release when the caller is finished
     */
-   page.once('close', () => release(page));
-   return page;
+    if (!isReusedPage) {
+      page.once('close', () => release(page));
+    }
+    return page;
    }
-};
\ No newline at end of file
+};
+
+/*
+** get active page from the pool
+*/
+export async function getActivePage(browser) {
+  const pages = await browser.pages();
+  for (const page of pages) {
+    const isActive = await page.evaluate(() => {
+      document.visibilityState === "visible" && document.hasFocus();
+    });
+    if (isActive) return page;
+  }
+  return pages[0];
+}
\ No newline at end of file
diff --git a/src/lib/plan.orchestrator.ts b/src/lib/plan.orchestrator.ts
index ac29800..4740371 100644
--- a/src/lib/plan.orchestrator.ts
+++ b/src/lib/plan.orchestrator.ts
@@ -99,23 +99,36 @@ Return ONLY a generate_action_plan tool call with the "steps" array.
 /*
 ** translate the atomic web-browsing instruction into the proper tool invocation.
 */
-export async function stepTranslator(step: string, history: string[]): Promise<Record<string, any>> {
-    const prompt = `Translate the atomic web-browsing instruction below into the proper tool invocation.
+export async function stepTranslator(step: string, history: string[], ctx: any[]): Promise<Record<string, any>> {
+    let prompt = `Translate the atomic web-browsing micro-step instruction below into the proper tool invocation.
+
+You will be given a micro step instruction and you need to translate it into the proper tool invocation.
 
     Guidelines:
     - choose the single tool that accomplishes the action, taking prior tool usage in this conversation into account.
     - Output ONLY the call in the exact form of the tool invocation.
     - Do not add any explanatory text.
-    - Always use tool 'done' to return the final answer to the sub-query.
+    - Do not ask any clarification question to the user.`;
+
+    // Include page structure context if available
+    if (ctx && ctx.length > 0) {
+        prompt += `
+
+    Current Page Structure:
+    ${JSON.stringify(ctx, null, 2)}`;
+    }
+
+    prompt += `
 
     Instruction:
     ${step}
     `;
+
     const config = {
         temperature: 0.0,
         maxOutputTokens: 1000,
         mode: 'ANY',
-        tools: [{ functionDeclarations: [ActionDeclarations] }]
+        tools: [{ functionDeclarations: ActionDeclarations }]
     }
 
     const resp = await callLLM({ 
@@ -173,15 +186,16 @@ Respond ONLY with an evaluate_answer function call.
 export async function summarizeText({
     rawText, 
     query, 
-}: {rawText: string, query?: string}): Promise<{summary: string}> {
+    maxRetries = 7
+}: {rawText: string, query?: string, maxRetries?: number}): Promise<{summary: string}> {
     const prompt = `Raw visible text: ${truncate(rawText, 2000000)}\nSub-query: ${query}`;
     
     const summaryCall = await callLLM({
-        modelId: "gemini-2.5-flash",
+        modelId: "gemini-2.5-flash-lite",
         contents: [{ role: "user", parts: [ { text: prompt } ] }],
         config: {
             temperature: 0.0,
-            maxOutputTokens: 10096,
+            maxOutputTokens: 700000,
             mode: "ANY",
             tools: [{ functionDeclarations: [SummaryDeclaration] }]
         },
@@ -189,6 +203,23 @@ export async function summarizeText({
     });
 
     const fn = getFnCall(summaryCall);
+
+    if (!fn?.args?.summary) {
+        // check if summaryCall.candidates[0].content.parts[0].text is not empty
+        if (summaryCall?.candidates?.[0]?.content?.parts?.[0]?.text) {
+            return {
+                summary: summaryCall.candidates[0].content.parts[0].text
+            }
+        }
+        const retry = maxRetries - 1;
+        if (retry > 0) {
+            return await summarizeText({
+                rawText: rawText,
+                query: query,
+                maxRetries: retry
+            });
+        }
+    }
     return {
         summary: fn?.args?.summary ?? rawText,
     }
diff --git a/src/lib/plan.tools.ts b/src/lib/plan.tools.ts
index 8c99fbf..9d629e9 100644
--- a/src/lib/plan.tools.ts
+++ b/src/lib/plan.tools.ts
@@ -91,9 +91,10 @@ export const SummaryDeclaration = {
 export const PickLinksDeclaration = {
   name: "pick_links",
   description: `Given a list of interactive elements on the current page,
-return the indices that should be visited next in order
-to answer the sub-query.  Prefer elements whose href / text / aria-label
-semantically match the sub-query.`,
+return the indices that should be visited next in order to answer the sub-query. 
+Prefer elements whose href / text / aria-label semantically match the sub-query.
+
+The indices should be returned as an array of numbers using indices as the keys not pick_links`,
   parameters: {
     type: Type.OBJECT,
     properties: {
diff --git a/src/lib/pptr.js b/src/lib/pptr.js
index 30ba070..98083cd 100644
--- a/src/lib/pptr.js
+++ b/src/lib/pptr.js
@@ -26,34 +26,34 @@ export const handlePuppeteerAction = async ({actionDetails, browserInstance, cur
             // ===== NAVIGATION =====
             case "search_google": {
                 const searchUrl = `https://www.google.com/search?q=${encodeURIComponent(parameters.query)}`;
-                await pageInstance.goto(searchUrl, { waitUntil: 'networkidle0', timeout: 60000 });
+                await pageInstance.goto(searchUrl, { waitUntil: 'networkidle2', timeout: 60000 });
                 result = { success: true, data: { searchQuery: parameters.query, navigatedTo: searchUrl } };
                 if (logged) console.log(`🔍 Searched for "${parameters.query}" in Google`);
                 break;
             }
 
             case "go_to_url": {
-                await pageInstance.goto(parameters.url, { waitUntil: 'networkidle0' });
+                await pageInstance.goto(parameters.url, { waitUntil: 'networkidle2', timeout: 50000 });
                 result = { success: true, data: { navigatedTo: parameters.url } };
                 if (logged) console.log(`🔗 Navigated to ${parameters.url}`);
                 break;
             }
 
             case "go_back": {
-                await pageInstance.goBack({ waitUntil: 'networkidle0' });
+                await pageInstance.goBack({ waitUntil: 'networkidle2' });
                 result = { success: true, data: { action: 'navigated back' } };
                 if (logged) console.log("🔙 Navigated back");
                 break;
             }
 
             case "go_forward": {
-                await pageInstance.goForward({ waitUntil: 'networkidle0' });
+                await pageInstance.goForward({ waitUntil: 'networkidle2' });
                 result = { success: true, data: { action: 'navigated forward' } };
                 if (logged) console.log("⏭️ Navigated forward");
                 break;
             }
             case "refresh_page": {
-                await pageInstance.reload({ waitUntil: 'networkidle0' });
+                await pageInstance.reload({ waitUntil: 'networkidle2' });
                 result = { success: true, data: { action: 'page refreshed' } };
                 if (logged) console.log("🔄 Page refreshed");
                 break;
@@ -68,8 +68,10 @@ export const handlePuppeteerAction = async ({actionDetails, browserInstance, cur
             }
             // ===== ELEMENT INTERACTION =====
             case "click_element_by_index": {
-                const clickableElements = await domService.getClickableElementsWithIndices({ highlightElements: false });
-                const clickResult = await domService.clickElementByIndex(parameters.index, clickableElements.elementMap);
+                // const clickableElements = await domService.getClickableElementsWithIndices({ highlightElements: false });
+                // const clickResult = await domService.clickElementByIndex(parameters.index, clickableElements.elementMap);
+                const elements = await domService.collectIntertiveElements();
+                const clickResult = await domService.clickElement({ targetIndex: parameters.index, elements });
                 
                 if (clickResult.isFileInput) {
                     result = { success: false, error: clickResult.message, data: null };
@@ -80,38 +82,28 @@ export const handlePuppeteerAction = async ({actionDetails, browserInstance, cur
                 break;
             } 
             case "input_text": {
-                const inputElements = await domService.getClickableElementsWithIndices({ highlightElements: false });
-                if (!inputElements.elementMap[parameters.index]) {
-                    throw new Error(`Element with index ${parameters.index} not found`);
+                try {
+                    const elements = await domService.collectIntertiveElements();
+                    const inputResult = await domService.typeTextByIndex(parameters.index, elements, parameters.text);
+
+                    result = { success: true, data: inputResult };
+                    if (logged) console.log(`⌨️ Input text into element ${parameters.index}`);
+                } catch (error) {
+                    result = { success: false, error: error.message, data: null };
+                    if (logged) console.log(`❌ Error inputting text: ${error.message}`);
                 }
-
-                const inputElement = inputElements.elementMap[parameters.index];
-                const inputResult = await pageInstance.evaluate((xpath, text) => {
-                    const element = document.evaluate(
-                        xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
-                    ).singleNodeValue;
-                    
-                    if (!element) {
-                        throw new Error('Input element not found');
-                    }
-
-                    element.scrollIntoView({ behavior: 'smooth', block: 'center' });
-                    element.focus();
-                    element.value = text;
-                    element.dispatchEvent(new Event('input', { bubbles: true }));
-                    element.dispatchEvent(new Event('change', { bubbles: true }));
-                    
-                    return { success: true, inputText: text };
-                }, inputElement.xpath, parameters.text);
-
-                result = { success: true, data: inputResult };
-                if (logged) console.log(`⌨️ Input text into element ${parameters.index}`);
                 break;
             }
             case "send_keys": {
-                await pageInstance.keyboard.press(parameters.keys);
-                result = { success: true, data: { keys: parameters.keys } };
-                if (logged) console.log(`⌨️ Sent keys: ${parameters.keys}`);
+                try {
+                    const elements = await domService.collectIntertiveElements();
+                    const inputResult = await domService.typeTextByIndex(parameters.index, elements, parameters.keys);
+                    result = { success: true, data: inputResult };
+                    if (logged) console.log(`⌨️ Sent keys: ${parameters.keys}`);
+                } catch (error) {
+                    result = { success: false, error: error.message, data: null };
+                    if (logged) console.log(`❌ Error sending keys: ${error.message}`);
+                }                
                 break;
             }
             // ===== SCROLLING =====
@@ -156,159 +148,6 @@ export const handlePuppeteerAction = async ({actionDetails, browserInstance, cur
                 break;
             }
 
-            // ===== DROPDOWN OPERATIONS =====
-            case "get_dropdown_options": {
-                const dropdownElements = await domService.getClickableElementsWithIndices({ highlightElements: false });
-                const dropdown = dropdownElements.elementMap[parameters.index];
-                
-                if (!dropdown || dropdown.tagName !== 'select') {
-                    throw new Error(`Element at index ${parameters.index} is not a select dropdown`);
-                }
-
-                const options = await pageInstance.evaluate((xpath) => {
-                    const select = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
-                    if (!select) return [];
-                    
-                    return Array.from(select.options).map((opt, index) => ({
-                        index,
-                        text: opt.text,
-                        value: opt.value
-                    }));
-                }, dropdown.xpath);
-
-                result = { success: true, data: { options } };
-                if (logged) console.log(`📋 Found ${options.length} options in dropdown`);
-                break;
-            }
-
-            case "select_dropdown_option": {
-                const selectElements = await domService.getClickableElementsWithIndices({ highlightElements: false });
-                const selectElement = selectElements.elementMap[parameters.index];
-                
-                if (!selectElement || selectElement.tagName !== 'select') {
-                    throw new Error(`Element at index ${parameters.index} is not a select dropdown`);
-                }
-
-                const selectResult = await pageInstance.evaluate((xpath, optionText) => {
-                    const select = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
-                    if (!select) throw new Error('Select element not found');
-                    
-                    const option = Array.from(select.options).find(opt => opt.text === optionText);
-                    if (!option) {
-                        return { success: false, error: `Option "${optionText}" not found` };
-                    }
-                    
-                    select.value = option.value;
-                    select.dispatchEvent(new Event('change', { bubbles: true }));
-                    return { success: true, selectedText: optionText, selectedValue: option.value };
-                }, selectElement.xpath, parameters.text);
-
-                result = selectResult;
-                if (logged) console.log(`📋 Selected option: ${parameters.text}`);
-                break;
-            }
-
-            // ===== DRAG AND DROP =====
-            case "drag_drop": {
-                let sourceX, sourceY, targetX, targetY;
-                
-                if (parameters.source_index !== undefined) {
-                    const elements = await domService.getClickableElementsWithIndices({ highlightElements: false });
-                    const sourceEl = elements.elementMap[parameters.source_index];
-                    if (!sourceEl) throw new Error(`Source element ${parameters.source_index} not found`);
-                    sourceX = sourceEl.rect.x + sourceEl.rect.width / 2;
-                    sourceY = sourceEl.rect.y + sourceEl.rect.height / 2;
-                } else {
-                    sourceX = parameters.source_x;
-                    sourceY = parameters.source_y;
-                }
-
-                if (parameters.target_index !== undefined) {
-                    const elements = await domService.getClickableElementsWithIndices({ highlightElements: false });
-                    const targetEl = elements.elementMap[parameters.target_index];
-                    if (!targetEl) throw new Error(`Target element ${parameters.target_index} not found`);
-                    targetX = targetEl.rect.x + targetEl.rect.width / 2;
-                    targetY = targetEl.rect.y + targetEl.rect.height / 2;
-                } else {
-                    targetX = parameters.target_x;
-                    targetY = parameters.target_y;
-                }
-
-                await pageInstance.mouse.move(sourceX, sourceY);
-                await pageInstance.mouse.down();
-                
-                const steps = parameters.steps || 10;
-                const delay = parameters.delay_ms || 50;
-                
-                for (let i = 0; i <= steps; i++) {
-                    const x = sourceX + (targetX - sourceX) * (i / steps);
-                    const y = sourceY + (targetY - sourceY) * (i / steps);
-                    await pageInstance.mouse.move(x, y);
-                    await new Promise(resolve => setTimeout(resolve, delay));
-                }
-                
-                await pageInstance.mouse.up();
-                
-                result = { success: true, data: { draggedFrom: { x: sourceX, y: sourceY }, draggedTo: { x: targetX, y: targetY } } };
-                if (logged) console.log(`🫳 Drag and drop completed`);
-                break;
-            }
-
-            // ===== TAB MANAGEMENT =====
-            case "switch_tab": {
-                if (!browserInstance) throw new Error("Browser instance not available");
-                const pages = await browserInstance.pages();
-                const targetPage = pages[parameters.page_id];
-                if (!targetPage) throw new Error(`Tab ${parameters.page_id} not found`);
-                
-                await targetPage.bringToFront();
-                result = { success: true, data: { switchedTo: parameters.page_id, url: targetPage.url() } };
-                if (logged) console.log(`🔄 Switched to tab ${parameters.page_id}`);
-                break;
-            }
-
-            case "open_tab": {
-                if (!browserInstance) throw new Error("Browser instance not available");
-                const newPage = await browserInstance.newPage();
-                await newPage.goto(parameters.url, { waitUntil: 'networkidle0' });
-                const pages2 = await browserInstance.pages();
-                const newTabIndex = pages2.length - 1;
-                
-                result = { success: true, data: { newTabIndex, url: parameters.url } };
-                if (logged) console.log(`🔗 Opened new tab with ${parameters.url}`);
-                break;
-            }
-
-            case "get_all_tabs": {
-                if (!browserInstance) throw new Error("Browser instance not available");
-                const allPages = await browserInstance.pages();
-                const tabs = allPages.map((p, i) => ({ id: i, url: p.url(), title: p.title() ? p.title() : "" }));
-                result = { success: true, data: { tabs } };
-                if (logged) console.log(`🔗 Retrieved all tabs`);
-                break;
-            }
-
-            case "get_current_tab": {
-                if (!browserInstance) throw new Error("Browser instance not available");
-                const for_pages = await browserInstance.pages();
-                const currentTab = for_pages.findIndex(p => p === pageInstance);
-                result = { success: true, data: { currentTab } };
-                if (logged) console.log(`🔗 Retrieved current tab`);
-                break;
-            }
-
-            case "close_tab": {
-                if (!browserInstance) throw new Error("Browser instance not available");
-                const pagesBeforeClose = await browserInstance.pages();
-                const pageToClose = pagesBeforeClose[parameters.page_id];
-                if (!pageToClose) throw new Error(`Tab ${parameters.page_id} not found`);
-                
-                await pageToClose.close();
-                result = { success: true, data: { closedTab: parameters.page_id } };
-                if (logged) console.log(`❌ Closed tab ${parameters.page_id}`);
-                break;
-            }
-
             case "get_visible_text": {
                 const visibleText = await domService.getVisibleText();
                 result = { success: true, data: { visibleText } };
@@ -318,14 +157,32 @@ export const handlePuppeteerAction = async ({actionDetails, browserInstance, cur
 
             // ===== PAGE STATE =====
             case "get_simplified_page_context": {
-                let screenshot = null;
+
+                const elements = await domService.getInteractiveElements();
+                console.log("elements", elements);
+
+                const rawContext = {
+                    interactiveElements: elements.map(e => ({
+                        index : e.index,
+                        tag : e.tag,
+                        accessibleName : e.accessibleName,
+                        href : e.href
+                    })),
+                    totalElements : elements.length,
+                };
+
+                result = { success: true, data: rawContext };
+                break;
+            }
+
+            case "get_simplified_page_context_legacy": {
                 let browserState;
 
                 try {
                     // apply highlights
                     browserState = await domService.getClickableElementsWithIndices({
                         highlightElements: false,
-                        maxElements: parameters.max_elements || 50,
+                        maxElements: parameters.max_elements || 100,
                         focusElement: parameters.focus_element_for_screenshot
                     });
                     // highlights are now on the page with z-index: 10000
@@ -340,7 +197,6 @@ export const handlePuppeteerAction = async ({actionDetails, browserInstance, cur
                     }
                 } finally {
                     // remove highlights after the delay
-                    await domService.removeHighlights();
                 }
 
                 // if browserState was not populated due to an error before its assignment
@@ -348,7 +204,7 @@ export const handlePuppeteerAction = async ({actionDetails, browserInstance, cur
                     // fallback: get elements without highlighting if the primary path failed
                     browserState = await domService.getClickableElementsWithIndices({
                         highlightElements: false,
-                        maxElements: parameters.max_elements || 50
+                        maxElements: parameters.max_elements || 100
                     });
                 }
 
@@ -375,38 +231,6 @@ export const handlePuppeteerAction = async ({actionDetails, browserInstance, cur
                 break;
             }
 
-            case "save_pdf": {
-                const filename = parameters.filename || `page-${Date.now()}.pdf`;
-                await pageInstance.pdf({
-                    path: filename,
-                    format: parameters.format || 'A4',
-                    printBackground: false
-                });
-                result = { success: true, data: { savedAs: filename } };
-                if (logged) console.log(`💾 PDF saved as ${filename}`);
-                break;
-            }
-
-            // ===== FILE OPERATIONS =====
-            case "upload_file": {
-                const fileElements = await domService.getClickableElementsWithIndices({ highlightElements: false });
-                const fileElement = fileElements.elementMap[parameters.index];
-                
-                if (!fileElement || fileElement.attributes.type !== 'file') {
-                    throw new Error(`Element at index ${parameters.index} is not a file input`);
-                }
-
-                const fileInput = await pageInstance.$(fileElement.xpath.replace('//*[@id="', '#').replace('"]', ''));
-                if (!fileInput) {
-                    throw new Error('File input element not found');
-                }
-
-                await fileInput.uploadFile(parameters.file_path);
-                result = { success: true, data: { uploadedFile: parameters.file_path } };
-                if (logged) console.log(`📁 Uploaded file: ${parameters.file_path}`);
-                break;
-            }
-
             // ===== RUNTIME CONTROL =====
             case "ask_user": {
                 result = { 
@@ -420,17 +244,6 @@ export const handlePuppeteerAction = async ({actionDetails, browserInstance, cur
                 break;
             }
 
-            case "execute_javascript": {
-                const jsResult = await pageInstance.evaluate(parameters.script);
-                result = { success: true, data: { result: jsResult } };
-                if (logged) console.log(`⚙️ Executed JavaScript`);
-                break;
-            }
-            // ===== FALLBACK =====
-            default:
-                result = { success: false, error: `Unsupported action: ${action}`, data: null };
-                break;
-
             // ===== OVERLAY =====
             case 'show_mesh_overlay': {
                 await domService.showMeshOverlay(pageInstance);
@@ -442,6 +255,11 @@ export const handlePuppeteerAction = async ({actionDetails, browserInstance, cur
                 result = { success:true, data:null };
                 break;
             }
+
+            // ===== FALLBACK =====
+            default:
+                result = { success: false, error: `Unsupported action: ${action}`, data: null };
+                break;
         }
 
     } catch (error) {
@@ -450,7 +268,6 @@ export const handlePuppeteerAction = async ({actionDetails, browserInstance, cur
         
         // Attempt cleanup if error occurs
         try {
-            await domService.removeHighlights();
             if (logged) console.log(`🧹 Emergency cleanup completed after error`);
         } catch (cleanupError) {
             if (logged) console.log(`⚠️ Cleanup error: ${cleanupError.message}`);
diff --git a/src/lib/query.helpers.ts b/src/lib/query.helpers.ts
index 2c9b972..845f4ba 100644
--- a/src/lib/query.helpers.ts
+++ b/src/lib/query.helpers.ts
@@ -1,3 +1,4 @@
+
 /*
 ** build a digest of the recent conversation
 */
@@ -23,11 +24,37 @@ export function buildHistoryDigest(msgs: any[]): string {
 /*
 ** add and update plan to task
 */
-export function addPlanToTask({tasks, taskId, newPlan}) {
-    return tasks.map(t => 
-        t.taskId === taskId ? { ...t, plans: [...t.plans, newPlan] } : t
-    )
+export function addPlanToTask({
+    tasks, 
+    taskId, 
+    newPlan, 
+    action, 
+    url
+}: {
+    tasks: any[],
+    taskId: string,
+    newPlan: any,
+    action: string,
+    url: string
+}) {
+
+    return tasks.map(t => {
+        if (t.taskId !== taskId) return t;
+
+        const next: any = {
+            ...t,
+            plans: [...t.plans, newPlan]
+        }
+
+        if (action === "go_to_url") {
+            next.tabs = [...t.tabs, url]
+        }
+        return next;
+    })
 }
+
+
+
 export function updatePlanInTask({tasks, taskId, actionId, status, error}) {
     return tasks.map(t => 
         t.taskId === taskId 
diff --git a/src/lib/query.llm.ts b/src/lib/query.llm.ts
index f8611dc..a9f3695 100644
--- a/src/lib/query.llm.ts
+++ b/src/lib/query.llm.ts
@@ -1,24 +1,83 @@
 import { callLLM } from "./llm.engine";
-import { QueryAnalysisDeclaration } from "./tools";
+import { 
+    QueryAnalysisDeclaration, 
+    SmallTalkDeclaration,
+    BrowserActionDeclaration
+} from "./query.tools";
 import { getFnCall } from "./task.execution.helpers";
 import { QUERY_FEW_SHOT } from "./query.fewshot";
 import { SplitQueryResponse } from "./query.schemas";
 
 
-export async function splitQuery({query, history}: {query: string, history: any[]}): Promise<SplitQueryResponse> {
+export async function splitQuery({query, history, runtimeMode}: {query: string, history: any[], runtimeMode: string}): Promise<SplitQueryResponse> {
     const analysisDate = new Date().toISOString().split('T')[0];
-    const prompt = `
+    const prompt_general = `
 ${history ? `### CONVERSATION HISTORY\n${history}` : ''}
-User-Query: "${query}"
+User: "${query}"
 
-Goal: Always use the tool 'analyze_query_strategy' to break the query into
-SEQUENTIAL sub-queries. Independent information-gathering steps must be placed
-*earlier* in the list so later steps can reference their answers.
+### TOOL SELECTION GUIDE
+Choose the appropriate tool based on the user's query:
 
-### ADDITIONAL GUIDANCE
+**Use 'small_talk_response' when:**
+- User is making polite conversation, asking for jokes, or requesting simple factual information
+- The query can be answered directly without information gathering or web research or browser action
+- Examples: "Hello", "What is the capital of France?", "Tell me a joke", "How are you?"
+
+**Use 'browser_action' when:**
+- User wants to perform actions on a web page or website
+- Request involves interacting with web elements (clicking, typing, searching, booking, etc.)
+- Examples: "Book a flight to Paris", "Search for hotels in Tokyo", "Find jobs on LinkedIn", "Fill out this form", "Ask a question about a page"
+
+**Use 'analyze_query_strategy' when:** 
+- To break the query into SEQUENTIAL sub-queries. 
+- Independent information-gathering steps must be placed *earlier* in the list so later steps can reference their answers.
+- User needs information that requires research or multi-step analysis
+- Query requires breaking down into sequential sub-tasks
+- Request involves gathering information from multiple sources
+- Examples: "Compare prices between different airlines", "Research the history of AI development", "Find the best restaurants in multiple cities"
+
+### ADDITIONAL GUIDANCE FOR 'analyze_query_strategy'
+- Produce the minimal number of sub-queries necessary to answer the user's query.
+- Use concise keywords whenever possible; avoid long, verbose sub-query sentences or questions.
+- Take the conversation history into account: do not repeat questions for information already provided, 
+  and resolve pronouns or ellipses using the context. If the user requests information about multiple 
+  elements or entities, split the query into separate sub-queries for each entity, rather than searching for all at once.
+- Specify "dependencies" so that each subsequent sub-query lists the indices of the queries it depends on.
+  If a step is logically independent but you still want to enforce strict execution order, include the index of the previous step in "depends_on".
+- For independent steps, list the index of the previous step in "depends_on" to maintain order.
+
+${QUERY_FEW_SHOT}
+
+Date: ${analysisDate}
+(Please use this date for accurate search results)
+`;
+
+const prompt_research = `
+${history ? `### CONVERSATION HISTORY\n${history}` : ''}
+User: "${query}"
+
+### TOOL SELECTION GUIDE
+Choose the appropriate tool based on the user's query:
+
+**Use 'small_talk_response' when:**
+- User is making polite conversation, asking for jokes, or requesting simple factual information
+- The query can be answered directly without information gathering or web research or browser action
+- Examples: "Hello", "What is the capital of France?", "Tell me a joke", "How are you?"
+
+**Use 'analyze_query_strategy' when:** 
+- To break the query into SEQUENTIAL sub-queries. 
+- Independent information-gathering steps must be placed *earlier* in the list so later steps can reference their answers.
+- User needs information that requires research or multi-step analysis
+- Query requires breaking down into sequential sub-tasks
+- Request involves gathering information from multiple sources
+- Examples: "Compare prices between different airlines", "Research the history of AI development", "Find the best restaurants in multiple cities"
+
+### ADDITIONAL GUIDANCE FOR 'analyze_query_strategy'
 - Produce the minimal number of sub-queries necessary to answer the user's query.
 - Use concise keywords whenever possible; avoid long, verbose sub-query sentences or questions.
-- Take the conversation history into account: do not repeat questions for information already provided, and resolve pronouns or ellipses using the context. If the user requests information about multiple elements or entities, split the query into separate sub-queries for each entity, rather than searching for all at once.
+- Take the conversation history into account: do not repeat questions for information already provided, 
+  and resolve pronouns or ellipses using the context. If the user requests information about multiple 
+  elements or entities, split the query into separate sub-queries for each entity, rather than searching for all at once.
 - Specify "dependencies" so that each subsequent sub-query lists the indices of the queries it depends on.
   If a step is logically independent but you still want to enforce strict execution order, include the index of the previous step in "depends_on".
 - For independent steps, list the index of the previous step in "depends_on" to maintain order.
@@ -29,6 +88,56 @@ Date: ${analysisDate}
 (Please use this date for accurate search results)
 `;
 
+    const prompt_browser_action = `
+${history ? `### CONVERSATION HISTORY\n${history}` : ''}
+User: "${query}"
+
+### TOOL SELECTION GUIDE
+Choose the appropriate tool based on the user's query:
+
+**Use 'small_talk_response' when:**
+- User is making polite conversation, asking for jokes, or requesting simple factual information
+- The query can be answered directly without information gathering or web research or browser action
+- Examples: "Hello", "What is the capital of France?", "Tell me a joke", "How are you?"
+
+**Use 'browser_action' when:**
+- User wants to perform actions on a web page or website
+- Request involves interacting with web elements (clicking, typing, searching, booking, etc.)
+- Examples: "Book a flight to Paris", "Search for hotels in Tokyo", "Find jobs on LinkedIn", "Fill out this form", "Ask a question about a page"
+  or searching from information in personal page of the user like slack, linkedin, notion, gmail, etc.
+
+Date: ${analysisDate}
+
+`;
+
+    const MODE = runtimeMode; // research or general or action
+    const SYSTEM_CONFIG = {
+        action: {
+            prompt: prompt_browser_action,
+            tools: [
+                SmallTalkDeclaration, 
+                BrowserActionDeclaration
+            ]
+        },
+        research: {
+            prompt: prompt_research,
+            tools: [
+                QueryAnalysisDeclaration, 
+                SmallTalkDeclaration
+            ]
+        },
+        general: {
+            prompt: prompt_general,
+            tools: [
+                QueryAnalysisDeclaration, 
+                SmallTalkDeclaration, 
+                BrowserActionDeclaration
+            ]
+        }
+    }
+    
+    // tool declaration based on the mode choses
+    const TOOLS = SYSTEM_CONFIG[MODE].tools;
     /*
     ** config setup
     */
@@ -36,14 +145,18 @@ Date: ${analysisDate}
         temperature: 0.0,
         maxOutputTokens: 10096,
         mode: 'ANY',
-        tools: [{ functionDeclarations: QueryAnalysisDeclaration }]
+        tools: [{ 
+            functionDeclarations: [
+                ...TOOLS
+            ] 
+        }]
     }
     /*
     ** call LLM
     */
     const response = await callLLM({
         modelId: 'gemini-2.5-flash',
-        contents: [{ role: 'user', parts: [{ text: prompt }] }],
+        contents: [{ role: 'user', parts: [{ text: SYSTEM_CONFIG[MODE].prompt }] }],
         config,
         ignoreFnCallCheck: true
     });
@@ -52,9 +165,29 @@ Date: ${analysisDate}
     ** get function call
     */
     const call = getFnCall(response);
-    if (!call) return { queries: [query], dependencies: [], researchFlags: [] };
+    if (!call) return { 
+        queries: [query], 
+        kind: "analysis",
+        dependencies: [], 
+        researchFlags: [] 
+    };
+
+    if (call?.name === "small_talk_response") {
+        return {
+            kind: "small_talk",
+            reply: call?.args?.reply ?? ""
+        }
+    }
+
+    if (call?.name === "browser_action") {
+        return {
+            kind: "browser_action",
+            steps: call?.args?.steps ?? []
+        }
+    }
 
     const resp = {
+        kind: "analysis",
         queries: call?.args?.queries ?? [query],
         dependencies: call?.args?.dependencies ?? [],
         researchFlags: call?.args?.researchFlags ?? []
diff --git a/src/lib/query.schemas.ts b/src/lib/query.schemas.ts
index bbdc807..6a81ea0 100644
--- a/src/lib/query.schemas.ts
+++ b/src/lib/query.schemas.ts
@@ -3,7 +3,10 @@
 ** split query
 */
 export interface SplitQueryResponse {
-  queries: string[];
-  dependencies: { query_index: number, depends_on: number[] }[];
-  researchFlags: boolean[];
-}
\ No newline at end of file
+  kind: string;
+  queries?: string[];
+  dependencies?: { query_index: number, depends_on: number[] }[];
+  researchFlags?: boolean[];
+  reply?: string;
+  steps?: string[];
+}
diff --git a/src/lib/query.tools.ts b/src/lib/query.tools.ts
new file mode 100644
index 0000000..cab5e15
--- /dev/null
+++ b/src/lib/query.tools.ts
@@ -0,0 +1,105 @@
+import { Type } from '@google/genai';
+
+/*
+** query analysis
+*/
+export const QueryAnalysisDeclaration = {
+    name: 'analyze_query_strategy',
+    description: 
+        `Break the user query into an ordered SEQUENTIAL chain of sub-queries.
+        Return each sub-query and an explicit dependencies list so the caller
+        knows which earlier answers are required for each later step.`,
+    parameters: {
+      type: Type.OBJECT,
+      properties: {
+        queries: {
+          type: Type.ARRAY,
+          items: {
+            type: Type.STRING,
+            description: 'One concrete, self-contained sub-query.'
+          }
+        },
+        researchFlags: {
+          type: Type.ARRAY,
+          description: `List the indices of sub-queries (e.g., [0, 1, 3]) that require open-web research. If an index is not listed, it does not require research.
+          In general, if the user requests information, open-web research should be performed.`,
+          items: {
+            type: Type.NUMBER,
+            description: 'Index of a sub-query that requires open-web research'
+          }
+        },
+        dependencies: {
+          type: Type.ARRAY,
+          description: 'For every sub-query that relies on previous answers, specify its index and the indexes it depends on.',
+          items: {
+            type: Type.OBJECT,
+            properties: {
+              query_index: {
+                type: Type.NUMBER,
+                description: 'Index of the dependent sub-query'
+              },
+              depends_on: {
+                type: Type.ARRAY,
+                items: {
+                  type: Type.NUMBER,
+                  description: 'Indices of prerequisite sub-queries'
+                }
+              }
+            },
+            required: ['query_index', 'depends_on']
+          }
+        }
+      },
+      required: ['queries', 'dependencies', 'researchFlags']
+    }
+};
+
+/*
+** small talk response
+*/
+export const SmallTalkDeclaration = {
+  name: "small_talk_response",
+  description:
+    `Use when the user is clearly making polite conversation or the request
+     requires no information gathering. Return a direct natural-language reply.
+     If the user is asking for a joke, return a joke. Or if the user is asking for factual information, 
+     return a fact. For example, if the user asks "What is the capital of France?", return "The capital of France is Paris."`,
+  parameters: {
+    type: Type.OBJECT,
+    properties: {
+      reply: {
+        type: Type.STRING,
+        description: "A concise, friendly reply from the assistant."
+      }
+    },
+    required: ["reply"]
+  }
+};
+  
+/*
+** browser action declaration
+*/
+export const BrowserActionDeclaration = {
+  name: "browser_action",
+  description: `
+Use this tool to generate a step-by-step deterministic *plan* for accomplishing 
+an on-page action (e.g. "booking flight", "searching hotel", "searching for a job", "asking question about a page", in general, anything that can be done on a web page).  
+Each step describes **what** to do on the web page; the executor layer (Puppeteer) will translate it into clicks, typing, scrolling, etc.
+The plan should be a list of steps, each step is a string that describes what to do on the web page.
+`,
+  parameters: {
+    type: Type.OBJECT,
+    properties: {
+      steps: {
+        type: Type.ARRAY,
+        description:
+          "Ordered list of atomic UI actions that the agent must perform.",
+        items: {
+          type: Type.STRING
+        }
+      }
+    },
+    required: ["steps"]
+  }
+
+}
\ No newline at end of file
diff --git a/src/lib/skills-backup.json b/src/lib/skills-backup.json
new file mode 100644
index 0000000..4952cf5
--- /dev/null
+++ b/src/lib/skills-backup.json
@@ -0,0 +1,157 @@
+{
+  "websites": [
+    {
+      "domain": "amazon.com",
+      "skills": [
+        {
+          "name": "search_products",
+          "description": "Search Amazon for a product name and return the list items (title, price, link).",
+          "input":  { "text": "string" },
+          "output": "results",
+          "steps": [
+            { "action": "navigate_to_url", "url": "https://www.amazon.com" },
+            { "action": "wait_for_selector", "selector": "#twotabsearchtextbox" },
+            { "action": "click",              "selector": "#twotabsearchtextbox" },
+            { "action": "type",               "selector": "#twotabsearchtextbox", "input_key": "text" },
+            { "action": "press_enter" },
+            { "action": "wait_for_selector",  "selector": "div[data-component-type='s-search-result'][data-asin]:not([data-asin=''])" },
+            { "action": "scroll_down" },
+            {
+              "action":     "extract_list",
+              "selector":   "div[data-component-type='s-search-result'][data-asin]:not([data-asin=''])",
+              "schema": {
+                "asin": "@data-asin",
+                "title": "[data-cy='title-recipe'] a h2::text",
+                "price": "[data-cy='price-recipe'] .a-row [aria-describedby='price-link'] .a-price .a-offscreen::text",
+                "link": "[data-cy='title-recipe'] a::href"
+              },
+              "output_key": "results"
+            }
+          ]
+        },
+        {
+          "name": "open_result_by_index",
+          "description": "Navigate to the N-th product page that was returned by search_products.",
+          "input":  { "index": "number" },
+          "output": "product_url",
+          "steps": [
+            { "action": "navigate_to_url",      "url": "{{url_override}}" },
+            { "action": "wait_for_selector",      "selector": "#productTitle" },
+            { "action": "extract_fields",         "selector": "html", "output_key": "product_url" }
+          ]
+        },
+        {
+          "name": "extract_product_details",
+          "description": "On a product page, capture title, price & rating (no cart action).",
+          "output": "product_details",
+          "steps": [
+            { "action": "wait_for_selector", "selector": "#productTitle" },
+            {
+              "action":     "extract_fields",
+              "selector":   "#productTitle, #priceblock_ourprice, #price_inside_buybox, #acrPopover",
+              "output_key": "product_url"
+            }
+          ]
+        },
+        {
+          "name": "add_current_product_to_cart",
+          "description": "Click “Add to Cart” on the current product page and return cart count.",
+          "output": "cart_status",
+          "steps": [
+            { "action": "wait_for_selector", "selector": "#add-to-cart-button" },
+            { "action": "click",             "selector": "#add-to-cart-button" },
+            { "action": "wait_for_selector", "selector": "#nav-cart-count" },
+            {
+              "action":     "extract_fields",
+              "selector":   "#nav-cart-count",
+              "output_key": "cart_status"
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "domain": "gmail.com",
+      "skills": [
+        {
+          "name": "search_emails",
+          "description": "Use the Gmail search bar to find messages and return the visible result rows (subject, sender, snippet, link).",
+          "input":  { "text": "string" },
+          "output": "email_results",
+          "steps": [
+            { "action": "navigate_to_url", "url": "https://mail.google.com" },
+            { "action": "wait_for_selector", "selector": "input[name='q']" },
+            { "action": "click",             "selector": "input[name='q']" },
+            { "action": "type",              "selector": "input[name='q']", "input_key": "text" },
+            { "action": "press_enter" },
+            { "action": "wait_for_selector", "selector": "tr.zA" },
+            {
+              "action":     "extract_list",
+              "selector":   "tr.zA",
+              "output_key": "email_results"
+            }
+          ]
+        },
+        {
+          "name": "open_email_by_index",
+          "description": "Open the N-th email in the current search / inbox view.",
+          "input":  { "index": "number" },
+          "output": "thread_url",
+          "steps": [
+            { "action": "wait_for_selector", "selector": "tr.zA" },
+            {
+              "action":   "click_element_by_index",
+              "selector": "tr.zA",
+              "index":     0
+            },
+            { "action": "wait_for_selector", "selector": "h2.hP" },
+            {
+              "action":     "extract_fields",
+              "selector":   "html",
+              "output_key": "thread_url"
+            }
+          ]
+        },
+        {
+          "name": "extract_email_details",
+          "description": "Inside an open thread, capture subject, sender, date, and body snippet.",
+          "output": "email_details",
+          "steps": [
+            { "action": "wait_for_selector", "selector": "h2.hP" },
+            {
+              "action":     "extract_fields",
+              "selector":   "h2.hP, span.gD, span.g3, div.a3s",
+              "output_key": "email_details"
+            }
+          ]
+        },
+        {
+          "name": "compose_and_send_email",
+          "description": "Compose a new email and send it.",
+          "input": {
+            "to":      "string",
+            "subject": "string",
+            "body":    "string"
+          },
+          "output": "send_status",
+          "steps": [
+            { "action": "wait_for_selector", "selector": ".T-I.T-I-KE.L3" },
+            { "action": "click",             "selector": ".T-I.T-I-KE.L3" },
+            { "action": "wait_for_selector", "selector": "textarea[name='to']" },
+            { "action": "type",              "selector": "textarea[name='to']",          "input_key": "to" },
+            { "action": "type",              "selector": "input[name='subjectbox']",     "input_key": "subject" },
+            { "action": "type",              "selector": "div[aria-label='Message Body']", "input_key": "body" },
+            { "action": "click",             "selector": "div[aria-label^='Send']" },
+            { "action": "wait_for_selector", "selector": "span.bAq" },
+            {
+              "action":     "extract_fields",
+              "selector":   "span.bAq",
+              "output_key": "send_status"
+            }
+          ]
+        }
+      ]
+    }
+    
+  ]
+}
\ No newline at end of file
diff --git a/src/lib/skills.json b/src/lib/skills.json
new file mode 100644
index 0000000..9dfbf4e
--- /dev/null
+++ b/src/lib/skills.json
@@ -0,0 +1,241 @@
+{
+  "websites": [
+    {
+      "domain": ["amazon.com", "amazon.co.uk", "amazon.de"],
+      "skills": [
+        {
+          "name": "search_products",
+          "description": "Search for a product on Amazon. this skill will take the user query as input, and perform the search on amazon.com and it will return the list of results of products.",
+          "input":  { "text": "string" },
+          "output": "results",
+          "steps": [
+            { "action": "navigate_to_url", "url": "https://www.amazon.com" },
+            { "action": "wait_for_selector", "selector": "#twotabsearchtextbox" },
+            { "action": "click",              "selector": "#twotabsearchtextbox" },
+            { "action": "type",               "selector": "#twotabsearchtextbox", "input_key": "text" },
+            { "action": "press_enter" },
+            { "action": "wait_for_selector",  "selector": "div[data-component-type='s-search-result'][data-asin]:not([data-asin=''])" },
+            { "action": "scroll_down",        "times": 3 },
+            {
+              "action":     "extract_list",
+              "selector":   "div[data-component-type='s-search-result'][data-asin]:not([data-asin=''])",
+              "schema": {
+                "asin": "@data-asin",
+                "title": "[data-cy='title-recipe'] a h2::text",
+                "price": "[data-cy='price-recipe'] .a-row [aria-describedby='price-link'] .a-price .a-offscreen::text",
+                "link": "[data-cy='title-recipe'] a::href"
+              },
+              "output_key": "results"
+            }
+          ]
+        },
+        {
+          "name": "open_result_by_index",
+          "description": "Navigate to the N-th product page that was returned by search_products.",
+          "input":  { "index": "number" },
+          "output": "product_url",
+          "steps": [
+            { "action": "navigate_to_url",      "url": "{{url_override}}" },
+            { "action": "wait_for_selector",    "selector": "#productTitle" },
+            { "action": "scroll_down",          "times": 1 }
+          ]
+        },
+        {
+          "name": "extract_product_details",
+          "description": "Extract the details of the product from the current page. This skill will return the details of the product as output, for example: title, price, rating.",
+          "output": "product_details",
+          "steps": [
+            { "action": "wait_for_selector", "selector": "#productTitle" },
+            {
+              "action":     "extract_fields",
+              "selector":   "#productTitle, #corePriceDisplay_desktop_feature_div span#aok-offscreen, #averageCustomerReviews span#acrPopover",
+              "output_key": "product_details"
+            }
+          ]
+        },
+        {
+          "name": "add_current_product_to_cart",
+          "description": "Click \"Add to cart\" button. This skill will return the count of the products in the cart as output.",
+          "output": "cart_status",
+          "steps": [
+            { "action": "wait_for_selector", "selector": "#addToCart_feature_div #add-to-cart-button" },
+            { "action": "click",             "selector": "#addToCart_feature_div #add-to-cart-button" },
+            { "action": "wait_for_selector", "selector": "#nav-cart-count" },
+            {
+              "action":     "extract_fields",
+              "selector":   "#nav-cart-count",
+              "output_key": "cart_status"
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "domain": ["gmail.com", "mail.google.com"],
+      "skills": [
+        {
+          "name": "search_emails",
+          "description": "Use Gmail search bar to find messages and return the visible result rows (subject, sender, snippet, link). This skill will take the user query as input, and perform the search on gmail.com and it will return the list of results of emails.",
+          "input":  { "text": "string" },
+          "output": "email_results",
+          "steps": [
+            { "action": "navigate_to_url", "url": "https://mail.google.com" },
+            { "action": "wait_for_selector", "selector": "input[name='q']" },
+            { "action": "click",             "selector": "input[name='q']" },
+            { "action": "type",              "selector": "input[name='q']", "input_key": "text" },
+            { "action": "press_enter" },
+            { "action": "wait_for_selector", "selector": "tr.zA" },
+            {
+              "action":     "extract_list",
+              "selector":   "tr.zA",
+              "output_key": "email_results"
+            }
+          ]
+        },
+        {
+          "name": "open_email_by_index",
+          "description": "Open the N-th email in the current search / inbox view. This skill will take the index of the result as input, and open the result page.",
+          "input":  { "index": "number" },
+          "output": "thread_url",
+          "steps": [
+            { "action": "wait_for_selector", "selector": "tr.zA" },
+            {
+              "action":   "click_element_by_index",
+              "selector": "tr.zA",
+              "index":     0
+            },
+            { "action": "wait_for_selector", "selector": "h2.hP" },
+            {
+              "action":     "extract_fields",
+              "selector":   "html",
+              "output_key": "thread_url"
+            }
+          ]
+        },
+        {
+          "name": "extract_email_details",
+          "description": "Inside an open thread, capture subject, sender, date, and body snippet of the email. This skill will return the details of the email as output.",
+          "output": "email_details",
+          "steps": [
+            { "action": "wait_for_selector", "selector": "h2.hP" },
+            {
+              "action":     "extract_fields",
+              "selector":   "h2.hP, span.gD, span.g3, div.a3s",
+              "output_key": "email_details"
+            }
+          ]
+        },
+        {
+          "name": "compose_and_send_email",
+          "description": "Compose and send an email. This skill will take the email details as input, and send the email.",
+          "input": {
+            "to":      "string",
+            "subject": "string",
+            "body":    "string"
+          },
+          "output": "send_status",
+          "steps": [
+            { "action": "wait_for_selector", "selector": ".T-I.T-I-KE.L3" },
+            { "action": "click",             "selector": ".T-I.T-I-KE.L3" },
+            { "action": "wait_for_selector", "selector": "textarea[name='to']" },
+            { "action": "type",              "selector": "textarea[name='to']",          "input_key": "to" },
+            { "action": "type",              "selector": "input[name='subjectbox']",     "input_key": "subject" },
+            { "action": "type",              "selector": "div[aria-label='Message Body']", "input_key": "body" },
+            { "action": "click",             "selector": "div[aria-label^='Send']" },
+            { "action": "wait_for_selector", "selector": "span.bAq" },
+            {
+              "action":     "extract_fields",
+              "selector":   "span.bAq",
+              "output_key": "send_status"
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "domain": ["notion.so", "notion.com"],
+      "skills": [
+        {
+          "name": "search_pages",
+          "description": "Search your Notion workspace for pages whose titles match a query and return the visible results (title, link).",
+          "input":  { "text": "string" },
+          "output": "page_results",
+          "steps": [
+            { "action": "navigate_to_url", "url": "https://www.notion.so" },
+            { "action": "wait_for_selector", "selector": "div[role='button'] svg.magnifyingGlass" },
+            { "action": "click",             "selector": "div[role='button'] svg.magnifyingGlass" },
+            { "action": "wait_for_selector", "selector": "input[role='combobox'][placeholder^='Search']" },
+            { "action": "type",              "selector": "input[role='combobox'][placeholder^='Search']", "input_key": "text" },
+            { "action": "wait_for_selector", "selector": "div[role='listbox'] div[role='option']" },
+            {
+              "action":     "extract_list",
+              "selector":   "div[role='listbox'] div[role='option']",
+              "schema": {
+                "title": ".notranslate::text",
+                "link":  "a::href"
+              },
+              "output_key": "page_results"
+            }
+          ]
+        },
+    
+        {
+          "name": "open_page_by_index",
+          "description": "Open the N-th page from the current search results. This skill will take the index of the result as input, and open the result page.",
+          "input":  { "index": "number" },
+          "steps": [
+            { "action": "wait_for_selector", "selector": "div[role='option'].search-query-result-item" },
+            {
+              "action":   "click_element_by_index",
+              "selector": "div[role='option'].search-query-result-item",
+              "index":     0
+            },
+            { "action": "wait_for_selector", "selector": "h1.notranslate[contenteditable='true']" }
+          ]
+        },
+    
+        {
+          "name": "extract_page_content",
+          "description": "Capture the page title and the visible rich-text blocks from the open Notion page.",
+          "output": "page_content",
+          "steps": [
+            { "action": "wait_for_selector", "selector": "h1.notranslate[contenteditable='true']" },
+            { "action": "wait_for_selector", "selector": "div.notion-page-content" },
+            {
+              "action": "extract_fields",
+              "selector": "h1.notranslate[contenteditable='true']::text, div.notion-page-content div[contenteditable='true']::text",
+              "output_key": "page_content",
+              "schema": {
+                "title": "h1.notranslate[contenteditable='true']::text",
+                "body":  "div.notion-page-content div[contenteditable='true']::text"
+              }
+            }
+          ]
+        },
+    
+        {
+          "name": "create_page",
+          "description": "Create a new top-level Notion page. Supply one `text` string formatted as `title<<<rt-space>>>body`; the runner splits it into title and body.",
+          "input": {
+            "text": "string"
+          },
+          "output": "page_url",
+          "steps": [
+            { "action": "navigate_to_url", "url": "https://www.notion.so" },
+            { "action": "wait_for_selector", "selector": "[data-testid='new-page-button']" },
+            { "action": "click",             "selector": "[data-testid='new-page-button']" },
+            { "action": "wait_for_selector", "selector": "h1.notranslate[contenteditable='true']" },
+            { "action": "type",              "selector": "h1.notranslate[contenteditable='true']", "input_key": "text", "split": "before" },
+            { "action": "wait_for_selector", "selector": "div.notion-page-content div.notranslate[contenteditable='true']" },
+            { "action": "type",              "selector": "div.notion-page-content div.notranslate[contenteditable='true']", "input_key": "text", "split": "after" },
+            {
+              "action":     "extract_fields",
+              "selector":   "link[rel='canonical']::href",
+              "output_key": "page_url"
+            }
+          ]
+        }
+      ]
+    }    
+  ]
+}
\ No newline at end of file
diff --git a/src/lib/task.browser.executor.ts b/src/lib/task.browser.executor.ts
new file mode 100644
index 0000000..d103912
--- /dev/null
+++ b/src/lib/task.browser.executor.ts
@@ -0,0 +1,144 @@
+import { v4 as uuidv4 } from "uuid";
+import { ExecOps } from "./task.browser.schemas";
+import { actionDesc, StepRunnerRegistry } from "./task.browser.runner";
+import { emit } from "./task.browser.helpers";
+// import { Browser } from "puppeteer-core/lib/esm/puppeteer/puppeteer-core-browser.js";
+
+function findValidUrl(list: any[]) {
+    // go through the list and find the first valid raw element
+    for (const item of list) {
+        if (item.link && item.link.startsWith("https://")) {
+            return item;
+        }
+    }
+    return undefined;
+}
+
+export async function executeMacroPlan({
+    taskId,
+    pageManager,
+    browser,
+    plan,
+    skillMaps
+}: ExecOps) {
+    const page = await pageManager();
+    const history: Record<string, unknown> = {};
+
+    for (const step of plan.skills) {
+        /*
+        ** find the skill definition
+        */
+        const skillDef = skillMaps
+            .flatMap(s => s.skills)
+            .find(s => s.name === step.skill);
+
+        /*
+        ** build params map visible to a single skill page
+        */
+        let stepParams: Record<string, unknown> = {
+            ...step.parameters,
+            ...history
+        };
+
+        if ("text" in stepParams) stepParams.text = stepParams.text as string;
+        if ("number" in stepParams) stepParams.number = stepParams.number as number;
+        if ("times" in stepParams) stepParams.times = stepParams.times as number;
+        
+        /*
+        ** original text for splitting
+        */
+        const originalText = stepParams.text;
+
+        console.log("---->", skillDef?.name);
+
+        const planId = uuidv4();
+        emit("task_action_start", {
+            taskId,
+            action: "plan_execution",
+            speakToUser: "Executing the user request",
+            status: "running",
+            actionId: planId
+        });
+
+        /*
+        ** run every low level step
+        */
+        if (skillDef?.name === "open_result_by_index") {
+            const list = history.results as { link?: string; selector?: string }[] | undefined;
+            const idx = (stepParams.index ?? 0) as number;
+
+            if (list?.[idx]?.link) {
+                const element = findValidUrl(list);
+                stepParams.url_override = element?.link;
+                console.log("+++++>", element);
+            } else if (list?.[idx]?.selector) {
+                stepParams.selector_override = list[idx]!.selector; 
+            } else {
+                throw new Error(`Result list empty or index ${idx} missing`);
+            }
+        }
+        for (const step of skillDef?.steps || []) {
+            const actionId = uuidv4();
+
+            emit("task_action_start", {
+                taskId,
+                action: step.action,
+                speakToUser: actionDesc(step.action),
+                status: "running",
+                actionId
+            });
+
+            try {
+                /*
+                ** handle text splitting for split parameters
+                */
+                if (step.split && originalText && typeof originalText === "string") {
+                    const [title, body] = (originalText as string).split("<<<rt-space>>>", 2);
+                    
+                    if (step.split === "before") {
+                        stepParams.text = title.trim();
+                    } else if (step.split === "after") {
+                        stepParams.text = body?.trim() ?? "";
+                    }
+                }
+
+                /*
+                ** run the step
+                */
+                const result = await StepRunnerRegistry[step.action](step, { 
+                    page, 
+                    params: stepParams,
+                    browser
+                });
+                console.log("------->", result);
+
+                if (step.output_key)  history[step.output_key] = (result as { success: true, data: unknown }).data;
+
+                emit("task_action_complete", {
+                    taskId,
+                    action: step.action,
+                    status: "success",
+                    actionId
+                });
+            } catch (error) {
+                emit("task_action_end", {
+                    taskId,
+                    action: step.action,
+                    status: "error",
+                    actionId,
+                    error: error.message
+                });
+                throw error;
+            }
+        }
+
+        emit("task_action_complete", {
+            taskId,
+            action: "plan_execution",
+            status: "success",
+            actionId: planId
+        });
+    }
+
+    return history;
+}
\ No newline at end of file
diff --git a/src/lib/task.browser.helpers.ts b/src/lib/task.browser.helpers.ts
new file mode 100644
index 0000000..921f55b
--- /dev/null
+++ b/src/lib/task.browser.helpers.ts
@@ -0,0 +1,76 @@
+import { taskEventEmitter } from "./emitters";
+import { detectWebsites } from "./task.browser.skill.resolver";
+
+/*
+** emit an event
+*/
+export function emit(event: string, payload: any) {
+    try {
+        taskEventEmitter.emit(event, payload);
+    } catch (error) { }
+}
+
+/*
+** get function call
+*/
+export function getFnCall(resp: any): { name: string; args: Record<string, any> } | null {
+    const call = resp?.functionCalls?.[0];
+    return call
+}
+
+/**
+ * validate domains
+ */
+function validateDomains(domains: string[]): boolean {
+    return domains.every(domain => 
+        typeof domain === "string" && 
+        domain.includes(".") && 
+        domain.split(".").length >= 2
+    );
+}
+
+/**
+ * detect and validate domains
+ */
+export async function detectDomains({query, steps}: {query: string, steps: string[]}): Promise<string[]> {
+    const maxRetries = 3;
+    
+    for (let attempt = 1; attempt <= maxRetries; attempt++) {
+        try {
+            const domains = await detectWebsites({query, steps});
+
+            /*
+            ** validate domains
+            */
+            if (domains.length === 0) {
+                throw new Error("No domains detected");
+            }
+
+            if (!validateDomains(domains)) {
+                throw new Error("Invalid domains");
+            }
+
+            /*
+            ** validation succeeded, return domains
+            */
+            return domains;
+
+        } catch (error) {
+            console.log(`domain detection attempt ${attempt}/${maxRetries} failed:`, error.message);
+            
+            if (attempt === maxRetries) {
+                /*
+                ** final attempt failed, throw the error
+                */
+                throw new Error(`Failed after ${maxRetries} attempts: ${error.message}`);
+            }
+            
+            /*
+            ** wait a bit before retrying
+            */
+            await new Promise(resolve => setTimeout(resolve, 1000));
+        }
+    }
+    
+    throw new Error("Domain detection failed after all retry attempts");
+}
diff --git a/src/lib/task.browser.runner.atomic.ts b/src/lib/task.browser.runner.atomic.ts
new file mode 100644
index 0000000..ebe1782
--- /dev/null
+++ b/src/lib/task.browser.runner.atomic.ts
@@ -0,0 +1,147 @@
+import { Page } from "puppeteer-core/lib/esm/puppeteer/puppeteer-core-browser.js";
+import { SkillStep } from "./task.browser.schemas";
+
+type AtomicResult = 
+  | { success: true, data: unknown }
+  | { success: false, error: string };
+
+const DEFAULT_TIMEOUT = 10_000;
+
+/*
+** execute one loàw level browser step using pptr
+*/
+export async function runAtomicStep(
+    { step, page, params }:
+    { step: SkillStep, page: Page, params: Record<string, unknown> }
+): Promise<AtomicResult> {
+    try {
+        switch (step.action) {
+            /*
+            ** click & typing
+            */
+            case "click": {
+                await page.waitForSelector(step.selector!, { timeout: DEFAULT_TIMEOUT });
+                await page.click(step.selector!);
+                return { success: true, data: null };
+            }
+            case "type": {
+                const text = (params[step.input_key as string] ?? "") as string;
+                await page.type(step.selector!, text, { delay: 80 });
+                return { success: true, data: null };
+            }
+            case "press_enter": {
+                await page.keyboard.press("Enter");
+                return { success: true, data: null };
+            }
+
+            /*
+            ** scroll
+            */
+            case "scroll_down": {
+                const times = (step.times ?? 8); // times is basically 1 -> 0.25. to scroll 3 times, we need 12
+                for (let i = 0; i < times; i++) {
+                    await page.evaluate(
+                        () => window.scrollBy({
+                            top: window.innerHeight * 0.25,
+                            left: 0,
+                            behavior: 'smooth'
+                        })
+                    );
+                    await new Promise(resolve => setTimeout(resolve, 800));
+                }
+                return { success: true, data: null };
+            }
+
+            /*
+            ** waiting
+            */
+            case "wait_for_selector": {
+                await page.waitForSelector(step.selector!, { timeout: DEFAULT_TIMEOUT });
+                return { success: true, data: null };
+            }
+
+            /*
+            ** DOM extraction
+            */
+            case "extract_list": {
+                const { selector, schema } = step;
+                console.log("+++++>", selector, schema);
+              
+                const rows = await page.$$eval(selector!, (nodes, schema) => {
+                  return nodes.map((n: Element, idx: number) => {
+                    const out: Record<string, unknown> = { index: idx };
+                    for (const [key, rule] of Object.entries(schema as Record<string, string>)) {
+                      if (rule.startsWith("@")) {
+                        out[key] = n.getAttribute(rule.slice(1));
+                      } else if (rule.endsWith("::href")) {
+                        const sub = n.querySelector(rule.replace("::href", ""));
+                        out[key] = sub ? (sub as HTMLAnchorElement).href : null;
+                      } else if (rule.endsWith("::text")) {
+                        const sub = n.querySelector(rule.replace("::text", ""));
+                        out[key] = sub ? sub.textContent?.trim() : null;
+                      } else if (rule.endsWith("::self")) {
+                        out[key] = rule.replace("::self", "").replace("%asin%", n.getAttribute("data-asin") || "");
+                      } else {
+                        const sub = n.querySelector(rule);
+                        out[key] = sub ? sub.textContent?.trim() : null;
+                      }
+                    }
+                    return out;
+                  });
+                }, schema);
+              
+                return { success: true, data: rows };
+            }
+            
+            case "extract_fields": {
+                const raw = step.selector!;
+                const selectors = raw.split(",").map(s => s.trim()).filter(Boolean);
+                
+                const pieces = await page.evaluate((sels) => {
+                    return sels.map(sel => {
+                        const el = document.querySelector(sel);
+                        return el ? (el as HTMLElement).innerText.trim() : "";
+                    });
+                }, selectors);
+
+                const text = pieces
+                    .filter(Boolean)
+                    .map(t => t.replace(/\s+/g, " "))
+                    .join(" | ")
+                return { success: true, data: text };
+            }
+
+            /*
+            ** navigation helpers
+            */
+            case "navigate_back": {
+                await page.goBack({ waitUntil: "networkidle2" });
+                return { success: true, data: null };
+            }
+            case "navigate_to_url": {
+                const url = params.url_override || step.url;    
+                await page.goto(url as string, { waitUntil: "networkidle2", timeout: DEFAULT_TIMEOUT });
+                return { success: true, data: null };
+            }
+            case "click_element_by_index": {
+                const sel = params.selector_override || step.selector;
+                const idx = params.number ?? step.index ?? 0;
+              
+                await page.evaluate(({ sel, idx }) => {
+                  const nodes = Array.from(document.querySelectorAll(sel as string));
+                  if (!nodes[idx as number]) throw new Error(`Index ${idx} out of range`);
+                  (nodes[idx as number] as HTMLElement).scrollIntoView({ block: "center" });
+                  (nodes[idx as number] as HTMLElement).click();
+                }, { sel, idx });
+              
+                return { success: true, data: null };
+            }
+
+            default: {
+                throw new Error(`Unknown action: ${step.action}`);
+            }
+        }
+    } catch (error) {
+        return { success: false, error: (error as Error).message };
+    }
+}
\ No newline at end of file
diff --git a/src/lib/task.browser.runner.ts b/src/lib/task.browser.runner.ts
new file mode 100644
index 0000000..2b91917
--- /dev/null
+++ b/src/lib/task.browser.runner.ts
@@ -0,0 +1,113 @@
+import { SkillStep } from "./task.browser.schemas";
+import { runAtomicStep } from "./task.browser.runner.atomic";
+import { Browser, Page } from "puppeteer-core/lib/esm/puppeteer/puppeteer-core-browser.js";
+
+interface Runner {
+  (
+    step: SkillStep, 
+    ctx: { 
+      browser: Browser, 
+      page: Page, 
+      params: Record<string, unknown> 
+    }): Promise<unknown>;
+}
+
+export const StepRunnerRegistry: Record<SkillStep["action"], Runner> = {
+  /*
+  ** handle click
+  */
+  click: async (step, { page, params }) => runAtomicStep({ step, page, params }),
+
+  /*
+  ** handle type
+  */
+  type: async (step, { page, params }) => {
+    const text = params[step.input_key as string] as string;
+    return runAtomicStep({ step, page, params: { text } });
+  },
+
+  /*
+  ** handle press enter
+  */
+  press_enter: async (step, { page, params }) => runAtomicStep({ step, page, params }),
+
+  /*
+  ** handle wait for selector
+  */
+  wait_for_selector: async (step, { page, params }) => runAtomicStep({ step, page, params }),
+
+  /*
+  ** handle extract list
+  */
+  extract_list: async (step, { page, params }) => runAtomicStep({ step, page, params }),
+
+  /*
+  ** handle extract fields
+  */
+  extract_fields: async (step, { page, params }) => runAtomicStep({ step, page, params }),
+
+  /*
+  ** handle navigate back
+  */
+  navigate_back: async (step, { page, params }) => runAtomicStep({ step, page, params }),
+
+  /*
+  ** handle click element by index
+  */
+  click_element_by_index: async (step, { page, params }) => runAtomicStep({ step, page, params }),
+  /*
+  ** navigate to url
+  */
+  navigate_to_url: async (step, { page, params }) => runAtomicStep({ step, page, params }),
+
+  /*
+  ** handle scroll down
+  */
+  scroll_down: async (step, { page, params }) => runAtomicStep({ step, page, params }),
+};
+
+/**
+ * Provides semantic descriptions for browser actions
+ * @param action - The action type to get description for
+ * @returns A human-readable description of what the action does
+ */
+export function actionDesc(action: SkillStep["action"]): string {
+  const descriptions: Record<SkillStep["action"], string> = {
+    click: "Clicks on a specific element identified by a CSS selector",
+    type: "Types text into an input field or text area using a CSS selector",
+    press_enter: "Simulates pressing the Enter key on the currently focused element",
+    wait_for_selector: "Waits for an element to appear on the page before proceeding",
+    extract_list: "Extracts a list of text content or data from multiple elements matching a selector",
+    extract_fields: "Extracts field data (like form values) from elements matching a selector",
+    navigate_back: "Navigates back to the previous page in the browser history",
+    click_element_by_index: "Clicks on a specific element from a list of elements, identified by index position",
+    navigate_to_url: "Navigates to a specific URL",
+    scroll_down: "Scrolls down the page"
+  };
+
+  return descriptions[action] || `Unknown action: ${action}`;
+}
+
+/**
+ * Gets all available actions with their descriptions
+ * @returns An object mapping action names to their descriptions
+ */
+export function getAllActionDescriptions(): Record<SkillStep["action"], string> {
+  const actions: SkillStep["action"][] = [
+    "click",
+    "type", 
+    "press_enter",
+    "wait_for_selector",
+    "extract_list",
+    "extract_fields",
+    "navigate_back",
+    "click_element_by_index",
+    "navigate_to_url",
+    "scroll_down"
+  ];
+
+  return actions.reduce((acc, action) => {
+    acc[action] = actionDesc(action);
+    return acc;
+  }, {} as Record<SkillStep["action"], string>);
+}
\ No newline at end of file
diff --git a/src/lib/task.browser.schemas.ts b/src/lib/task.browser.schemas.ts
new file mode 100644
index 0000000..e026287
--- /dev/null
+++ b/src/lib/task.browser.schemas.ts
@@ -0,0 +1,80 @@
+import { JSONSchema7 } from "json-schema";
+/*
+** website skills
+*/
+export interface WebsiteSkills {
+    domain: string;
+    description?: string;
+    skills: {
+        name: string;
+        description: string;
+        input?: Record<string, unknown>;
+        output?: string;
+        steps?: SkillStep[];
+    }[];
+}
+
+/*
+** step action
+*/
+export type StepAction = 
+   | "click"
+   | "type"
+   | "press_enter"
+   | "wait_for_selector"
+   | "extract_list"
+   | "extract_fields"
+   | "navigate_back"
+   | "click_element_by_index"
+   | "navigate_to_url"
+   | "scroll_down";
+
+export interface ExecOps {
+    taskId: string;
+    browser: any;
+    pageManager: () => Promise<any>;
+    plan: any;
+    skillMaps: WebsiteSkills[];
+}
+/*
+** skill step
+*/
+export interface SkillStep {
+    action: StepAction;
+    selector?: string;
+    url?: string;
+    input_key?: string;
+    index?: number;
+    output_key?: string;
+    schema?: Record<string, string>;
+    times?: number;
+    split?: "before" | "after";
+}
+
+
+/*
+** remote options
+*/
+export interface RemoteOptions {
+    company?: string;
+    repo?: string;
+    branch?: string;
+}
+
+/*
+** browser run options
+*/
+export interface BrowserRunOptions {
+    taskId: string;
+    originalQuery: string;
+    browserInstance: any; // Browser to avoid silent errors
+    pageManager: () => Promise<any>; // Page;
+    steps: string[];
+}
+
+/*
+** browser plan declaration
+*/
+export interface BrowserPlan extends JSONSchema7 {
+    name: string;
+}
\ No newline at end of file
diff --git a/src/lib/task.browser.skill.registry.ts b/src/lib/task.browser.skill.registry.ts
new file mode 100644
index 0000000..0ab30fd
--- /dev/null
+++ b/src/lib/task.browser.skill.registry.ts
@@ -0,0 +1,96 @@
+import { invoke } from "@tauri-apps/api/core";
+import type { WebsiteSkills } from "./task.browser.schemas";
+import type { RemoteOptions } from "./task.browser.schemas";
+import SKILLS from "./skills.json" with { type: "json" };
+
+/*
+** registry
+*/
+export class SkillRegistry {
+    private sites = new Map<string, WebsiteSkills>();
+    private cache = new Map<string, WebsiteSkills>();
+
+    /*
+    ** get skills
+    */
+    private async fetchFromBackend(
+        domain: string,
+        { 
+            company = "runtime-org",
+            repo = "sk",
+            branch = "main"
+        }: RemoteOptions = {}
+    ): Promise<WebsiteSkills> {
+        return await invoke<WebsiteSkills>("load_skills", {
+            domain,
+            company,
+            repo,
+            branch
+        });
+    }
+    
+    // private loadFromRemote(domain: string): WebsiteSkills {
+    //     const website = SKILLS.websites.find((w: any) => w.domain === domain);
+    //     if (!website) throw new Error(`No skills found for domain: ${domain}`);
+
+    //     return {
+    //         domain: website.domain,
+    //         skills: website.skills as WebsiteSkills["skills"]
+    //     };
+    // }
+
+    private loadFromLocal(domain: string): WebsiteSkills {
+        const website = SKILLS.websites.find((w: any) => w.domain.includes(domain));
+        if (!website) throw new Error(`No skills found for domain: ${domain}`);
+
+        return {
+            domain: website.domain[0],
+            skills: website.skills as WebsiteSkills["skills"]
+        };
+    }
+
+    private fuzzyMatch(targetDomain: string): WebsiteSkills | null {
+        if (this.sites.has(targetDomain)) {
+            return this.sites.get(targetDomain)!;
+        }
+
+        const getBaseDomain = (domain: string): string => {
+            const lastDotIndex = domain.lastIndexOf('.');
+            return lastDotIndex > 0 ? domain.substring(0, lastDotIndex) : domain;
+        };
+
+        const targetBase = getBaseDomain(targetDomain);
+
+        for (const [existingDomain, skills] of this.sites.entries()) {
+            const existingBase = getBaseDomain(existingDomain);
+            if (existingBase === targetBase) {
+                return skills;
+            }
+        }
+        return null;
+    }
+
+    /*
+    ** get the full skill definition for a domain with caching
+    */
+    async byDomains(domains: string[]): Promise<WebsiteSkills[]> {
+        const sites: WebsiteSkills[] = [];
+
+        for (const domain of domains) {
+            try {
+                const existingSite = this.fuzzyMatch(domain);
+
+                if (existingSite) {
+                    sites.push(existingSite);
+                } else {
+                    const skills = this.loadFromLocal(domain) as WebsiteSkills;
+                    this.sites.set(domain, skills);
+                    sites.push(skills);
+                }
+            } catch (error) {
+                console.warn("error", error.message);
+            }
+        }
+        return sites;
+    }
+}
diff --git a/src/lib/task.browser.skill.resolver.ts b/src/lib/task.browser.skill.resolver.ts
new file mode 100644
index 0000000..b174889
--- /dev/null
+++ b/src/lib/task.browser.skill.resolver.ts
@@ -0,0 +1,192 @@
+import { SkillRegistry } from "./task.browser.skill.registry";
+import { WebsiteSkills } from "./task.browser.schemas";
+import { getFnCall } from "./task.browser.helpers";
+import { DomainDetectDeclaration } from "./task.browser.tools";
+import { buildMacroTool } from "./task.browser.tools.builder";
+import { callLLM } from "./llm.engine";
+ 
+/*
+** skill registry
+*/
+const reg = new SkillRegistry();
+
+/*
+** resolve skill plan
+*/
+export async function generateMacroPlan({
+        sites,
+        query
+    }: {
+        sites: WebsiteSkills[],
+        query: string
+    }): Promise<any> {
+
+    const macroTool = buildMacroTool(sites);
+    console.log("macroTool", macroTool);
+
+    const config = {
+        temperature: 0.0,
+        maxOutputTokens: 8192,
+        mode: "ANY",
+        tools: [{ functionDeclarations: [ macroTool ] }]
+    };
+
+    const FEW_SHOTS = `
+EXAMPLE 1:
+User Request: "search for the USB C under 25 dollars cable in amazon, and put it in my cart"
+{
+  "skills": [
+    { "skill": "search_products",          "parameters": { "text": "USB C under 25 dollars cable" } },
+    { "skill": "open_result_by_index",     "parameters": { "index": 0 } },
+    { "skill": "extract_product_details",  "parameters": {} },
+    { "skill": "add_current_product_to_cart", "parameters": {} }
+  ]
+}
+
+
+EXAMPLE 2:
+User Request: "check my gmail, and see the latest drone received from dji, and try to look for it on amazon, and add the cheapest one to my cart"
+{
+  "skills": [
+    { "skill": "search_emails", "parameters": { "text": "drone from dji" } },
+    { "skill": "open_email_by_index", "parameters": { "number": 0 } },
+    { "skill": "extract_email_details", "parameters": {} },
+    { "skill": "search_products", "parameters": { "text": "<product name from previous step>" } },
+    { "skill": "open_result_by_index", "parameters": { "number": 0 } },
+    { "skill": "extract_product_details", "parameters": {} },
+    { "skill": "add_current_product_to_cart", "parameters": {} }
+  ]
+}
+- add_current_product_to_cart: parameters: {}
+
+EXAMPLE 3:
+User Request: "check my gmail, and see the latest drone received from dji, and try to look for it on amazon, and give me the cheapest one"
+{
+  "skills": [
+    { "skill": "search_emails", "parameters": { "text": "drone from dji" } },
+    { "skill": "open_email_by_index", "parameters": { "number": 0 } },
+    { "skill": "extract_email_details", "parameters": {} },
+    { "skill": "search_products", "parameters": { "text": "<product name from previous step>" } },
+    { "skill": "open_result_by_index", "parameters": { "number": 0 } },
+    { "skill": "extract_product_details", "parameters": {} }
+  ]
+}
+`
+
+    // skills generator
+    const generateSkillsSection = (sites: WebsiteSkills[]): string => {
+        return sites.map(site => {
+            const skillsList = site.skills.map(skill => 
+                `- ${skill.name}: ${skill.description}`
+            ).join('\n');
+            
+            return `Skills of ${site.domain}:\n${skillsList}`;
+        }).join('\n\n');
+    };
+
+    const skillsSection = generateSkillsSection(sites);
+
+    const prompt = `
+You are a plan generator for a browser automation task on specific websites.
+You will be given a list of skills and a user request. The skills are specific to the websites you are going to interact with. 
+And each skill has a description of what it does and the input it needs optionally.
+You must generate a plan that will fully satisfy the user request.
+If the user request involves multiple websites, you will be provided a list of skills for each website.
+And each function has already some step for it execution, you don't need to worry about the steps, just focus on the successfull execution of the functions/skills.
+
+Note: You should use the following tool to generate the skill pipeline: generate_skill_pipeline
+
+## Skills
+${skillsSection}
+
+## Examples
+${FEW_SHOTS}
+
+Here is the user request:
+User request: ${query}
+Plan as list of skills:
+`
+
+    console.log("prompt", prompt);
+    const resp = await callLLM({
+        modelId: "gemini-2.5-flash",
+        contents: [{ role: "user", parts: [{ text: prompt } ] }],
+        config,
+        ignoreFnCallCheck: true
+    });
+
+    console.log("resp", resp);
+
+    const fn = getFnCall(resp);
+    if (!fn?.args?.skills) throw new Error("Plan generation failed");
+
+    return fn.args;
+}
+
+/*
+** detect target site
+*/
+const FEW_SHOTS = `
+EXAMPLE 1:
+User Request: "search for the cheapiest USB C cable in amazon, and put it in my cart"
+Steps: ["Go to amazon.com", "Search for \"cheapest USB C cable\"", "Click on the add to cart button for the cheapest USB C cable"]
+Domains: ["amazon.com"]
+
+EXAMPLE 2:
+User Request: "check slack, and tell me the car brand of Alex"
+Steps: ["Go to Slack.", "Search for Alex.", "Find information about Alex's car brand."]
+Domains: ["slack.com"]
+
+EXAMPLE 3:
+User Request: "check my gmail, and see the latest drone received from dji, and try to look for it on amazon, and give me the cheapest one"
+Steps: ["Go to Gmail.", "Search for emails from DJI about drones.", "Identify the latest drone model received.", "Go to Amazon.", "Search for the identified DJI drone model.", "Find the cheapest option."]
+Domains: ["gmail.com", "amazon.com"]
+
+EXAMPLE 4:
+User Request: "check for the email received from cursor and notion, and tell me the latest update they have"
+Steps: ["Go to Gmail.", "Search for emails from Cursor and Notion.", "Find the latest update from each sender."]
+Domains: ["gmail.com"]
+`
+
+export async function detectWebsites(
+    {query, steps}: 
+    {query: string, steps: string[]}
+): Promise<string[]> {
+
+    const _steps = steps.map((step) => `"${step}"`).join(", ");
+
+    const prompt = `You are a domain detector.
+You will be given a user request and a list of steps. You must use the steps to determine the domains of websites that the user intends to interact with.
+Your task is to identify the domains of the websites that the user intends to interact with. Please use steps interactions to determine the domains.
+Please return only the domain names, and do not include any additional text.
+
+Notes:
+- The user request may refer to one or more domains.
+- Sometimes, the user request may mention other websites in the query, but the steps may not involve that website, so you should not mention those websites.
+
+${FEW_SHOTS}
+
+Assistant:
+User request: ${query}
+Steps: [${_steps}]
+Domains:
+`;
+
+    const config = {
+        temperature: 0.0,
+        maxOutputTokens: 8192,
+        mode: "ANY",
+        tools: [{ functionDeclarations: [DomainDetectDeclaration] }]
+    };
+    const resp = await callLLM({
+      modelId: "gemini-2.5-flash",
+      contents: [{ role: "user", parts: [{ text: prompt }] }],
+      config,
+      ignoreFnCallCheck: true
+    });
+
+    const fn = getFnCall(resp);
+
+    if (!fn?.args?.domains) throw new Error("Domain detection failed");
+    return fn.args.domains as string[];
+}
\ No newline at end of file
diff --git a/src/lib/task.browser.tools.builder.ts b/src/lib/task.browser.tools.builder.ts
new file mode 100644
index 0000000..846d20c
--- /dev/null
+++ b/src/lib/task.browser.tools.builder.ts
@@ -0,0 +1,50 @@
+import { WebsiteSkills } from "./task.browser.schemas";
+import { Type } from "@google/genai"
+
+/*
+** collect every skills name from every website map
+*/
+function flatSkills(sites: WebsiteSkills[]): string[] {
+    return sites.flatMap((site) => site.skills.map((skill) => skill.name));
+}
+
+export function buildMacroTool(
+    sites: WebsiteSkills[]
+) {
+    const skillNames = flatSkills(sites);
+
+    return {
+        name: "generate_skill_pipeline",
+        description: `
+Return an OBJECT with a single key "skills".
+"skills" is an ordered ARRAY; each element invoke ONE skill.  
+Allowed parameters key are "text" (STRING) and "number" (NUMBER).
+Leave it empty {} when the skill has no input.
+        `,
+        parameters: {
+            type: Type.OBJECT,
+            properties: {
+                skills: {
+                    type: Type.ARRAY,
+                    items: {
+                        type: Type.OBJECT,
+                        properties: {
+                            skill: { type: Type.STRING, enum: skillNames },
+                            parameters: { 
+                                type: Type.OBJECT,
+                                properties: {
+                                    text: { type: Type.STRING },
+                                    number: { type: Type.NUMBER }
+                                },
+                                required: []
+                            }
+                        },
+                        required: ["skill", "parameters"]
+                    }
+                }
+            },
+            required: ["skills"]
+        }
+    };
+}
+
diff --git a/src/lib/task.browser.tools.ts b/src/lib/task.browser.tools.ts
new file mode 100644
index 0000000..fde70a7
--- /dev/null
+++ b/src/lib/task.browser.tools.ts
@@ -0,0 +1,20 @@
+import { Type } from "@google/genai";
+
+/*
+** domain detection declaration
+*/
+export const DomainDetectDeclaration = {
+    name: "detect_domains",
+    description: "Detect the domains of websites that the user wants to interact with based on their query. Return only the base domains as an array.",
+    parameters: {
+      type: Type.OBJECT,
+      properties: { 
+        domains: { 
+          type: Type.ARRAY, 
+          items: { type: Type.STRING },
+          description: "Array of base domains, e.g. ['amazon.com', 'google.com']" 
+        } 
+      },
+      required: ["domains"]
+    }
+};
diff --git a/src/lib/task.browser.ts b/src/lib/task.browser.ts
new file mode 100644
index 0000000..1aab450
--- /dev/null
+++ b/src/lib/task.browser.ts
@@ -0,0 +1,87 @@
+
+import { emit, detectDomains } from "./task.browser.helpers";
+import { 
+    BrowserRunOptions
+} from "./task.browser.schemas";
+
+import { SkillRegistry } from "./task.browser.skill.registry";
+import { handler } from "./task.helpers";
+import { generateMacroPlan } from "./task.browser.skill.resolver";
+import { executeMacroPlan } from "./task.browser.executor";
+
+/*
+** run a browser task
+*/
+export async function runBrowserAction(opts: BrowserRunOptions) {
+    const { 
+        taskId, 
+        originalQuery, 
+        browserInstance, 
+        pageManager, 
+        steps
+    } = opts;
+
+    const currentPage = await pageManager();
+
+    const payload_detect_website_intent = {
+        taskId,
+        action: "detect_website_intent",
+        status: "running",
+        speakToUser: "Identifying the website you want to interact with, it will help me to understand your request better."
+    }
+
+    /*
+    ** domain detection
+    */
+    const { 
+        response: response_detect_website_intent,
+        error: error_detect_website_intent,
+        // actionId
+    } = await handler(
+        async () => await detectDomains({query: originalQuery, steps}),
+        payload_detect_website_intent
+    );
+
+    if (error_detect_website_intent) return;
+
+    console.log("domains", response_detect_website_intent);
+    const domains = response_detect_website_intent;
+
+    const skill_maps = await new SkillRegistry().byDomains(domains)
+
+    console.log("skillMap", skill_maps);
+
+    /*
+    ** generate the skill plan
+    */
+    const plan = await generateMacroPlan({sites: skill_maps, query: originalQuery});
+    console.log("plan", plan);
+
+    /*
+    ** execute the macro plan
+    */
+    const history = await executeMacroPlan({
+        taskId,
+        browser: browserInstance,
+        pageManager,
+        plan,
+        skillMaps: skill_maps
+    });
+
+    console.log("history", history);
+
+    /*
+    ** update the workflow
+    */
+    emit("workflow_update", {
+        taskId,
+        action: "done",
+        status: "completed",
+        speakToUser: "Task completed",
+        error: null
+    });
+
+
+
+
+}
diff --git a/src/lib/task.execution.helpers.ts b/src/lib/task.execution.helpers.ts
index 031954d..a293153 100644
--- a/src/lib/task.execution.helpers.ts
+++ b/src/lib/task.execution.helpers.ts
@@ -27,7 +27,7 @@ export function emit(event: string, payload: any) {
 ** truncate a string to a given length
 */
 export function truncate(str: string, maxLength: number) {
-    return str.length > maxLength ? str.slice(0, maxLength) + "…" : str;
+    return str.length > maxLength ? str.slice(0, maxLength) + "..." : str;
 }
 
 /*
@@ -36,7 +36,7 @@ export function truncate(str: string, maxLength: number) {
 export async function researchHelper(opts: ResearchHelperOptions): Promise<{links: { index: number, href: string }[]}> {
     const {
         subQuery,
-        maxLinks = 10,
+        maxLinks = 6,
         browserInstance,
         currentPage,
         history,
@@ -47,18 +47,20 @@ export async function researchHelper(opts: ResearchHelperOptions): Promise<{link
     ** get the interactive elements
     */
     const simplifyingId = uuidv4();
+
     emit("task_action_start", { 
         actionId: simplifyingId, 
         action: "get_simplified_page_context", 
         speakToUser: `Looking for links to answer: ${subQuery}`,
         taskId: taskId,
         status: "running",
+        url: currentPage.url()
     });
-    
+
     const pptrRes = await handlePuppeteerAction({
         actionDetails : {
           action : "get_simplified_page_context",
-          parameters: { include_screenshot: false, max_elements: 50 },
+          parameters: { max_elements: 10000 },
           taskId : taskId
         },
         currentPage,
@@ -68,7 +70,7 @@ export async function researchHelper(opts: ResearchHelperOptions): Promise<{link
     pushHistory(
         history, 
         'get_simplified_page_context', 
-        { include_screenshot: false, max_elements: 50 },
+        { max_elements: 10000 },
         pptrRes.data
     );
 
@@ -82,33 +84,37 @@ export async function researchHelper(opts: ResearchHelperOptions): Promise<{link
     */
     const listForLLM = Object.values(elementMap)
         .map((e: any) => {
-            const preview = (e.text || e.attributes?.["aria-label"] || "").slice(0, 80);
-            const url = e.attributes?.href 
-                ? ` → ${truncate(e.attributes.href, 40)}` 
-                : "";
-            return `${e.index}. [${e.tag}] ${preview}${url}`;
+            return `${e.index}. [${e.tag}] ${e.accessibleName} - ${truncate(e.href, 40)}`;
         })
         .join("\n");
     
-    const prompt = `Sub-query: "${subQuery}"
+    const prompt = `
+You are a helpful assistant that helps users find information on a web page.
+you should use the pick_links tool to select the indices of the elements that should be visited and return the function call.
+
+Sub-query: "${subQuery}"
 
-Here is a list of interactive elements (index, tag, preview):
+Here is a list of interactive elements (index, tag, accessibleName, href):
 
 ${listForLLM}
 
-Based on the list of elements above and the sub-query, select up to ${maxLinks} indices that should be visited, in order, to answer the sub-query. Prioritize the 4 to 5 most relevant indices.`
+Based on the list of elements above and the sub-query, select up to ${maxLinks} indices 
+that should be visited, in order, to answer the sub-query. Prioritize the 3 to 4 most relevant indices.
+
+Return the indices as an array of numbers using indices as the keys not pick_links`
 
     const pickResp = await callLLM({
         modelId : "gemini-2.5-flash",
         contents: [{ role : "user", parts: [{ text: prompt }] }],
         config: {
             temperature : 0,
-            maxOutputTokens : 10096,
+            maxOutputTokens : 100000,
             mode : "ANY",
             tools : [{ functionDeclarations: [PickLinksDeclaration] }]
         },
         ignoreFnCallCheck: true
     });
+
     emit("task_action_complete", {
         actionId: simplifyingId,
         action: "get_simplified_page_context",
@@ -125,9 +131,8 @@ Based on the list of elements above and the sub-query, select up to ${maxLinks}
     const links = indices
         .map(i => ({
             index: i,
-            href: elementMap[i]?.attributes?.href
-        }))
-        .filter(l => !!l.href); // drop button without href (links)
+            href: elementMap[i]?.href
+        }));
 
     return { links };
 
@@ -157,6 +162,7 @@ export async function visitAndSummarizeUrl(opts: VisitAndSummarizeUrlOptions) {
         speakToUser: `Visiting: ${truncate(href, 40)}`,
         taskId: taskId,
         status: "running",
+        url: currentPage.url()
     });
     const navRes = await handlePuppeteerAction({
         actionDetails: {
@@ -201,6 +207,7 @@ export async function visitAndSummarizeUrl(opts: VisitAndSummarizeUrlOptions) {
         speakToUser: `Summarizing the text`,
         taskId: taskId,
         status: "running",
+        url: currentPage.url()
     });
     const { summary } = await summarizeText({
         rawText: (textRes.data as any)?.visibleText || "",
@@ -314,24 +321,6 @@ export function semanticPptrExplanation(fnName: string, fnArgs: any, rawResult?:
         case 'scroll_to_text':
             description = `Successfully scrolled to find "${fnArgs.text}"`;
             break;
-            
-        /*
-        ** dropdown interactions
-        */
-        case 'get_dropdown_options':
-            description = `Successfully retrieved dropdown options from element ${fnArgs.index}`;
-            break;
-            
-        case 'select_dropdown_option':
-            description = `Successfully selected "${fnArgs.text}" from dropdown ${fnArgs.index}`;
-            break;
-            
-        /*
-        ** content extraction
-        */
-        case 'get_accessibility_tree':
-            description = `Successfully retrieved accessibility tree with ${fnArgs.number_of_elements} elements`;
-            break;
         
         case 'get_visible_text':
             description = `Successfully retrieved visible text: ${rawResult.summary}`;
@@ -361,40 +350,4 @@ export function semanticPptrExplanation(fnName: string, fnArgs: any, rawResult?:
     }
     
     return description;
-}
-
-/*
-** purge history of a specific link
-*/
-export function removeLinkFromHistory(
-    history: any[],
-    linkOrPageUrl: string,
-    elemIndex?: number
-) {
-    return history.filter(msg => {
-        const text  = msg?.parts?.[0]?.text ?? "";
-        const fCall = msg?.parts?.[0]?.functionCall;
-    
-        /*
-        ** Match by raw URL
-        */
-        if (!elemIndex && typeof linkOrPageUrl === "string") {
-          if (text.includes(linkOrPageUrl)) return false;
-          if (fCall?.args?.data?.interactiveElements) {
-            return !fCall.args.data.interactiveElements.some(
-              (el: any) => el.attributes?.href === linkOrPageUrl
-            );
-          }
-        }
-    
-        /*
-        ** Match by pageUrl + element index
-        */
-        if (elemIndex !== undefined) {
-          const key = `${linkOrPageUrl}::${elemIndex}`;
-          return !text.includes(key);
-        }
-    
-        return true;
-      });
 }
\ No newline at end of file
diff --git a/src/lib/task.execution.llm.ts b/src/lib/task.execution.llm.ts
index 8360984..155aa97 100644
--- a/src/lib/task.execution.llm.ts
+++ b/src/lib/task.execution.llm.ts
@@ -18,27 +18,38 @@ Return one synthesize_results tool call.
 Important:
 - Please format your response using markdown formatting (for example, use '#' for headings, '**word**' for bold, and ' *' for bullet points).
 - Always use bullet points to enhance clarity and readability, unless the answer consists of a single sentence. Avoid presenting answers as a block of sentences; instead, use bullet points for better organization.
+  But always start with a sentence.
 - Limit each paragraph to a maximum of two sentences, but always prefer to use bullet points to enhance clarity and readability.
 - Do not mention any tools unless they are directly relevant to the user's question.
 - If there is missing information or a knowledge gap, provide the best possible answer based on the information available.
 `;
 
-console.log("synthesisPrompt", synthesisPrompt);
-
-  const synthResp = await callLLM({
-    modelId: model,
-    contents: [{ role: "user", parts: [{ text: synthesisPrompt }] }],
-    config: {
-      temperature: 0.2,
-      maxOutputTokens: 4096,
-      mode: "ANY",
-      tools: [{ functionDeclarations: SynthesisDeclaration }]
-    }, 
-    ignoreFnCallCheck: true
-  });
-
-  const synthFn = getFnCall(synthResp);
-  console.log("synthFn", synthFn);
+  console.log("synthesisPrompt", synthesisPrompt);
+
+  let synthFn: any = null;
+  let attempt = 0;
+  const maxTries = 2;
+
+  while (attempt < maxTries && !synthFn?.args?.synthesized_answer) {
+    attempt++;
+    console.log(`Synthesis attempt ${attempt}/${maxTries}`);
+
+    const synthResp = await callLLM({
+      modelId: model,
+      contents: [{ role: "user", parts: [{ text: synthesisPrompt }] }],
+      config: {
+        temperature: attempt === 1 ? 0.2 : 0.3,
+        maxOutputTokens: 8192,
+        mode: "ANY",
+        tools: [{ functionDeclarations: SynthesisDeclaration }]
+      }, 
+      ignoreFnCallCheck: true
+    });
+
+    synthFn = getFnCall(synthResp);
+    console.log(`synthFn (attempt ${attempt})`, synthFn);
+  }
+
   const finalAnswer =
     synthFn?.args?.synthesized_answer ||
     synthFn?.args?.summary ||
diff --git a/src/lib/task.execution.ts b/src/lib/task.execution.ts
index fc6d4a5..15d3e28 100644
--- a/src/lib/task.execution.ts
+++ b/src/lib/task.execution.ts
@@ -29,8 +29,6 @@ export async function runSequentialTask(opts: SeqRunOptions) {
         model = 'gemini-2.5-flash'
     } = opts;
 
-    console.log("queries", queries);
-
     /*
     ** shared memory across queries tasks
     */
@@ -116,7 +114,6 @@ export async function runSequentialTask(opts: SeqRunOptions) {
         /*
         ** build a deterministic plan -> steps 
         */
-        console.log("🔍 SQ", qIdx, subQuery);
         while (!planDone && attempts < 5) {
             attempts++;
 
@@ -145,9 +142,8 @@ export async function runSequentialTask(opts: SeqRunOptions) {
             */
             for (let s = 0; s < steps.length; s++) {
                 const sentence = steps[s];
-                // console.log("📚 History context:", history);
 
-                const toolCall = await stepTranslator(sentence, history);
+                const toolCall = await stepTranslator(sentence, history, []);
 
                 if (!toolCall) {
                     opts.onError?.(`Translator failed at step ${s} of SQ${qIdx}`);
@@ -192,7 +188,8 @@ export async function runSequentialTask(opts: SeqRunOptions) {
                     action: toolCall.name, 
                     speakToUser: sentence, 
                     actionId, 
-                    status:"running" 
+                    status:"running" ,
+                    url: currentPage.url()
                 });
 
                 let pptrRes = await handlePuppeteerAction({
@@ -297,7 +294,8 @@ export async function runSequentialTask(opts: SeqRunOptions) {
         action: "synthesize_results",
         actionId,
         speakToUser: "Reasoning about the results",
-        status: "running"
+        status: "running",
+        url: currentPage.url()
     });
 
     const finalResult = await synthesizeResults(originalQuery, results, model);
@@ -327,5 +325,5 @@ export async function runSequentialTask(opts: SeqRunOptions) {
         currentPage
     });
 
-    await currentPage.close();
+    // await currentPage.close();
 }
\ No newline at end of file
diff --git a/src/lib/task.helpers.ts b/src/lib/task.helpers.ts
new file mode 100644
index 0000000..b727a61
--- /dev/null
+++ b/src/lib/task.helpers.ts
@@ -0,0 +1,63 @@
+import { v4 as uuidv4 } from "uuid";
+import { emit } from "./task.browser.helpers";
+
+/*
+** handler for coupling task with event emitter
+*/
+export async function handler(
+    task: () => Promise<any>, 
+    payload: any
+): Promise<{actionId: string, response: any, error: string | null}> {
+
+    const actionId = uuidv4();
+    let response: any = null;
+
+    /*
+    ** emit start
+    */
+    emit("task_action_start", {
+        actionId,
+        ...payload
+    });
+
+    /*
+    ** run the task
+    */
+    try {
+        response = await task();
+
+        /*
+        ** emit success
+        */
+        emit("task_action_complete", {
+            actionId,
+            ...payload,
+            status: "success"
+        });
+    } catch (error) {
+        /*
+        ** emit error
+        */
+        console.log("error", error.message);
+        emit("task_action_error", {
+            actionId,
+            ...payload,
+            error: error.message,
+            status: "error"
+        });
+        return {
+            actionId,
+            response,
+            error: error.message
+        };
+    }
+
+    /*
+    ** return the response
+    */
+    return {
+        actionId,
+        response,
+        error: null
+    };
+}
\ No newline at end of file
diff --git a/src/lib/tools.ts b/src/lib/tools.ts
index e976fa3..4284487 100644
--- a/src/lib/tools.ts
+++ b/src/lib/tools.ts
@@ -167,40 +167,6 @@ export const ActionDeclarations = [
     }
   },
 
-  // ===== DROPDOWN INTERACTIONS =====
-  {
-    name: 'get_dropdown_options',
-    description: 'Get all options from a native dropdown select element',
-    parameters: {
-      type: Type.OBJECT,
-      properties: {
-        index: {
-          type: Type.NUMBER,
-          description: 'The index number of the dropdown element'
-        }
-      },
-      required: ['index']
-    }
-  },
-  {
-    name: 'select_dropdown_option',
-    description: 'Select dropdown option for interactive element index by the text of the option you want to select',
-    parameters: {
-      type: Type.OBJECT,
-      properties: {
-        index: {
-          type: Type.NUMBER,
-          description: 'The index number of the dropdown element'
-        },
-        text: {
-          type: Type.STRING,
-          description: 'The exact text of the option to select'
-        }
-      },
-      required: ['index', 'text']
-    }
-  },
-
   // ===== PAGE STATE/CONTEXT =====
   {
     name: 'get_simplified_page_context',
@@ -214,7 +180,7 @@ export const ActionDeclarations = [
         },
         max_elements: {
           type: Type.NUMBER,
-          description: 'Maximum number of interactive elements to return (default: 50)'
+          description: 'Maximum number of interactive elements to return (default: 100)'
         }
       }
     }
@@ -253,59 +219,6 @@ export const ActionDeclarations = [
   },
 ];
 
-export const QueryAnalysisDeclaration = [
-  {
-    name: 'analyze_query_strategy',
-    description: 
-        `Break the user query into an ordered SEQUENTIAL chain of sub-queries.
-        Return each sub-query and an explicit dependencies list so the caller
-        knows which earlier answers are required for each later step.`,
-    parameters: {
-      type: Type.OBJECT,
-      properties: {
-        queries: {
-          type: Type.ARRAY,
-          items: {
-            type: Type.STRING,
-            description: 'One concrete, self-contained sub-query.'
-          }
-        },
-        researchFlags: {
-          type: Type.ARRAY,
-          description: `List the indices of sub-queries (e.g., [0, 1, 3]) that require open-web research. If an index is not listed, it does not require research.
-          In general, if the user requests information, open-web research should be performed.`,
-          items: {
-            type: Type.NUMBER,
-            description: 'Index of a sub-query that requires open-web research'
-          }
-        },
-        dependencies: {
-          type: Type.ARRAY,
-          description: 'For every sub-query that relies on previous answers, specify its index and the indexes it depends on.',
-          items: {
-            type: Type.OBJECT,
-            properties: {
-              query_index: {
-                type: Type.NUMBER,
-                description: 'Index of the dependent sub-query'
-              },
-              depends_on: {
-                type: Type.ARRAY,
-                items: {
-                  type: Type.NUMBER,
-                  description: 'Indices of prerequisite sub-queries'
-                }
-              }
-            },
-            required: ['query_index', 'depends_on']
-          }
-        }
-      },
-      required: ['queries', 'dependencies', 'researchFlags']
-    }
-  }
-];
-
 
 export const AvailableActions = [
   "done",
@@ -320,8 +233,6 @@ export const AvailableActions = [
   "scroll_down",
   "scroll_up", 
   "scroll_to_text",
-  "get_dropdown_options",
-  "select_dropdown_option",
   "get_simplified_page_context",
   "get_visible_text",
   "ask_user",
diff --git a/src/lib/utils.js b/src/lib/utils.js
deleted file mode 100644
index 0d5be05..0000000
--- a/src/lib/utils.js
+++ /dev/null
@@ -1,41 +0,0 @@
-export function formatDateString(dateString) {
-    if (typeof dateString !== 'string') {
-        throw new TypeError('dateString must be a string');
-    }
-    const date = new Date(dateString);
-
-    if (isNaN(date.getTime())) {
-        return "Invalid Date";
-    }
-  
-    const day = date.getDate();
-    const monthIndex = date.getMonth();
-    const year = date.getFullYear();
-  
-    let hours = date.getHours(); // (0-23).
-    const minutes = date.getMinutes(); // (0-59).
-    const seconds = date.getSeconds(); // (0-59).
-  
-    const monthNames = [
-      "Jan", "Feb", "Mar", "Apr", "May", "Jun",
-      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
-    ];
-    const monthName = monthNames[monthIndex]; 
-  
-    const ampm = hours >= 12 ? 'PM' : 'AM';
-    hours = hours % 12;
-    hours = hours ? hours : 12;
-  
-    const paddedMinutes = minutes < 10 ? '0' + minutes : minutes;
-    const paddedSeconds = seconds < 10 ? '0' + seconds : seconds;
-  
-    const formattedDate = `${day} ${monthName} ${year} at ${hours}:${paddedMinutes}:${paddedSeconds} ${ampm}`;
-  
-    return formattedDate;
-  }
-  
-  export const browserIcons = {
-    "chrome": "/icons/chrome.svg",
-    "edge": "/icons/edge.svg",
-    "default": "/icons/chrome.svg"
-  }
\ No newline at end of file
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
new file mode 100644
index 0000000..58d0e31
--- /dev/null
+++ b/src/lib/utils.ts
@@ -0,0 +1,93 @@
+export function formatDateString(dateString: string) {
+  if (typeof dateString !== 'string') {
+      throw new TypeError('dateString must be a string');
+  }
+  const date = new Date(dateString);
+
+  if (isNaN(date.getTime())) {
+      return "Invalid Date";
+  }
+
+  const day = date.getDate();
+  const monthIndex = date.getMonth();
+  const year = date.getFullYear();
+
+  let hours = date.getHours(); // (0-23).
+  const minutes = date.getMinutes(); // (0-59).
+  const seconds = date.getSeconds(); // (0-59).
+
+  const monthNames = [
+    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+  ];
+  const monthName = monthNames[monthIndex]; 
+
+  const ampm = hours >= 12 ? 'PM' : 'AM';
+  hours = hours % 12;
+  hours = hours ? hours : 12;
+
+  const paddedMinutes = minutes < 10 ? '0' + minutes : minutes;
+  const paddedSeconds = seconds < 10 ? '0' + seconds : seconds;
+
+  const formattedDate = `${day} ${monthName} ${year} at ${hours}:${paddedMinutes}:${paddedSeconds} ${ampm}`;
+
+  return formattedDate;
+}
+
+export const browserIcons: Record<string, string> = {
+    "chrome": "/icons/chrome.svg",
+    "edge": "/icons/edge.svg",
+    "default": "/icons/chrome.svg"
+}
+
+export function mdToHtml(source: string): string {
+  if (!source) return "";
+
+  const esc = (s: string) =>
+    s.replace(/&/g, "&amp;")
+     .replace(/</g, "&lt;")
+     .replace(/>/g, "&gt;");
+
+  const bold = (s: string) =>
+    s.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
+
+  const lines     = source.split(/\r?\n/);
+  let html        = "";
+  let listStack   = 0;           // how many <ul> are open
+  const openUl    = () => { html += "<ul>"; listStack++; };
+  const closeUl   = () => { html += "</ul>"; listStack--; };
+
+  const flushListsTo = (level: number) => {
+    while (listStack > level) closeUl();
+    while (listStack < level) openUl();
+  };
+
+  lines.forEach(raw => {
+    if (!raw.trim()) {
+      flushListsTo(0);
+      return;
+    }
+
+    const bullet = raw.match(/^(\s*)\*\s+(.*)/);
+    if (bullet) {
+      const indent = Math.floor(bullet[1].length / 2);
+      flushListsTo(indent + 1);
+      html += `<li>${bold(esc(bullet[2]))}</li>`;
+      return;
+    }
+
+    const heading = raw.match(/^(#{1,6})\s+(.*)/);
+    if (heading) {
+      flushListsTo(0);
+      const lvl = heading[1].length;
+      html += `<h${lvl}>${bold(esc(heading[2]))}</h${lvl}>`;
+      return;
+    }
+
+    flushListsTo(0);
+    html += `<p>${bold(esc(raw.trim()))}</p>`;
+  });
+
+  flushListsTo(0);
+  return html;
+}
diff --git a/src/lib/workflow.runner.ts b/src/lib/workflow.runner.ts
index ec2b5fa..041860a 100644
--- a/src/lib/workflow.runner.ts
+++ b/src/lib/workflow.runner.ts
@@ -1,61 +1,68 @@
 import { v4 as uuidv4 } from 'uuid';
 
-import { makeWorkflow } from "./workflow.helpers";
+// import { makeWorkflow } from "./workflow.helpers";
 import { createPagePool } from "./page.manager";
 import { runSequentialTask } from "./task.execution";
 import { emit } from "./task.execution.helpers";
 import { RunnerOptions } from "./workflow.schema";
+import { runBrowserAction } from "./task.browser";
 
 export async function runWorkflow(opts: RunnerOptions) {
   const { 
+    mode,
     originalQuery, 
     sessionId, 
     queries, 
     dependencies,
     browserInstance,
-    researchFlags
+    researchFlags,
+    steps
   } = opts;
 
-  const pageManager = createPagePool({ browser: browserInstance });
-
-  /*  
-  ** create workflow
-  */
-  makeWorkflow({ originalQuery, sessionId, queries, dependencies });
 
+  const pageManager = createPagePool({ browser: browserInstance });
   /* 
   ** emit
   */
   const taskId = uuidv4();
   emit('workflow_update', { taskId, action: 'CREATE_WORKFLOW', speakToUser: "Starting the task", status: 'initial' });
 
-  /*
-  ** create and run a runner for each sub-query
-  */
-  const next = async () => {
-    await runSequentialTask({
-      originalQuery,
-      queries,
+  if (mode === "browser_action") {
+    /*
+    ** create and run a runner for the browser action
+    */
+    console.log("steps", steps);
+    await runBrowserAction({
       taskId,
-      sessionId,
+      originalQuery,
       pageManager,
-      dependencies,
-      researchFlags,
-      browserInstance
+      browserInstance,
+      steps
     });
+  } else if (mode === "analysis") {
+    /*
+    ** create and run a runner for each sub-query
+    */
+    const next = async () => {
+      await runSequentialTask({
+        originalQuery,
+        queries,
+        taskId,
+        sessionId,
+        pageManager,
+        dependencies,
+        researchFlags,
+        browserInstance
+      });
 
-  };
+    };
 
-  /*
-  ** start
-  */
-  await next();
-
-  /*
-  ** update
-  */
+    /*
+    ** start
+    */
+    await next();
 
-  // emit('workflow_update', { taskId, action:'COMPLETE_WORKFLOW', speakToUser: "Task completed", status: 'completed' });
+  }
 }
 
 
diff --git a/src/lib/workflow.schema.ts b/src/lib/workflow.schema.ts
index 5659a44..6fb4a67 100644
--- a/src/lib/workflow.schema.ts
+++ b/src/lib/workflow.schema.ts
@@ -33,6 +33,7 @@ export interface Workflow {
 }
 
 export interface RunnerOptions {
+  mode: string;
   originalQuery: string;
   sessionId: string;
   queries: string[];
@@ -42,4 +43,5 @@ export interface RunnerOptions {
   }[];
   browserInstance: any;
   researchFlags: number[];
+  steps: string[];
 }
