Treat your prompts like the product logic they are.
Your prompts decide what customers are told, yet most teams keep them as strings nobody can test, version or debug. Promptise turns them into typed, versioned and tested components, so a prompt change is reviewed like a code change.
No more untested strings deciding what customers see.
In most frameworks a system prompt is a raw string: untestable, unversioned and invisible at runtime. Promptise treats it as a software component with types, priorities, tests and a debugger.
One giant string
No structure and no tests. When the agent makes a bad decision you read two hundred lines and guess which part caused it. Version control means copying a file.
Typed blocks that assemble
Snap Identity, Rules, Examples and OutputFormat together. Each has a priority, so when the token budget is tight the lowest-priority block is dropped first. Test each one on its own.
What ships with it
Eight block types, conversation flows, five composable strategies, four perspectives, eleven context providers, output guards, a version registry, an inspector and a pytest harness.
When context runs short, lose the least important part, not the instructions.
The system prompt is built from eight block types, not concatenated strings. When the context window is tight the assembler drops the lowest-priority block first, so the agent degrades instead of truncating mid-sentence.
Identity
Who the agent is: role, expertise, personality. The last thing dropped.
P10Rules
Hard constraints such as never share PII or always cite sources.
P9OutputFormat
The expected response structure: a schema, markdown, specific fields.
P8ContextSlot
Dynamic data filled at runtime: user info, retrieved data, tool results.
P6Section
Domain-specific instructions for anything that fits no other type.
P5Examples
Few-shot demonstrations. First to go when context is tight.
P4Conditional
Wraps a block that appears or disappears on a runtime condition.
wrapsComposite
Groups related blocks into one reusable collection.
groupsPriorities shown are the defaults; each block takes its own.
One prompt that adapts to each stage of a conversation.
Conversations have phases and questions have shapes. Flows swap blocks as a conversation moves, and strategies change how the model reasons before it answers.
Greeting
A friendly identity with the company policy rules.
Gathering
Clarifying questions and data collection rules.
Diagnosis
Technical knowledge, with the greeting tone switched off.
Solution
An action-oriented identity with escalation rules.
Conversation flows
A support chat is greeting, gathering, diagnosis and solution, and each needs different context, rules and tone. A flow defines phases with their active blocks and lifecycle hooks, so the agent’s voice follows the conversation instead of staying static.
Five strategies, four perspectives
Chain of thought, structured reasoning, self-critique, plan and execute and decompose combine with the + operator. Analyst, critic, advisor and creative are orthogonal: any perspective pairs with any strategy. Chaining operators chain, parallel, branch, retry and fallback wire prompts together.
from promptise.prompts import analystfrom promptise.prompts.strategies import ( chain_of_thought, self_critique, decompose, plan_and_execute,) # reason step by step, then critique the reasoningcareful = analyze.with_strategy( chain_of_thought + self_critique,) # break the problem down, plan each part, then solvethorough = analyze.with_strategy( decompose + plan_and_execute,) # a perspective pairs with any strategyreview = careful.with_perspective(analyst)Every answer grounded in current information.
Before every model call, context providers inject up-to-date information. They are async, and a custom one is a class with a single provide method.
User and environment
Who is asking and where the agent is running.
Conversation
The history so far, and how it was shaped.
Tools, memory, tasks
What the agent can call, remembers and is working on.
Team and world
Other agents, shared state and structured facts.
Build your own
Anything the agent needs to know can be a provider: an API call, a database row, the current time. Attach providers with the @context decorator, or at runtime with with_context.
from promptise.prompts import prompt, contextfrom promptise.prompts.context import memory_context class WeatherProvider: """Inject current weather for location-aware agents.""" async def provide(self, ctx) -> str: weather = await weather_api.get("Berlin") return f"## Current weather\n{weather.summary}" @prompt(model="openai:gpt-5-mini")@context(WeatherProvider(), memory_context(limit=5))async def advise(question: str) -> str: """Answer using the injected context: {question}"""Stop bad output before it reaches your systems.
Guards run before and after generation. A guard that fails raises a GuardError instead of letting bad output reach the rest of your program.
ContentFilterGuard
Block specific words, such as competitor names, and require specific phrases, such as disclaimers.
LengthGuard
Enforce minimum and maximum lengths: no one-word answers, no unbounded essays.
SchemaStrictGuard
The output must parse as well-formed JSON. Anything else is rejected.
InputValidatorGuard
A callable that checks the input before the model call, so bad queries are refused early.
OutputValidatorGuard
A callable that checks the output after the call: business rules and domain checks.
Your own
Guards are small objects. Write one for any rule your product needs.
from promptise.prompts.guards import ( ContentFilterGuard, LengthGuard, SchemaStrictGuard, OutputValidatorGuard,) guarded = respond.with_guards( ContentFilterGuard( blocked=["competitor_name"], required=["disclaimer"], ), LengthGuard(min_length=100, max_length=2000), SchemaStrictGuard(), OutputValidatorGuard(check_policy),)Debug, version and test prompts like code.
Debugging, versioning and testing are what separate a prompt from a string. Pick one to see it.
Ask the prompt why, not the model.
When an agent makes an unexpected decision, the inspector records how the prompt was assembled: which blocks went in, which were left out, which context providers ran and which guards rejected the output.
- Blocks included and excluded, with priority and token estimate
- Context providers that ran, and their timing
- Guards that passed and failed
from promptise.prompts import PromptInspector inspector = PromptInspector()traced = analyze.with_inspector(inspector) await traced("Q3 revenue data") print(inspector.summary())# --- Trace 1: analyze ---# Model: openai:gpt-5-mini# Blocks included: identity, rules trace = inspector.last()trace.blocks_excluded # what was droppedtrace.context_providers # what ran, and how longtrace.guards_failed # what rejected the outputControl exactly what the model sees, and what it costs.
Register context layers by priority, set a budget, and the engine assembles what fits. Required layers are never touched; the rest trim from the lowest priority up.
Counted, not estimated
The engine counts tokens with a real tokenizer rather than a character guess, and reserves room for the response. Conversation history is trimmed by removing the oldest user and assistant pairs, never splitting a pair.
- Layers carry a priority and can be marked required
- Each layer picks a trim strategy
- A report records what was kept and what was cut
from promptise import ContextEngine engine = ContextEngine( model="openai:gpt-5-mini", # window inferred from the model response_reserve=1024, # leave room for the output) # higher priority = kept longerengine.add_layer("identity", priority=10, required=True)engine.add_layer("rules", priority=9, required=True)engine.add_layer("current_task", priority=8)engine.add_layer("conversation_history", priority=1) engine.set_content("identity", "You are a billing analyst.")messages = engine.assemble()You do not have to adopt all of it at once
Start with a string
Pass a plain string to build_agent(instructions=...). Fine for scripts and prototypes.
Add blocks
When a prompt has three or more concerns, split them into typed blocks with priorities.
Add flows and strategies
When a conversation has phases, or answers need reasoning, add them.
Add guards and tests
Before production, add output guards and a PromptTestCase for each prompt.
Stop guessing which line of the prompt broke.
Start with a plain string, add blocks when it grows, and keep every prompt versioned, tested and inspectable.