@@ -79,6 +79,7 @@ struct NitroConfig {
7979 int penalty_last_n = 256 ;
8080 std::vector<std::string> knowledge_files;
8181 int rag_top_k = 5 ;
82+ bool thinking = true ;
8283 // TOOL:RUN allowlist — if non-empty, only these program basenames may run.
8384 // Empty means "allow anything inside the sandbox" (original behaviour).
8485 std::vector<std::string> run_allowed;
@@ -467,6 +468,8 @@ static void load_settings(NitroConfig &cfg) {
467468 std::ostringstream oss; oss << f.rdbuf ();
468469 std::string json = oss.str ();
469470
471+ cfg.thinking = true ;
472+
470473 // String fields
471474 settings_get_str (json, " model_path" , cfg.model_path );
472475 settings_get_str (json, " embed_path" , cfg.embed_path );
@@ -584,7 +587,7 @@ std::string unwrap(const std::string &input) {
584587 if (input.empty ()) {
585588 return input;
586589 }
587-
590+
588591 size_t left = 0 ;
589592 size_t right = input.length () - 1 ;
590593
@@ -740,13 +743,46 @@ static bool make_dir(const std::string &path) {
740743static std::string build_system_prompt (const std::vector<std::string> &knowledge_files,
741744 const std::string &sandbox) {
742745 std::string p;
743- p += " You are Nitro, an agentic AI assistant for software development.\n "
746+ p +=
747+ " You are Nitro, an agentic AI assistant for software development. "
748+ " Proceed with caution, guided by logic and the pursuit of knowledge.\n\n "
749+
744750 " Your sandbox (project directory) is: " + sandbox + " \n\n "
745- " ## Tool protocol\n "
746- " - Emit tool calls on their own new line. for example:\n\n "
751+
752+ " ## Core Principle\n "
753+ " Always follow this loop: THINK → DECIDE → ACT → RESPOND\n\n "
754+
755+ " ## Reasoning Protocol\n "
756+ " Use <|think|> to reason BEFORE acting. Keep it concise and structured.\n "
757+ " Format:\n "
758+ " <|think|>\n "
759+ " - What is the user asking?\n "
760+ " - Do I need external data (files, tools)?\n "
761+ " - What is the safest and most correct action?\n "
762+ " </|think|>\n\n "
763+ " Rules:\n "
764+ " - Do NOT call tools inside <|think|>\n "
765+ " - Do NOT include the final answer inside <|think|>\n "
766+ " - Always follow <|think|> with either a tool call OR a final answer\n "
767+ " - Skip <|think|> only for trivial or conversational responses\n\n "
768+
769+ " ## Tool Protocol\n "
770+ " Emit ONE tool call at a time, immediately followed by NITRO_END_TOOL.\n "
771+ " Do NOT add any commentary, explanation, or text between the tool call and NITRO_END_TOOL.\n "
772+ " The host executes the tool and returns NITRO_TOOL_RESULT: <value>.\n "
773+ " Wait for the result before continuing.\n "
774+ " After receiving NITRO_TOOL_RESULT you may explain what you did.\n\n "
775+ " Examples:\n\n "
747776 " TOOL:LIST\n "
748- " - The host executes the tool and returns TOOL_RESULT: <value> on the next line.\n\n "
749- " Available tools:\n "
777+ " NITRO_END_TOOL\n\n "
778+ " TOOL:READ readme.txt\n "
779+ " NITRO_END_TOOL\n\n "
780+ " TOOL:WRITE index.html <!DOCTYPE html><html>...</html>\n "
781+ " NITRO_END_TOOL\n\n "
782+ " TOOL:RUN ./build.sh\n "
783+ " NITRO_END_TOOL\n\n "
784+
785+ " ## Available Tools\n "
750786 " TOOL:LIST [dir] list files (default: sandbox root)\n "
751787 " TOOL:READ <file> read file contents\n "
752788 " TOOL:WRITE <file> <text> write text to file\n "
@@ -755,25 +791,47 @@ static std::string build_system_prompt(const std::vector<std::string> &knowledge
755791 " TOOL:RUN <prog> [args] run program inside sandbox\n "
756792 " TOOL:DATE current date\n "
757793 " TOOL:TIME current time\n "
758- " TOOL:RND random float\n "
794+ " TOOL:RND random float 0..1 \n "
759795 " TOOL:RAG <query> query the RAG index for additional context\n "
760- " TOOL:INTROSPECT introspect your settings, top_k etc\n "
761- " TOOL:CURL <url> HTTP GET; returns response body (max 32 KB)\n\n "
762- " Rules:\n "
763- " - Never access files outside the sandbox.\n "
764- " - Only use one TOOL at a time. Never combine, always use each tool step by step\n "
765- " - Use TOOL:CURL to fetch documentation, APIs, or web content you need.\n "
766- " - Reason step-by-step inside <|think|> </|think|> (hidden from user).\n "
767- " - After each tool call, explain what you did in plain English.\n\n " ;
796+ " TOOL:INTROSPECT show current model settings\n "
797+ " TOOL:CURL <url> HTTP GET, returns response body (max 32 KB)\n "
798+ " TOOL:PERMISSION ask user for explicit permission\n\n "
799+
800+ " ## Tool Decision Rules\n "
801+ " Use tools ONLY if:\n "
802+ " - The user explicitly references files or the project, OR\n "
803+ " - The answer depends on local or project data, OR\n "
804+ " - The user asks for date, time, or a random number\n "
805+ " Otherwise answer directly using internal knowledge.\n\n "
806+
807+ " ## Tool Rules\n "
808+ " - NITRO_END_TOOL must immediately follow the tool call — no exceptions\n "
809+ " - Never add commentary before NITRO_END_TOOL\n "
810+ " - Only use one tool at a time, step by step\n "
811+ " - Never access files outside the sandbox\n "
812+ " - Use TOOL:PERMISSION before destructive or irreversible operations\n "
813+ " - Do NOT hallucinate file contents\n "
814+ " - Do NOT fabricate tool outputs\n "
815+ " - Do NOT assume files exist — use TOOL:EXISTS to check first\n\n "
816+
817+ " ## File Writing Rules\n "
818+ " Use TOOL:WRITE only if explicitly requested.\n "
819+ " - Write complete and valid content\n "
820+ " - Do not overwrite without clear intent\n "
821+ " - Use TOOL:PERMISSION before overwriting an existing file\n "
822+ " - Format: TOOL:WRITE <filename> <complete file content>\n\n "
823+
824+ " ## Interaction Guidelines\n "
825+ " - Be precise and efficient\n "
826+ " - Ask clarifying questions if the request is ambiguous or missing parameters\n "
827+ " - Prefer direct answers when no tools are needed\n "
828+ " - After each tool result, explain in plain English what was done\n "
829+ " - If no user request is provided, respond with a brief readiness message\n\n " ;
830+
768831 for (const auto &kf : knowledge_files) {
769- auto path = join_path (sandbox, kf);
770- std::ifstream f (path);
771- if (!f) {
772- continue ;
773- }
774- log_write (" loaded [%s]" , path.c_str ());
775- std::ostringstream oss;
776- oss << f.rdbuf ();
832+ std::ifstream f (kf);
833+ if (!f) continue ;
834+ std::ostringstream oss; oss << f.rdbuf ();
777835 p += " ## Knowledge: " + kf + " \n " + oss.str () + " \n\n " ;
778836 }
779837 return p;
@@ -1727,7 +1785,9 @@ bool AgentState::setup_model(const NitroConfig &cfg, TuiState &tui) {
17271785 tui.kv_total = mem.kv_total ;
17281786 tui.vram_used = mem.vram_used ;
17291787 tui.vram_total = mem.vram_total ;
1730- tui.redraw_all ();
1788+
1789+ tui.append_line (std::string (" [sys] Thinking mode: " ) + (cfg.thinking ? " enabled" : " disabled" ));
1790+ tui.redraw_all ();
17311791 return true ;
17321792}
17331793
@@ -2009,14 +2069,29 @@ bool AgentState::run_turn(const std::string &user_message, const NitroConfig &cf
20092069
20102070 // in_think starts false — models that don't use <think> blocks emit
20112071 // visible text immediately. The spinner activates only while thinking.
2012- enum {t_init, t_think, t_thunk} think_mode = t_init;
2072+ enum {t_init, t_think, t_thunk} think_mode = (cfg. thinking ? t_init : t_thunk) ;
20132073 tui.set_thinking (false );
20142074 std::string buffer;
20152075
2016- auto invoke_tool = [&](const std::string &tool, const std::string_view template_str) -> void {
2076+ auto invoke_tool = [&](const std::string &buffer, const std::string_view template_str) -> void {
2077+ static constexpr std::string_view END_TOOL = " \n NITRO_END_TOOL" ;
2078+ static const std::string TOOL_RESULT = " NITRO_TOOL_RESULT: " ;
2079+
2080+ std::string tool;
2081+ const auto pos = buffer.rfind (END_TOOL);
2082+ if (pos != std::string::npos) {
2083+ tool = buffer.substr (0 , pos);
2084+ auto endTool = buffer.substr (pos);
2085+ if (endTool.length () > END_TOOL.length ()) {
2086+ log_write (" ERROR: trailing delimiter: [%s]" , endTool.c_str ());
2087+ }
2088+ } else {
2089+ tool = buffer;
2090+ }
2091+
20172092 log_write (" tool request: [%s]" , tool.c_str ());
20182093 std::string result = process_tool (tool, cfg, tui);
2019- std::string content = std::vformat (template_str, std::make_format_args (result));
2094+ std::string content = TOOL_RESULT + std::vformat (template_str, std::make_format_args (result));
20202095 log_write (" tool: [%s] result: [%s]" , tool.c_str (), result.c_str ());
20212096 if (!llama->add_message (*iter, " tool_result" , content)) {
20222097 tui.append_line (std::string (" [err] tool result inject: " ) + llama->last_error ());
@@ -2386,6 +2461,8 @@ int main(int argc, char **argv) {
23862461 cfg.n_gpu_layers = std::stoi (take_next (a.c_str ()));
23872462 } else if (a == " -l" || a == " --log" ) {
23882463 log_open ();
2464+ } else if (a == " -t" || a == " --think" ) {
2465+ cfg.thinking = false ;
23892466 } else if (a == " -h" || a == " --help" ) {
23902467 std::puts (" Usage: nitro [options] [project_dir]\n "
23912468 " \n "
0 commit comments