{"course":{"slug":"agents-and-automation","title":"Agents and Automation","url":"https://addaly.com/learn/agents-and-automation","level":"intermediate","track":"build"},"lesson":{"slug":"the-message-list","title":"What the model actually sees each turn","url":"https://addaly.com/learn/agents-and-automation/the-message-list","position":12,"of":72,"minutes":8,"answer":"An agent's entire state is a list of messages you own and can rewrite; there is no session and no hidden memory between calls.","keyIdea":"An agent's entire state is a list of messages you own and can rewrite; there is no session and no hidden memory between calls."},"body":{"format":"markdown","text":"## There is no session\n\nThe most common wrong mental model is that the model \"remembers\" the conversation and you are sending it updates. It does not. Every API call is stateless, and your harness re-sends the entire history each time.\n\nHere is what step three of a small agent actually looks like on the wire. Details differ slightly between providers; the shape does not.\n\n```json\n[\n  { \"role\": \"system\",\n    \"content\": \"You are an operations assistant. Use tools; never guess an order id.\" },\n\n  { \"role\": \"user\",\n    \"content\": \"Refund the duplicate charge on ORD-48213.\" },\n\n> Figure: What your harness sends on every single step. Steps: 1. system (The instruction block. Re-sent unchanged on every call, and never dropped.); 2. user (The task. Written once and read on every turn after it.); 3. assistant (The model's own past decisions, read back as ordinary text. Delete one and it never called that tool.); 4. tool (Each result, paired to the id of the call that asked for it.); 5. the whole list again (Nothing is remembered between calls. This array is the entire state, and you own it.). There is no session. Drop a tool result while trimming and leave its call behind, and the provider returns a 400 rather than degrading gracefully, so trim in whole pairs.\n\n  { \"role\": \"assistant\",\n    \"tool_calls\": [\n      { \"id\": \"call_1\",\n        \"function\": { \"name\": \"get_order\",\n                      \"arguments\": \"{\\\"order_id\\\": \\\"ORD-48213\\\"}\" } }\n    ] },\n\n  { \"role\": \"tool\",\n    \"tool_call_id\": \"call_1\",\n    \"content\": \"{\\\"status\\\":\\\"shipped\\\",\\\"amount\\\":2499,\\\"refunded\\\":false}\" }\n]\n```\n\nFour messages, and the model reads all four before deciding anything. Three things in there are worth noticing.\n\n**The pairing is load-bearing.** Every `tool_call_id` must match a preceding tool call. Drop one while trimming history and the provider returns a 400, not a graceful degradation. Trim in whole pairs.\n\n**The model reads its own past decisions as text.** That assistant message is not a memory; it is a transcript entry. If you delete it, the model has no idea it ever called `get_order`.\n\n**Some providers hand back reasoning blocks** and expect them returned in the next request. If your harness quietly strips them, the model loses its own train of thought between steps, and the symptom is an agent that seems to restart its thinking every turn.\n\n## Consequence one: history is editable, and that is your best tool\n\nSince you own the list, you can rewrite it. Legitimately and routinely:\n\n- **Drop a failed detour.** Three steps of a dead end, replaced by one line: *tried the invoices API, it has no refund endpoint*.\n- **Replace a huge result with a summary.** The 30,000-token document becomes a 200-token abstract plus a file path.\n- **Inject a note as a tool result.** *The user has been waiting 40 seconds; prefer finishing over further searching.*\n- **Re-state the goal near the end**, where it will actually be read.\n\nThe model cannot tell that any of this happened. This is the single most useful power in a harness, and most beginners never use it because they think of the transcript as a record rather than as working memory they curate.\n\n## Consequence two: the transcript is the debug log\n\nIf you cannot print the exact array sent at step seven, you cannot debug the agent: you are guessing about a program whose input you never looked at. Log it. Every step. Redact secrets, keep the rest.\n\nAlmost every \"the model is being stupid\" bug turns out, on inspection, to be a tool result that was empty, truncated, or an error string nobody read.\n\n## Consequence three: position matters\n\nLong contexts are not read evenly. The well-replicated finding from *Lost in the Middle* (Liu and colleagues, 2023) is that material placed in the middle of a long context is used less reliably than material at the start or end, and the effect gets worse as the context grows.\n\nPractical rule: the constraint you most need obeyed goes **near the end**, not buried in a system prompt written six weeks ago.\n\n## The thing that unsettles people\n\nThe model cannot distinguish a tool result you fetched from one you invented. It is the same text in the same slot.\n\nThat cuts two ways. It is why a fabricated tool result is a perfectly good testing technique: you will use it in module three to test a harness without touching a real API. And it is why text arriving from the outside world inside a tool result is dangerous, which is module four."},"check":{"question":"A harness trims old messages to control cost by deleting the ten oldest entries whenever the transcript exceeds 40,000 tokens. After the trim, the provider starts returning 400 errors on the next call. What is the most likely cause?","options":[{"text":"The system prompt was deleted, and requests without one are rejected.","correct":false,"tempting":"Plausible in shape but wrong in fact: a missing system message is legal. It changes behaviour rather than causing an error."},{"text":"The trim exceeded the model's context window and the request is now too long.","correct":false,"tempting":"Inverts the situation. Trimming makes the request shorter, and an over-length request has a different error message."},{"text":"The trim orphaned a tool result whose matching tool call was among the deleted messages, so the pairing no longer validates.","correct":true},{"text":"Deleting messages invalidates the prompt cache, and cache misses are returned as errors.","correct":false,"tempting":"Confuses a cost consequence with an error. A cache miss silently costs more; it never fails the request."}]},"prev":{"slug":"structured-output-and-schemas","title":"A schema constrains the shape, not the truth","url":"https://addaly.com/learn/agents-and-automation/structured-output-and-schemas"},"next":{"slug":"system-prompts-for-agents","title":"The instruction block that has to survive forty turns","url":"https://addaly.com/learn/agents-and-automation/system-prompts-for-agents"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'What the model actually sees each turn', in Agents and Automation. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/agents-and-automation/the-message-list (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"What the model actually sees each turn","courseTitle":"Agents and Automation","module":{"index":2,"slug":"how-the-loop-runs","title":"The machinery: transcript, tools, loop"},"answer":null,"keyIdea":"An agent’s entire state is a list of messages you own and can rewrite; there is no session and no hidden memory between calls.","hero":{"kind":"figure","figure":{"t":"figure","label":"Figure","figure":{"type":"flow","steps":[{"label":"system","detail":"The instruction block. Re-sent unchanged on every call, and never dropped."},{"label":"user","detail":"The task. Written once and read on every turn after it."},{"label":"assistant","detail":"The model’s own past decisions, read back as ordinary text. Delete one and it never called that tool."},{"label":"tool","detail":"Each result, paired to the id of the call that asked for it."},{"label":"the whole list again","detail":"Nothing is remembered between calls. This array is the entire state, and you own it."}],"title":"What your harness sends on every single step","caption":"There is no session. Drop a tool result while trimming and leave its call behind, and the provider returns a 400 rather than degrading gracefully, so trim in whole pairs."},"alt":"Figure: What your harness sends on every single step. Steps: 1. system (The instruction block. Re-sent unchanged on every call, and never dropped.); 2. user (The task. Written once and read on every turn after it.); 3. assistant (The model’s own past decisions, read back as ordinary text. Delete one and it never called that tool.); 4. tool (Each result, paired to the id of the call that asked for it.); 5. the whole list again (Nothing is remembered between calls. This array is the entire state, and you own it.). There is no session. Drop a tool result while trimming and leave its call behind, and the provider returns a 400 rather than degrading gracefully, so trim in whole pairs."}},"blocks":[{"t":"h2","id":"there-is-no-session","runs":[{"text":"There is no session"}]},{"t":"p","runs":[{"text":"The most common wrong mental model is that the model “remembers” the conversation and you are sending it updates. It does not. Every API call is stateless, and your harness re-sends the entire history each time."}]},{"t":"p","runs":[{"text":"Here is what step three of a small agent actually looks like on the wire. Details differ slightly between providers; the shape does not."}]},{"t":"code","lang":"json","label":"JSON","text":"[\n  { \"role\": \"system\",\n    \"content\": \"You are an operations assistant. Use tools; never guess an order id.\" },\n\n  { \"role\": \"user\",\n    \"content\": \"Refund the duplicate charge on ORD-48213.\" },\n\n  { \"role\": \"assistant\",\n    \"tool_calls\": [\n      { \"id\": \"call_1\",\n        \"function\": { \"name\": \"get_order\",\n                      \"arguments\": \"{\\\"order_id\\\": \\\"ORD-48213\\\"}\" } }\n    ] },\n\n  { \"role\": \"tool\",\n    \"tool_call_id\": \"call_1\",\n    \"content\": \"{\\\"status\\\":\\\"shipped\\\",\\\"amount\\\":2499,\\\"refunded\\\":false}\" }\n]"},{"t":"p","runs":[{"text":"Four messages, and the model reads all four before deciding anything. Three things in there are worth noticing."}]},{"t":"p","runs":[{"text":"The pairing is load-bearing.","b":true},{"text":" Every "},{"text":"tool_call_id","code":true},{"text":" must match a preceding tool call. Drop one while trimming history and the provider returns a 400, not a graceful degradation. Trim in whole pairs."}]},{"t":"p","runs":[{"text":"The model reads its own past decisions as text.","b":true},{"text":" That assistant message is not a memory; it is a transcript entry. If you delete it, the model has no idea it ever called "},{"text":"get_order","code":true},{"text":"."}]},{"t":"p","runs":[{"text":"Some providers hand back reasoning blocks","b":true},{"text":" and expect them returned in the next request. If your harness quietly strips them, the model loses its own train of thought between steps, and the symptom is an agent that seems to restart its thinking every turn."}]},{"t":"h2","id":"consequence-one-history-is-editable-and-that-is-your-best-to","runs":[{"text":"Consequence one: history is editable, and that is your best tool"}]},{"t":"p","runs":[{"text":"Since you own the list, you can rewrite it. Legitimately and routinely:"}]},{"t":"ul","items":[[{"text":"Drop a failed detour.","b":true},{"text":" Three steps of a dead end, replaced by one line: "},{"text":"tried the invoices API, it has no refund endpoint","i":true},{"text":"."}],[{"text":"Replace a huge result with a summary.","b":true},{"text":" The 30,000-token document becomes a 200-token abstract plus a file path."}],[{"text":"Inject a note as a tool result.","b":true},{"text":" "},{"text":"The user has been waiting 40 seconds; prefer finishing over further searching.","i":true}],[{"text":"Re-state the goal near the end","b":true},{"text":", where it will actually be read."}]]},{"t":"p","runs":[{"text":"The model cannot tell that any of this happened. This is the single most useful power in a harness, and most beginners never use it because they think of the transcript as a record rather than as working memory they curate."}]},{"t":"h2","id":"consequence-two-the-transcript-is-the-debug-log","runs":[{"text":"Consequence two: the transcript is the debug log"}]},{"t":"p","runs":[{"text":"If you cannot print the exact array sent at step seven, you cannot debug the agent: you are guessing about a program whose input you never looked at. Log it. Every step. Redact secrets, keep the rest."}]},{"t":"p","runs":[{"text":"Almost every “the model is being stupid” bug turns out, on inspection, to be a tool result that was empty, truncated, or an error string nobody read."}]},{"t":"h2","id":"consequence-three-position-matters","runs":[{"text":"Consequence three: position matters"}]},{"t":"p","runs":[{"text":"Long contexts are not read evenly. The well-replicated finding from "},{"text":"Lost in the Middle","i":true},{"text":" (Liu and colleagues, 2023) is that material placed in the middle of a long context is used less reliably than material at the start or end, and the effect gets worse as the context grows."}]},{"t":"p","runs":[{"text":"Practical rule: the constraint you most need obeyed goes "},{"text":"near the end","b":true},{"text":", not buried in a system prompt written six weeks ago."}]},{"t":"h2","id":"the-thing-that-unsettles-people","runs":[{"text":"The thing that unsettles people"}]},{"t":"p","runs":[{"text":"The model cannot distinguish a tool result you fetched from one you invented. It is the same text in the same slot."}]},{"t":"p","runs":[{"text":"That cuts two ways. It is why a fabricated tool result is a perfectly good testing technique: you will use it in module three to test a harness without touching a real API. And it is why text arriving from the outside world inside a tool result is dangerous, which is module four."}]}],"check":{"question":[{"text":"A harness trims old messages to control cost by deleting the ten oldest entries whenever the transcript exceeds 40,000 tokens. After the trim, the provider starts returning 400 errors on the next call. What is the most likely cause?"}],"options":[{"text":[{"text":"The system prompt was deleted, and requests without one are rejected."}],"correct":false,"tempting":[{"text":"Plausible in shape but wrong in fact: a missing system message is legal. It changes behaviour rather than causing an error."}]},{"text":[{"text":"The trim exceeded the model’s context window and the request is now too long."}],"correct":false,"tempting":[{"text":"Inverts the situation. Trimming makes the request shorter, and an over-length request has a different error message."}]},{"text":[{"text":"The trim orphaned a tool result whose matching tool call was among the deleted messages, so the pairing no longer validates."}],"correct":true,"tempting":null},{"text":[{"text":"Deleting messages invalidates the prompt cache, and cache misses are returned as errors."}],"correct":false,"tempting":[{"text":"Confuses a cost consequence with an error. A cache miss silently costs more; it never fails the request."}]}],"order":[1,0,3,2],"why":null},"prev":{"slug":"structured-output-and-schemas","title":"A schema constrains the shape, not the truth","minutes":9},"next":{"slug":"system-prompts-for-agents","title":"The instruction block that has to survive forty turns","minutes":9}}}