Stop Shipping Prompt Wording in a Pull Request
Hardcoded prompts turn a two-minute wording tweak into a pull request. Langfuse prompt management fetches the production label at runtime so wording ships without engineering.

Langfuse prompt management versions the instructions, labels production, and compiles them at runtime. The agent does not care where the string came from.
The product person who wants a less formal reply is waiting on a deploy. That wait is the thing you can delete.
In this article: You will learn how Langfuse prompt management pulls Harbor Desk's classify and reply wording out of source and into two named, labeled prompts. We cover text versus chat,
get_promptpluscompile, client-side caching, and how to link the prompt object to a generation so you can compare versions. By the end, a wording change is a label move, not a deploy.
Product wants the Harbor Desk reply "less formal." The wording is a string in source. So someone opens a ticket, an engineer opens a pull request, CI runs, and a deploy ships two adjectives. That wait is the thing Langfuse prompt management deletes.
A prompt in Langfuse is the instructions, a string or a list of chat messages, plus optional config. It is versioned. It is labeled production or staging. The SDK caches it on the client, so a fetch on the hot path is a memory read, not a network hop. Updates deploy without engineering work.
This article moves Harbor Desk's two strings into two named prompts, compiles them into the system_prompt the agent already takes, and links the prompt object to the generation so you can compare versions later. DeepAgents takes the same compiled string. The Langfuse call is the same no matter which path produced the tree.

Text versus chat, decided at create time
Langfuse has two prompt types. type cannot change after creation.
Text is one string. It is fine for a system message.
Chat is an array of {role, content} messages. Use it when you need the whole conversation shape, few-shot exchanges, or history.
Harbor Desk's classify and reply instructions are system messages. Start them as text. You can add {{variables}} now, such as {{shop_name}}, and compile them at runtime.

This listing creates both Harbor Desk prompts as named text objects and pins the production label on each version.
from langfuse import get_client
langfuse = get_client() # ①
langfuse.create_prompt(
name="harbor-desk-classify", # ②
type="text", # ③
prompt=(
"You classify {{shop_name}} support tickets. " # ④
"Reply with exactly one word: refund, shipping, or other."
),
labels=["production"], # ⑤
)
langfuse.create_prompt(
name="harbor-desk-reply", # ⑥
type="text",
prompt=(
"You draft a short, plain support reply for {{shop_name}}. "
"Use only the policy provided. Do not invent terms."
),
labels=["production"],
)
① get_client() returns the process-wide SDK handle; later fetches reuse this same cached client.
② name is the stable key get_prompt will use; a later create_prompt with this name adds a version, not a new object.
③ type="text" is locked at create time because these are system-message strings, not chat arrays.
④ {{shop_name}} stays a compile-time variable so the shop name is filled at fetch, not baked into the version.
⑤ labels=["production"] points the production label at this version so fetch code does not change.
⑥ harbor-desk-reply is the second named prompt, same text type and production label, different wording.
Note: The full extracted listing at code/langfuse/part-6-prompt-management/listings/01-create-harbor-desk-prompts.py is the complete create_prompt script shown here.
JavaScript uses @langfuse/client:
import { LangfuseClient } from "@langfuse/client";
const langfuse = new LangfuseClient();
await langfuse.prompt.create({
name: "harbor-desk-classify",
type: "text",
prompt:
"You classify {{shop_name}} support tickets. " +
"Reply with exactly one word: refund, shipping, or other.",
labels: ["production"],
});
If a prompt with that name already exists, create_prompt adds a new version. Labels are how you point production at a version without changing code. Fetch the production label on purpose, not "whatever was last saved."

Gotcha: get_prompt("harbor-desk-reply", type="chat") on a text prompt, or the reverse, is the type mismatch that looks like a broken fetch. The type argument defaults to text. Pass type="chat" only for chat prompts.
Fetch, compile, and hand the string to the agent
classify_prompt = langfuse.get_prompt("harbor-desk-classify")
classify_text = classify_prompt.compile(shop_name="harbor-shop")
reply_prompt = langfuse.get_prompt("harbor-desk-reply")
reply_text = reply_prompt.compile(shop_name="harbor-shop")
get_prompt fetches the production version by default. compile(...) fills {{shop_name}}. Chat prompts compile to a list of messages; text prompts compile to a string.
On the Agent SDK path, those strings become the system_prompt you already pass. You can concatenate them, or keep classify in the system prompt and put the reply instructions in the user turn. DeepAgents takes the same compiled string as system_prompt= on create_deep_agent. The OpenAI drop-in path puts classify_text in messages[0]["content"].
JavaScript:
const prompt = await langfuse.prompt.get("harbor-desk-classify");
const classifyText = prompt.compile({ shop_name: "harbor-shop" });

Caching is enabled. Guaranteed availability is the feature that keeps a cached copy serving if Langfuse is briefly unreachable. You do not write that cache yourself.
Link the prompt to the generation
Fetching is not enough if you want metrics per prompt version. You must link the prompt object to the generation.
On a generation that you create with the Langfuse SDK, pass prompt=prompt into start_as_current_observation. You can also pass it into update_current_generation(prompt=prompt) inside an @observe(as_type="generation") function. That attaches the prompt only to that generation.
prompt = langfuse.get_prompt("harbor-desk-reply") # ①
compiled = prompt.compile(shop_name="harbor-shop") # ②
with langfuse.start_as_current_observation(
as_type="generation",
name="draft-reply",
model="gpt-4o",
prompt=prompt, # ③
) as generation:
# call the model with `compiled`
generation.update(output=reply) # ④
① get_prompt loads the production version of harbor-desk-reply.
② compile fills {{shop_name}} so the model sees a finished string.
③ Passing the prompt object on prompt= links this generation to that prompt version.
④ generation.update records the model output on the same observation so version metrics have a target.
Note: The full extracted listing at code/langfuse/part-6-prompt-management/listings/02-link-prompt-to-generation.py is the complete link-and-observe snippet shown here.
The Agent SDK instrumentor will not do this for you. If you need the link on an agent run, wrap the draft step in your own generation, or call update_current_generation, and pass the prompt object there. Opening that generation in the UI highlights the prompt version. The Metrics tab on the prompt is how you compare versions later.

A/B testing and the playground are the next steps in the UI: test a wording, label it production when it wins. You do not need a second implementation for that.
Do this today
Four moves, none of them a deploy:
- Create
harbor-desk-classifyandharbor-desk-replyas text prompts. Pinlabels=["production"]on the first version, and leave{{shop_name}}as a compile-time variable. - Replace the hardcoded strings with
get_promptpluscompile. Hand the compiled text to the Agent SDKsystem_prompt, to DeepAgents the same way, or intomessages[0]["content"]on the OpenAI drop-in. - Link the prompt object to the
draft-replygeneration. Passprompt=promptintostart_as_current_observationorupdate_current_generation. Fetching alone does not give you version metrics. - Change the reply wording in the UI, keep the
productionlabel on the new version, and run one ticket. You should see the new wording without a deploy.
Prompts are not source code
They are versioned objects with labels, cached in the SDK, and compiled at runtime. Harbor Desk's classify and reply strings live in harbor-desk-classify and harbor-desk-reply. The Agent SDK and DeepAgents both consume the compiled text. Link the prompt object to the generation if you want metrics by version.
The two-adjective wait was never an engineering problem. It was a storage problem. Put the wording where a label can move it, and the next "less formal" request never opens a pull request.