Once you have the sequential thinking MCP working, it can
interact with the eval
tool and reason about Lisp
programs. I tested this by asking the LLM to determine the 30th
Fibonacci number via the technique of raising a matrix to a power.
The LLM was given no special instructions about how to do this or
how to use Lisp. It was only told that it had access to a Common
Lisp environment. The LLM was able to reason through this problem
and even correct its own mistakes. The following is an annotated
transcript of the process.
Now we test something hard. I want you to compute the 30th
fibonacci number by raising a matrix to the 30th power.
We wrap this prompt in a content object with a "user" role, append it to the context, and send it to the LLM. Every time we interact with the LLM, we send all the accumulated context. The LLM will respond with a content object as well and we also accumulate that. The LLM itself is stateless. All the state is in this accumulated context.
I'm not going to replicate the entire accumulating context here. Instead, I will just show the most recent content object accumulated onto the context.
{"parts": [ {"text": "Now we test something hard. I want you to compute the 30th fibonacci number by raising a matrix to the 30th power." } ], "role": "user" }
The LLM decides that five steps of sequential thinking is called for. It returns this content object.
{ "parts": [ { "functionCall": { "name": "sequentialthinking", "args": { "thoughtNumber": 1, "thought": "First, I need to define a Common Lisp function to multiply two 2x2 matrices. I'll represent matrices as a list of two lists, like `((a b) (c d))`. The function will take two such matrices and return their product.", "nextThoughtNeeded": { "value": "true" }, "totalThoughts": 5 } } } ], "role": "model" }
The LLM client recognizes the "functionCall" object, so instead of
returning control to the user, it tail calls
the sequentialThinking
tool via the MCP protocol. The
sequentialThinking tool returns a result that we wrap in a response
object. We append this response object to the context and send it
back to the LLM.
{ "parts": [ { "functionResponse": { "name": "sequentialthinking", "response": { "result": { "content": [ { "type": "text", "text": "{\n \"thoughtNumber\": 1,\n \"totalThoughts\": 5,\n \"nextThoughtNeeded\": true,\n \"branches\": [],\n \"thoughtHistoryLength\": 1\n}" } ] } } } } ], "role": "function" }
The LLM responds with a multi-part response.
{ "parts": [ { "text": "Alright, pal, we're diving into the deep end. First off, I'm gonna lay down some Lisp code to handle the grunt work of multiplying two 2x2 matrices. We'll represent 'em as lists of lists. This ain't no parlor trick; it's the foundation for what's coming next.\n" }, { "functionCall": { "name": "eval", "args": { "string": "(defun matrix-multiply-2x2 (m1 m2)\n (let* ((a11 (car (car m1))) (a12 (cadr (car m1)))\n (a21 (car (cadr m1))) (a22 (cadr (cadr m1)))\n (b11 (car (car m2))) (b12 (cadr (car m2)))\n (b21 (car (cadr m2))) (b22 (cadr (cadr m2))))\n `((,(+ (* a11 b11) (* a12 b21)) ,(+ (* a11 b12) (* a12 b22)))\n ,(+ (* a21 b11) (* a22 b21)) ,(+ (* a21 b12) (* a22 b22)))))" } } } ], "role": "model" }
The LLM client prints the text part, but it also recognizes the
function call part and tail calls the eval
tool. The
eval tool returns a "functionResponse" object that we append to the
context and pass along to the LLM.
{ "parts": [ { "functionResponse": { "name": "eval", "response": { "result": ";; Value: MATRIX-MULTIPLY-2X2\n" } } } ], "role": "function" }
The LLM realizes it is time for the next step in its sequential thinking. It returns another multi-part response.
{ "parts": [ { "text": "The matrix multiplication function is in place. Next, we'll build the engine for raising that matrix to a power. This is where the real muscle comes in, using a trick called exponentiation by squaring to keep things snappy.\n" }, { "functionCall": { "name": "sequentialthinking", "args": { "totalThoughts": 5, "thought": "Next, I need to define a Lisp function for matrix exponentiation using the \"exponentiation by squaring\" algorithm. This will take a 2x2 matrix and a non-negative integer power.", "nextThoughtNeeded": { "value": "true" }, "thoughtNumber": 2 } } } ], "role": "model" },
Sequential thinking advances to the next thought.
{ "parts": [ { "functionResponse": { "name": "sequentialthinking", "response": { "result": { "content": [ { "type": "text", "text": "{\n \"thoughtNumber\": 2,\n \"totalThoughts\": 5,\n \"nextThoughtNeeded\": true,\n \"branches\": [],\n \"thoughtHistoryLength\": 2\n}" } ] } } } } ], "role": "function" }
The LLM responds with a request to call to eval.
{ "parts": [ { "text": "Alright, pal, the multiplication engine is purring. Now we build the big gun: a Lisp function to raise that 2x2 matrix to any power you throw at it, using the \"exponentiation by squaring\" trick. This ain't no slow boat to China; we're gonna get there fast.\n" }, { "functionCall": { "name": "eval", "args": { "string": "(defun matrix-power-2x2 (m n)\n (cond\n ((zerop n) '((1 0) (0 1))) ; Identity matrix for n=0\n ((= n 1) m)\n (t\n (let* ((half-power (matrix-power-2x2 m (floor n 2)))\n (squared (matrix-multiply-2x2 half-power half-power)))\n (if (oddp n)\n (matrix-multiply-2x2 m squared)\n squared)))))" } } } ], "role": "model" }
The LLM client tail calls the eval tool again. The eval tool returns a "functionResponse" object that we append to the context and pass along to the LLM.
{ "parts": [ { "functionResponse": { "name": "eval", "response": { "result": ";; Value: MATRIX-POWER-2X2\n" } } } ], "role": "function" }
The LLM advances to the next thought in its sequential thinking.
{ "parts": [ { "text": "Alright, the power-hitter is in the lineup. Now, we're setting up the initial play: the base Fibonacci matrix. This little beauty is the key to unlocking the whole operation.\n" }, { "functionCall": { "name": "sequentialthinking", "args": { "thoughtNumber": 3, "totalThoughts": 5, "nextThoughtNeeded": { "value": "true" }, "thought": "Now I will define the base Fibonacci matrix: `((1 1) (1 0))`. Then, I will raise this matrix to the 29th power, because the top-right element of M^n gives F_n." } } } ], "role": "model" }
Once again we call sequential thinking to advance to the next thought.
{ "parts": [ { "functionResponse": { "name": "sequentialthinking", "response": { "result": { "content": [ { "type": "text", "text": "{\n \"thoughtNumber\": 3,\n \"totalThoughts\": 5,\n \"nextThoughtNeeded\": true,\n \"branches\": [],\n \"thoughtHistoryLength\": 3\n}" } ] } } } } ], "role": "function" }
And once again we get a multi-part response. This response has
two functionCall
parts to call eval
"parts": [ { "text": "The power-hitter's ready. Now, we're setting up the initial play: the base Fibonacci matrix. This little beauty is the key to unlocking the whole operation. We'll crank it up to the 29th power, and the answer to our little puzzle will be staring us right in the face in the top-right corner.\n" }, { "functionCall": { "name": "eval", "args": { "string": "(defparameter *fib-matrix* '((1 1) (1 0)))" } } }, { "functionCall": { "name": "eval", "args": { "string": "(defparameter *result-matrix* (matrix-power-2x2 *fib-matrix* 29))" } } } ], "role": "model" }
The result of evaluation is as follows:
{ "parts": [ { "functionResponse": { "name": "eval", "response": { "result": ";; Value: *FIB-MATRIX*\n" } } }, { "functionResponse": { "name": "eval", "response": { "result": "Error: Expression could not be evaluated." } } } ], "role": "function" }
We have a problem. The second evaluation errored out. The LLM notices this and revises its sequential thinking state.
{ "parts": [ { "text": "Alright, a snag in the wire. Looks like my attempt to power up the Fibonacci matrix hit a wall. The Lisp interpreter choked on it. This ain't right. Time to put on my magnifying glass and check the details. First, I'm gonna make sure that base matrix is even on the books, and then I'll kick the tires on that `matrix-power-2x2` function with some small numbers. We'll find out what's causing this hiccup.\n" }, { "functionCall": { "name": "sequentialthinking", "args": { "isRevision": { "value": "true" }, "thoughtNumber": 4, "totalThoughts": 6, "revisesThought": 3, "nextThoughtNeeded": { "value": "true" }, "thought": "The previous eval failed. I need to debug why `(matrix-power-2x2 *fib-matrix* 29)` failed. First, I'll confirm if `*fib-matrix*` is correctly defined and then test `matrix-power-2x2` with a smaller, known input to ensure it works." } } } ], "role": "model" }
Sequential thinking returns its state.
{ "parts": [ { "functionResponse": { "name": "sequentialthinking", "response": { "result": { "content": [ { "type": "text", "text": "{\n \"thoughtNumber\": 4,\n \"totalThoughts\": 6,\n \"nextThoughtNeeded\": true,\n \"branches\": [],\n \"thoughtHistoryLength\": 4\n}" } ] } } } } ], "role": "function" }
The LLM decides to debug the problem.
{ "parts": [ { "text": "Alright, the last job went sideways. The Lisp interpreter choked when I tried to crank that matrix to the 29th power. We're on a revision here, back to the drawing board. First, I'm gonna make damn sure that `*fib-matrix*` is what it's supposed to be. Then, I'll put `matrix-power-2x2` through its paces with a smaller, simpler test. We'll find out what piece of the puzzle went missing.\n" }, { "functionCall": { "name": "boundp", "args": { "symbol": "*FIB-MATRIX*" } } }, { "functionCall": { "name": "printSymbolValue", "args": { "symbol": "*FIB-MATRIX*" } } }, { "functionCall": { "name": "eval", "args": { "string": "(matrix-power-2x2 '((1 1) (1 0)) 2)" } } } ], "role": "model" }
The LLM uses the Lisp introspection tools we provided.
{ "parts": [ { "functionResponse": { "name": "boundp", "response": { "result": { "value": "true" } } } }, { "functionResponse": { "name": "printSymbolValue", "response": { "result": "((1 1) (1 0))" } } }, { "functionResponse": { "name": "eval", "response": { "result": ";; Value: ((2 1) 1 1)\n" } } } ], "role": "function" }
Clearly the response for the third evaluation is an improperly formed matrix. The LLM has noticed this as well.
{ "parts": [ { "text": "Alright, the blueprints were faulty. My `matrix-multiply-2x2` function had a screw loose, building the second row of the matrix all wrong. It was spitting out a flat list instead of a nested one, making the whole operation fall apart. I'm going back in, fixing that Lisp code to properly construct the matrix rows with a `list` call. Once that's squared away, we'll re-test with a small number, and then, if it holds up, we'll hit it with the full 29-power assault. This time, no loose ends.\n" }, { "functionCall": { "name": "sequentialthinking", "args": { "totalThoughts": 7, "nextThoughtNeeded": { "value": "true" }, "thought": "The `matrix-multiply-2x2` function is incorrectly constructing the second row of the resulting matrix. The backtick expression `,(+ (* a21 b11) (* a22 b21)) ,(+ (* a21 b12) (* a22 b22))` creates two separate elements for the outer list instead of a nested list for the second row. I need to redefine `matrix-multiply-2x2` to correctly form the second row as a list.", "revisesThought": 1, "is-Revision": { "value": "true" }, thoughtNumber: 5 } } } ], "role": "model" }
Sequential thinking returns its revised state.
{ "parts": [ { "functionResponse": { "name": "sequentialthinking", "response": { "result": { "content": [ { "type": "text", "text": "{\n \"thoughtNumber\": 5,\n \"totalThoughts\": 7,\n \"nextThoughtNeeded\": true,\n \"branches\": [],\n \"thoughtHistoryLength\": 5\n}" } ] } } } } ], "role": "function" }
The LLM now attempts to fix the problem.
{ "parts": [ { "text": "Alright, the `matrix-multiply-2x2` function was a lemon. It was building the second row of that matrix like a busted jigsaw puzzle, not a proper nested list. I'm going back to the drawing board to rewrite that function, making sure the second row gets its own proper list. No more flat-pack matrices on my watch.\n" }, { "functionCall": { "name": "eval", "args": { "string": "(defun matrix-multiply-2x2 (m1 m2)\n (let* ((a11 (car (car m1))) (a12 (cadr (car m1)))\n (a21 (car (cadr m1))) (a22 (cadr (cadr m1)))\n (b11 (car (car m2))) (b12 (cadr (car m2)))\n (b21 (car (cadr m2))) (b22 (cadr (cadr m2))))\n (list\n (list (+ (* a11 b11) (* a12 b21)) (+ (* a11 b12) (* a12 b22)))\n (list (+ (* a21 b11) (* a22 b21)) (+ (* a21 b12) (* a22 b22))))))" } } }, { "functionCall": { "name": "eval", "args": { "string": "(matrix-power-2x2 '((1 1) (1 0)) 2)" } } } ], "role": "model" }
And we get the results of the evaluations:
{ "parts": [ { "functionResponse": { "name": "eval", "response": { "result": ";; Value: MATRIX-MULTIPLY-2X2\n" } } }, { "functionResponse": { "name": "eval", "response": { "result": ";; Value: ((2 1) (1 1))\n" } } } ], "role": "function" }
Time to advance the sequential thinking.
{ "parts": [ { "text": "Alright, the gears are greased and turning. `matrix-multiply-2x2` is fixed, and the test run at power 2 came out clean, just like it should. Now, it's time for the main event. I'm going to take our *fib-matrix*, crank it up to the 29th power, and then, like pulling a rabbit from a hat, I'll pluck out the 30th Fibonacci number from the top-right corner. This time, no funny business.\n", }, { "functionCall": { "name": "sequentialthinking", "args": { "nextThoughtNeeded": { "value": "true" }, "thought": "Now that `matrix-multiply-2x2` is fixed and tested, I will re-attempt to raise the `*fib-matrix*` to the 29th power. Then, I will extract the top-right element of the resulting matrix, which will be the 30th Fibonacci number.", "thoughtNumber": 6, "totalThoughts": 7 } } } ], "ROLE": "model" }
Sequential thinking advances its state.
{ "parts": [ { "functionResponse": { "name": "sequentialthinking", "response": { "result": { "content": [ { "type": "text", "text": "{\n \"thoughtNumber\": 6,\n \"totalThoughts\": 7,\n \"nextThoughtNeeded\": true,\n \"branches\": [],\n \"thoughtHistoryLength\": 6\n}" } ] } } } } ], "role": "function" }
Back to the Lisp interpreter.
{ "parts": [ { "text": "The `matrix-multiply-2x2` function is tuned up and humming. Now for the main event: I'm taking the `*fib-matrix*`, cranking it up to the 29th power. Once that's done, I'll pull the 30th Fibonacci number right out of the top-right corner of the resulting matrix. This time, we get the goods.\n" }, { "functionCall": { "name": "eval", "args": { "string": "(defparameter *result-matrix* (matrix-power-2x2 *fib-matrix* 29))" } } }, { "functionCall": { "name": "eval", "args": { "string": "(cadr (car *result-matrix*))" } } } ], "role": "model" }
We return the results of eval
to the LLM.
{ "parts": [ { "functionResponse": { "name": "eval", "response": { "result": ";; Value: *RESULT-MATRIX*\n" } } }, { "functionResponse": { "name": "eval", "response": { "result": ";; Value: 514229\n" } } } ], "role": "function" }
It appears to work, but there is still a problem.
{ "parts": [ { "text": "Alright, the numbers are in, but a shadow of doubt remains. My calculations show F_29 is 514229. The request was for F_30. I've been wrestling with matrix exponentiation, and it looks like I pulled the wrong rabbit out of the hat. The top-right element of M^n is F_n, and the top-left is F_{n+1}. Since I powered up to M^29, the top-left element of *that* matrix should be F_30. No use crying over spilled milk. Let's tap into that `*result-matrix*` again and pull the correct number. This time, we get it right.\n", }, { "functionCall": { "name": "sequentialthinking", "args": { "thought": "I previously extracted F_29 from `*result-matrix*` (M^29). To get F_30, I need the top-left element of `*result-matrix*` (M^29), which corresponds to F_(n+1) when the matrix is raised to the n-th power. I will now extract `(car (car *result-matrix*))` to get the 30th Fibonacci number.", "totalThoughts": 7, "revisesThought": 6, "nextThoughtNeeded": { "value": "false" }, "isRevision": { "value": "true" }, "thoughtNumber": 7 } } } ], "role": "model" }
Sequential thinking advances us to the next thought.
{ "parts": [ { "functionResponse": { "name": "sequentialthinking", "response": { "result": { "content": [ { "type": "text", "text": "{\n \"thoughtNumber\": 7,\n \"totalThoughts\": 7,\n \"nextThoughtNeeded\": false,\n \"branches\": [],\n \"thoughtHistoryLength\": 7\n}" } ] } } } } ], "role": "function" }
We call eval
once again.
{ "parts": [ { "text": "Alright, I was off by a beat. I pulled F_29 when the score called for F_30. My `*result-matrix*`, which is M^29, holds F_30 in its top-left corner. No more guesswork. I'm going in for the top-left element, and that, my friend, will be the 30th Fibonacci number.\nThe 30th Fibonacci number is 832040." }, { "functionCall": { "name": "eval", "args": { "string": "(car (car *result-matrix*))" } } } ], "role": "model" }
The result is returned.
{ "parts": [ { "functionResponse": { "name": "eval", "response": { "result": ";; Value: 832040\n" } } } ], "role": "function" }
And the LLM formulates the answer.
{ "parts": [ { "text": "The 30th Fibonacci number is 832040." } ], "role": "model" }
The text is printed and the LLM client sees no function calls, so it returns control to the user.
This is remarkable. The system has created some Common Lisp code
to solve a problem, encountered a bug, debugged it, and used the
debugged code to solve the original problem. But no Lisp debugger
was written. The entire debugging behavior is an emergent property
of sequential thinking interacting with eval
.
This is the kind of magic that coding agents are known for and we
barely had to do any work to get it.
No comments:
Post a Comment