{"course":{"slug":"building-apps-with-ai","title":"Building Apps with AI","url":"https://addaly.com/learn/building-apps-with-ai","level":"beginner","track":"build"},"lesson":{"slug":"the-prompt-is-a-file-not-a-string","title":"The prompt is a file, not a string","url":"https://addaly.com/learn/building-apps-with-ai/the-prompt-is-a-file-not-a-string","position":60,"of":87,"minutes":7,"answer":"A prompt is code that changes behaviour, so it lives in a versioned file, is stamped into every log line it produced, and is changed on a branch like anything else.","keyIdea":"A prompt is code that changes behaviour, so it lives in a versioned file, is stamped into every log line it produced, and is changed on a branch like anything else."},"body":{"format":"markdown","text":"## Where the prompt ends up\n\nIn the generated route, the system prompt is a string literal three lines above the fetch call. It gets edited in place, by you and by the agent, and nobody can say afterwards which version produced which output. When the feature degrades (and it will, because somebody tightens a sentence to fix one case and breaks three others) there is no record of what the prompt was on the day it worked.\n\nA prompt is a piece of the program that changes the program's behaviour. Treat it like one.\n\n## One file per prompt\n\n```text\nprompts/\n  summarise.md\n  categorise-expense.md\n  reply-suggestion.md\n```\n\nPlain markdown, read at startup or imported as a string. The file is in git, so every change has a commit, a message and a diff; `git log -p prompts/summarise.md` is the history of the feature's behaviour, and the bisecting technique from the breaking block applies to prompts as well as to code.\n\nKeep placeholders explicit and visible:\n\n```markdown\nYou summarise notes for a small business owner.\nWrite at most {{max_sentences}} sentences.\nUse the same language as the note.\nDo not add information that is not in the note.\n```\n\nFill them with a small template function rather than string concatenation scattered through the route. The note itself does not go into the template; it goes in the user message, for reasons the next lesson makes concrete.\n\n## Stamp the version into the log\n\nEvery call should log which prompt produced it. A short hash of the file content, or the git commit of the prompts folder, stored beside the input, the output and the token count:\n\n```typescript\nlog.info({ feature: \"summarise\", promptHash, model, inputTokens, outputTokens, ms });\n```\n\nThat one line is what makes \"it got worse on Thursday\" answerable. Without it, the log shows outputs and no way to group them by cause. The evaluation course covers what to do with such logs at scale; here the point is that the line exists.\n\n## Changing a prompt is a change\n\nEdit it on a branch. Run the fixed set of inputs (the later lesson on whether the feature is good sets that up) against the old and new versions, side by side. Merge when the new one is better on the set, not when it fixed the one example that prompted the edit. Prompts drift towards handling the last complaint at the expense of the general case, and the fixed set is the only thing that notices.\n\nKeep the file short enough to read in one screen. A prompt that has grown to two pages of rules contradicts itself somewhere, and the model resolves the contradiction differently on different days. When a rule is added, look for one that can be removed.\n\n## What the model does with the file\n\nThe system prompt is text the model reads before the user's message. It is not a configuration the model enforces. \"Never mention competitors\" is a request the model will honour most of the time, and a well-phrased user message can talk it out of it. The prompting course covers how to write these well; the app builder's version is one sentence: rules in the prompt shape behaviour, and anything that must be true is checked in code afterwards, not requested in prose.\n\n## Secrets do not go in the prompt\n\nA system prompt is delivered to the model, and models repeat their system prompts when asked nicely enough. Nothing in the file should be something you would mind a user reading: no keys, no internal URLs, no \"the discount code for staff is\". Assume the file is public and write it so that being public costs nothing.\n\n## The agent and the prompt\n\nAn agent asked to \"make the summaries better\" will edit the prompt, and it will do so with the same confidence it edits code, adding rules for the case at hand. Point it at the fixed-set script and ask for a before-and-after, the same way you would ask for a passing test. A prompt change with no measurement is a guess, whoever made it."},"check":{"question":"Summaries got noticeably worse last Thursday. The prompt is an inline string in the route file, and three people have edited it since. How do you find what changed?","options":[{"text":"Ask the model to compare its recent outputs with the ones from before Thursday and explain in its own words what it is doing differently now and why the quality dropped.","correct":false,"tempting":"The model has no memory of previous calls and no access to the old prompt; it will produce a plausible story with no evidence behind it."},{"text":"Check the provider's status page for an incident on Thursday and wait for it to clear.","correct":false,"tempting":"A status incident causes errors and slowness, not a persistent change in quality that survives the weekend."},{"text":"Switch to a larger model, since the degradation is most likely the provider changing something on their side.","correct":false,"tempting":"Possible, but three known edits are the obvious suspects and cost nothing to check; changing the model adds a fourth variable."},{"text":"Run git log -p on the route file to see each edit to the string, and re-run last week's saved inputs against each version to find the one where quality dropped.","correct":true}]},"prev":{"slug":"the-first-call-from-your-own-server","title":"The first call from your own server","url":"https://addaly.com/learn/building-apps-with-ai/the-first-call-from-your-own-server"},"next":{"slug":"user-text-inside-a-prompt-is-untrusted","title":"User text inside a prompt is untrusted","url":"https://addaly.com/learn/building-apps-with-ai/user-text-inside-a-prompt-is-untrusted"},"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) 'The prompt is a file, not a string', in Building Apps with AI. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/building-apps-with-ai/the-prompt-is-a-file-not-a-string (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":"The prompt is a file, not a string","courseTitle":"Building Apps with AI","module":{"index":7,"slug":"a-model-inside-your-app","title":"Putting a model inside your own app"},"answer":null,"keyIdea":"A prompt is code that changes behaviour, so it lives in a versioned file, is stamped into every log line it produced, and is changed on a branch like anything else.","hero":{"kind":"plate","image":null,"alt":null,"caption":null},"blocks":[{"t":"h2","id":"where-the-prompt-ends-up","runs":[{"text":"Where the prompt ends up"}]},{"t":"p","runs":[{"text":"In the generated route, the system prompt is a string literal three lines above the fetch call. It gets edited in place, by you and by the agent, and nobody can say afterwards which version produced which output. When the feature degrades (and it will, because somebody tightens a sentence to fix one case and breaks three others) there is no record of what the prompt was on the day it worked."}]},{"t":"p","runs":[{"text":"A prompt is a piece of the program that changes the program’s behaviour. Treat it like one."}]},{"t":"h2","id":"one-file-per-prompt","runs":[{"text":"One file per prompt"}]},{"t":"code","lang":"text","label":"Text","text":"prompts/\n  summarise.md\n  categorise-expense.md\n  reply-suggestion.md"},{"t":"p","runs":[{"text":"Plain markdown, read at startup or imported as a string. The file is in git, so every change has a commit, a message and a diff; "},{"text":"git log -p prompts/summarise.md","code":true},{"text":" is the history of the feature’s behaviour, and the bisecting technique from the breaking block applies to prompts as well as to code."}]},{"t":"p","runs":[{"text":"Keep placeholders explicit and visible:"}]},{"t":"code","lang":"markdown","label":"Markdown","text":"You summarise notes for a small business owner.\nWrite at most {{max_sentences}} sentences.\nUse the same language as the note.\nDo not add information that is not in the note."},{"t":"p","runs":[{"text":"Fill them with a small template function rather than string concatenation scattered through the route. The note itself does not go into the template; it goes in the user message, for reasons the next lesson makes concrete."}]},{"t":"h2","id":"stamp-the-version-into-the-log","runs":[{"text":"Stamp the version into the log"}]},{"t":"p","runs":[{"text":"Every call should log which prompt produced it. A short hash of the file content, or the git commit of the prompts folder, stored beside the input, the output and the token count:"}]},{"t":"code","lang":"typescript","label":"TypeScript","text":"log.info({ feature: \"summarise\", promptHash, model, inputTokens, outputTokens, ms });"},{"t":"p","runs":[{"text":"That one line is what makes “it got worse on Thursday” answerable. Without it, the log shows outputs and no way to group them by cause. The evaluation course covers what to do with such logs at scale; here the point is that the line exists."}]},{"t":"h2","id":"changing-a-prompt-is-a-change","runs":[{"text":"Changing a prompt is a change"}]},{"t":"p","runs":[{"text":"Edit it on a branch. Run the fixed set of inputs (the later lesson on whether the feature is good sets that up) against the old and new versions, side by side. Merge when the new one is better on the set, not when it fixed the one example that prompted the edit. Prompts drift towards handling the last complaint at the expense of the general case, and the fixed set is the only thing that notices."}]},{"t":"p","runs":[{"text":"Keep the file short enough to read in one screen. A prompt that has grown to two pages of rules contradicts itself somewhere, and the model resolves the contradiction differently on different days. When a rule is added, look for one that can be removed."}]},{"t":"h2","id":"what-the-model-does-with-the-file","runs":[{"text":"What the model does with the file"}]},{"t":"p","runs":[{"text":"The system prompt is text the model reads before the user’s message. It is not a configuration the model enforces. “Never mention competitors” is a request the model will honour most of the time, and a well-phrased user message can talk it out of it. The prompting course covers how to write these well; the app builder’s version is one sentence: rules in the prompt shape behaviour, and anything that must be true is checked in code afterwards, not requested in prose."}]},{"t":"h2","id":"secrets-do-not-go-in-the-prompt","runs":[{"text":"Secrets do not go in the prompt"}]},{"t":"p","runs":[{"text":"A system prompt is delivered to the model, and models repeat their system prompts when asked nicely enough. Nothing in the file should be something you would mind a user reading: no keys, no internal URLs, no “the discount code for staff is”. Assume the file is public and write it so that being public costs nothing."}]},{"t":"h2","id":"the-agent-and-the-prompt","runs":[{"text":"The agent and the prompt"}]},{"t":"p","runs":[{"text":"An agent asked to “make the summaries better” will edit the prompt, and it will do so with the same confidence it edits code, adding rules for the case at hand. Point it at the fixed-set script and ask for a before-and-after, the same way you would ask for a passing test. A prompt change with no measurement is a guess, whoever made it."}]}],"check":{"question":[{"text":"Summaries got noticeably worse last Thursday. The prompt is an inline string in the route file, and three people have edited it since. How do you find what changed?"}],"options":[{"text":[{"text":"Ask the model to compare its recent outputs with the ones from before Thursday and explain in its own words what it is doing differently now and why the quality dropped."}],"correct":false,"tempting":[{"text":"The model has no memory of previous calls and no access to the old prompt; it will produce a plausible story with no evidence behind it."}]},{"text":[{"text":"Check the provider’s status page for an incident on Thursday and wait for it to clear."}],"correct":false,"tempting":[{"text":"A status incident causes errors and slowness, not a persistent change in quality that survives the weekend."}]},{"text":[{"text":"Switch to a larger model, since the degradation is most likely the provider changing something on their side."}],"correct":false,"tempting":[{"text":"Possible, but three known edits are the obvious suspects and cost nothing to check; changing the model adds a fourth variable."}]},{"text":[{"text":"Run git log -p on the route file to see each edit to the string, and re-run last week’s saved inputs against each version to find the one where quality dropped."}],"correct":true,"tempting":null}],"order":[0,1,2,3],"why":null},"prev":{"slug":"the-first-call-from-your-own-server","title":"The first call from your own server","minutes":9},"next":{"slug":"user-text-inside-a-prompt-is-untrusted","title":"User text inside a prompt is untrusted","minutes":9}}}