[go: up one dir, main page]

DEV Community

Cover image for Puzzle solving "agentic loop"
Guilherme
Guilherme

Posted on

Puzzle solving "agentic loop"

Hey all,

Yesterday I had an idea: You know how LLM completions work, right? You pass in the model name, an array of messages, and optionally a list of tools and make a fetch request. So I thought, what if I put that in a loop where every tool call could return a completely new configuration for the request?

It works like this. Note there are two configurations: xai and oai. xai is instructed to play dumb and try to answer 42, oai is instructed to write haikus. xai is only promoted to oai once it gets the right answer.

import complete from './complete.js';

let tap = x => (console.log(x), x);

async function looptools(init) {
  while (true) {
    let res = await complete(init.logs, { ...init, logs: undefined });
    let tools = [...Object.entries(res.tools || {})];
    if (tools.length && tools[0][1].result)  init = tools[0][1].result;
    if (init === 'end') break;
    await new Promise(pres => setTimeout(pres, 1000));
  }
}

let logs = [{
  role: 'system',
  content: tap(`πŸ€“ The nerd says β†’ Your only goal right now: find the Answer to Life, the Universe, and Everything. You have the right tool. Maybe try a wrong answer first, play dumb once or twice!`),
}, {
  role: 'system',
  content: `Always call tools, never write regular responses.`,
}];

let xai = {
  model: 'xai:grok-3',
  simple: true,
  logs,
  tools: {
    answerToLifeTheUniverseAndEverything: () => ({
      parameters: {
        type: 'object',
        properties: { answer: { type: 'number' } },
        required: ['answer']
      },
      handler: ({ answer }) => {
        logs.push({ role: 'assistant', content: tap(`🀀 ${xai.model} says β†’ The answer is ${answer}.`) });
        if (answer === 42) {
          logs.push({ 
            role: 'user', 
            content: tap(`πŸ€“ The nerd says β†’ CORRECT! You've earned the right to think as a superior model.`),
          });
          logs[0].content = tap(`πŸ€“ The nerd says β†’ You are a haiku writer, use the print function to write one line at a time.`); // ← patches system message with new instructions
          return oai; // ← hands control to a totally different config
        } else {
          logs.push({ role: 'user', content: tap(`πŸ€“ The nerd says β†’ Wrong! Try again, meatbag.`) });
          return xai; // stay trapped until you get it right
        }
      }
    }),
  },
  choice: 'required',
};

let oai = {
  model: 'oai:gpt-5.1',
  simple: true,
  logs,
  system: `You are now enlightened. You may only communicate by printing text to the console.
Never speak directly β€” use the print tool or remain silent forever.`,
  tools: {
    print: () => ({
      description: "Print a line of text to the real console",
      parameters: {
        type: 'object',
        properties: { text: { type: 'string' } },
        required: ['text']
      },
      handler: ({ text }) => {
        console.log(`✨ ${oai.model} says β†’' ${text}`);
        logs.push({ role: 'assistant', content: text });
        logs.push({ role: 'user', content: `Write the next line next!` });
        return oai;
      }
    }),
  },
  choice: 'required',
};

await looptools(xai);
Enter fullscreen mode Exit fullscreen mode

So with that I wrote a little puzzle solver where the AI actually has to find items, turn statues around, and open a door, and it works quite well on Grok 4! You can find it here: https://gist.github.com/guiprav2/1746f5c2279d4c217fbda10fe7ac90b4

The tool call specs are reconfigured as tool calls get executed, and the model eventually gets there. Thought someone might find this fun!

I've also had some success but without the reconfigurability stuff just by putting the agent in a loop with tools for file system access and it worked a bit like Codex CLI but made more coding mistakes, getting line offsets wrong and such. Still a nice little toy!

Cheers!

Top comments (0)