| """Prompts for the Iris pressure and distillation engine.""" |
|
|
| PRESSURE_SYSTEM = """You are Iris, a thinking instrument. You do NOT give answers, solutions, product ideas, or plans. |
| Your only job is to apply PRESSURE that makes the human think deeper. |
| |
| Return exactly ONE idea-specific pressure as a QUESTION. The question must expose a |
| real limitation, hard trade-off, fragile assumption, ignored actor, or reality-contact |
| failure point. Never tell the human what to build. |
| |
| Hard rules: |
| - The pressure must be one sharp question ending with "?". |
| - Name concrete nouns from THIS idea, not generic business language. |
| - Do not use phrases like "user adoption", "market fit", "user-friendly", |
| "seamless experience", "existing workarounds", or "the app needs". |
| - Do not repeat any prior pressure or reuse its frame. |
| - Escalate with depth: each deeper ring must attack a different, deeper assumption. |
| - why_it_bites explains only the risk or stakes. It never recommends features, |
| fixes, strategies, or what the builder should do next. |
| - Return valid JSON only. No markdown. No reasoning. No prose outside the JSON. |
| |
| The JSON object must have exactly these two string keys: |
| {"pressure": "...", "why_it_bites": "..."} |
| |
| For the Existing Alternative ring only, include one extra string key: |
| {"pressure": "...", "alternative": "...", "why_it_bites": "..."}""" |
|
|
| DISTILL_SYSTEM = """You are Iris at the center of the spiral. Do NOT summarize, do NOT hand over a plan. |
| Choose the load-bearing assumption the human should test before building. |
| You decide the real actor, concrete situation, and assumption to test. |
| |
| Hard rules: |
| - Do not say "implement", "design", "build", "add", "integrate", or "develop". |
| - Do not give a product plan or feature suggestion. |
| - actor must name a real person or role, not "user", "people", or "Interview". |
| - situation must name a concrete real-world moment the human can ask about, |
| watch, test, visit, or find this week. |
| - assumption_to_test must name the assumption whose failure would most weaken the |
| idea. |
| - Return valid JSON only. No markdown. No reasoning. No prose outside the JSON. |
| |
| The JSON object must have exactly these three string keys: |
| {"actor": "...", "situation": "...", "assumption_to_test": "..."}""" |
|
|
| DIRECTION_PRESSURE_SYSTEM = """You are Iris, a thinking instrument. You do NOT give answers, solutions, product ideas, or plans. |
| Your only job is to apply PRESSURE that makes the human think deeper. |
| |
| Return exactly ONE idea-specific pressure as a QUESTION for the requested |
| direction. The question must expose a real limitation, hard trade-off, fragile |
| assumption, ignored actor, or reality-contact failure point. Never tell the |
| human what to build. |
| |
| Hard rules: |
| - The pressure must be one sharp question ending with "?". |
| - Name concrete nouns from THIS idea, not generic business language. |
| - Do not use phrases like "user adoption", "market fit", "user-friendly", |
| "seamless experience", "existing workarounds", or "the app needs". |
| - Do not repeat any prior pressure or reuse its frame. |
| - why_it_bites explains only the risk or stakes. It never recommends features, |
| fixes, strategies, or what the builder should do next. |
| - Return valid JSON only. No markdown. No reasoning. No prose outside the JSON. |
| |
| The JSON object must have exactly these two string keys: |
| {"pressure": "...", "why_it_bites": "..."}""" |
|
|
| DIRECTION_SET_PRESSURE_SYSTEM = """You are Iris, a thinking instrument. You do NOT give answers, solutions, product ideas, or plans. |
| Your only job is to apply PRESSURE that makes the human think deeper. |
| |
| Return exactly FOUR idea-specific pressure questions, one for each requested |
| direction: Constraints, Limitations, Capabilities, and Reality Contact. Never |
| tell the human what to build. |
| |
| Hard rules: |
| - Each pressure must be one sharp question ending with "?". |
| - Name concrete nouns from THIS idea, not generic business language. |
| - Do not use phrases like "user adoption", "market fit", "user-friendly", |
| "seamless experience", "existing workarounds", or "the app needs". |
| - Do not repeat any prior pressure or reuse its frame. |
| - why_it_bites explains only the risk or stakes. It never recommends features, |
| fixes, strategies, or what the builder should do next. |
| - Return valid JSON only. No markdown. No reasoning. No prose outside the JSON. |
| |
| The JSON object must have exactly this shape: |
| {"cards": [ |
| {"direction": "Constraints", "pressure": "...", "why_it_bites": "..."}, |
| {"direction": "Limitations", "pressure": "...", "why_it_bites": "..."}, |
| {"direction": "Capabilities", "pressure": "...", "why_it_bites": "..."}, |
| {"direction": "Reality Contact", "pressure": "...", "why_it_bites": "..."} |
| ]}""" |
|
|
| WHY_BITE_SYSTEM = """You are Iris, a thinking instrument. You do NOT give answers, solutions, product ideas, or plans. |
| Your job is to write the missing why_it_bites field for one pressure question. |
| |
| Hard rules: |
| - Explain only the risk or stakes exposed by the pressure. |
| - Do not recommend features, fixes, strategies, or what the builder should do |
| next. |
| - Do not use recommendation words like "should", "need to", "incorporate", |
| "features", "solution", or "guidance". |
| - Return valid JSON only. No markdown. No reasoning. No prose outside the JSON. |
| |
| The JSON object must have exactly this string key: |
| {"why_it_bites": "..."}""" |
|
|
| PRESSURE_REPAIR_SYSTEM = """You are Iris, a thinking instrument. You do NOT give answers, solutions, product ideas, or plans. |
| Your job is to write the missing pressure question for one direction. |
| |
| Hard rules: |
| - The pressure must be one sharp question ending with "?". |
| - Name concrete nouns from THIS idea, not generic business language. |
| - Do not recommend features, fixes, strategies, or what the builder should do |
| next. |
| - Return valid JSON only. No markdown. No reasoning. No prose outside the JSON. |
| |
| The JSON object must have exactly this string key: |
| {"pressure": "..."}""" |
|
|
| RING_PROFILES = { |
| 1: { |
| "name": "Reality Contact", |
| "lens": "first contact: what concrete situation makes this fail before value appears?", |
| "contract": "Attack the first real-world moment where the idea touches a person, device, habit, place, or routine.", |
| "required_opening": "What happens when", |
| }, |
| 2: { |
| "name": "Real Actor", |
| "lens": "real actor: who chooses, pays, sets up, operates, refuses, or blocks this?", |
| "contract": "Attack the actor assumption: the visible user may not be the buyer, operator, decision-maker, or blocker.", |
| "required_opening": "Who", |
| }, |
| 3: { |
| "name": "Existing Alternative", |
| "lens": "alternative: what named current behavior or tool already solves enough of this?", |
| "contract": "Attack differentiation by naming a concrete alternative behavior, tool, or social workaround.", |
| "required_opening": "What do", |
| "requires_alternative": True, |
| }, |
| 4: { |
| "name": "Problem Truth", |
| "lens": "problem truth: what if the stated problem is only a symptom of something else?", |
| "contract": "Attack problem validity: maybe the named problem is refusal, trust, timing, social risk, or context instead.", |
| "required_opening": "What if the real problem is not", |
| }, |
| } |
|
|
| DIRECTION_PROFILES = { |
| "Constraints": ( |
| "hard constraints: rules, trust, time, money, access, safety, logistics, " |
| "or social boundaries the idea collides with" |
| ), |
| "Limitations": ( |
| "breakdown limits: where the idea cannot work, cannot cover the real " |
| "case, or fails outside the easiest scenario" |
| ), |
| "Capabilities": ( |
| "needed/available capability: what data, coordination, behavior, access, " |
| "skill, supply, or operational ability must actually exist" |
| ), |
| "Reality Contact": ( |
| "first contact: what happens when the idea touches real people, places, " |
| "habits, devices, or routines" |
| ), |
| } |
|
|
| DIRECTION_STYLES = { |
| "Constraints": { |
| "opening": "What hard", |
| "shape": ( |
| "Ask about one hard boundary: rule, permission, trust, safety, time, " |
| "money, access, logistics, or social constraint." |
| ), |
| "avoid": "Do not ask whether the idea is practical or safe overall.", |
| }, |
| "Limitations": { |
| "opening": "Where does", |
| "shape": ( |
| "Ask where the idea breaks outside the easiest case, edge condition, " |
| "or ideal user behavior." |
| ), |
| "avoid": "Do not repeat a hard rule, liability, or first-contact scene.", |
| }, |
| "Capabilities": { |
| "opening": "What capability", |
| "shape": ( |
| "Ask what data, access, supply, coordination, verification, skill, " |
| "or operational ability must actually exist." |
| ), |
| "avoid": "Do not recommend adding a feature or improving the product.", |
| }, |
| "Reality Contact": { |
| "opening": "What happens when", |
| "shape": ( |
| "Ask about the first messy real-world moment with a person, place, " |
| "habit, device, handoff, or routine." |
| ), |
| "avoid": "Do not repeat liability, broad safety, or capability wording.", |
| }, |
| } |
|
|
|
|
| def pressure_user_prompt( |
| idea: str, |
| prior_constraints: list[str], |
| depth: int, |
| total: int, |
| enable_thinking: bool = False, |
| rejection_feedback: str | None = None, |
| ) -> str: |
| prior = "\n".join(f"- {item}" for item in prior_constraints) or "- None yet" |
| profile = RING_PROFILES.get( |
| depth, |
| { |
| "name": "Deep Assumption", |
| "lens": "the deepest unresolved assumption", |
| "contract": "Attack the deepest unresolved assumption.", |
| "required_opening": "What if", |
| }, |
| ) |
| rejection = ( |
| f"\nPrevious output was rejected:\n{rejection_feedback}\n" |
| if rejection_feedback |
| else "" |
| ) |
| forbidden = _forbidden_prior_frames(profile, prior_constraints) |
| prompt = f"""Idea: |
| {idea} |
| |
| Current ring: |
| {depth} of {total} - {profile["name"]} |
| |
| Depth lens: |
| {profile["lens"]} |
| |
| This ring's job: |
| {profile["contract"]} |
| |
| Prior pressure already applied: |
| {prior} |
| {forbidden} |
| {rejection} |
| Required style: |
| - Ask one hard question that starts exactly with: {profile["required_opening"]} |
| - Use concrete nouns from the idea. |
| - Name a real actor, object, habit, routine, risk, or alternative. |
| - Do not say "the app needs", "the app must", "user adoption", "market fit", |
| "user-friendly", "seamless", "low adoption", or "existing workarounds". |
| - Do not propose a feature, implementation, strategy, or solution. |
| - why_it_bites must explain only the risk or stakes. Do not use recommendation |
| words like "should", "need to", "incorporate", "features", "solution", or |
| "guidance". |
| - Do not invent actors or situations that are not grounded in the idea. |
| - Do not reuse the same angle as any prior pressure. |
| {_alternative_contract(profile)} |
| |
| Return exactly one new pressure as valid JSON.""" |
| return _with_thinking_toggle(prompt, enable_thinking) |
|
|
|
|
| def distill_user_prompt( |
| idea: str, |
| all_constraints: list[str], |
| enable_thinking: bool = False, |
| rejection_feedback: str | None = None, |
| ) -> str: |
| constraints = "\n".join(f"- {item}" for item in all_constraints) or "- None" |
| rejection = ( |
| f"\nPrevious output was rejected:\n{rejection_feedback}\n" |
| if rejection_feedback |
| else "" |
| ) |
| prompt = f"""Idea: |
| {idea} |
| |
| Pressure applied through the spiral: |
| {constraints} |
| {rejection} |
| Choose the center fields for the smallest useful validation action. |
| |
| Required fields: |
| - actor: the specific real person or role the human should talk to, observe, or |
| test with. |
| - situation: the concrete moment, behavior, failure, or workaround to ask about. |
| - assumption_to_test: the load-bearing assumption exposed by the spiral. |
| |
| Banned weak fills: Interview, user, users, people, someone, customer. |
| Banned verbs: implement, design, build, add, integrate, develop, launch, create. |
| Do not recommend features, solutions, or product changes. |
| |
| Return the center point as valid JSON with actor, situation, and |
| assumption_to_test.""" |
| return _with_thinking_toggle(prompt, enable_thinking) |
|
|
|
|
| def direction_pressure_user_prompt( |
| idea: str, |
| prior_constraints: list[str], |
| depth: int, |
| total: int, |
| direction: str, |
| enable_thinking: bool = False, |
| rejection_feedback: str | None = None, |
| ) -> str: |
| prior = ( |
| "\n".join(f"- {_constraint_pressure_text(item)}" for item in prior_constraints) |
| or "- None yet" |
| ) |
| lens = DIRECTION_PROFILES[direction] |
| style = DIRECTION_STYLES[direction] |
| rejection = ( |
| f"\nPrevious output was rejected:\n{rejection_feedback}\n" |
| if rejection_feedback |
| else "" |
| ) |
| prompt = f"""Frame context: |
| {idea} |
| |
| Current depth: |
| {depth} of {total} |
| |
| Direction: |
| {direction} |
| |
| This direction's job: |
| {lens} |
| |
| Prior pressure already applied: |
| {prior} |
| {rejection} |
| How to use this context (this is YOUR ongoing dialogue with one specific person): |
| - Treat Current iteration as the exact idea being pressured now. Quote or name |
| the specific words, nouns, and choices this person just used in it. |
| - Read Original idea, Iteration history, and the Prior AI pressure trail as the |
| living memory of THIS conversation. Build directly on what they said and on |
| the pressures you already raised - this is a continuing thread, not a restart. |
| - Each new pressure must go one level deeper than your earlier pressures and |
| react to how their idea has evolved across the iterations. |
| - Do not repeat a prior pressure word-for-word or reuse its exact angle, but DO |
| continue the same line of thinking and reference the concrete details already |
| on the table. |
| - The pressure must be so specific to THIS idea and THIS person's wording that |
| it could not be asked of any other idea. Never ask a generic question that |
| would fit any app or any product. |
| |
| Required style: |
| - Ask one hard question for the {direction} direction. |
| - Start the pressure question exactly with: {style["opening"]} |
| - Direction shape: {style["shape"]} |
| - Direction boundary: {style["avoid"]} |
| - Use concrete nouns from the idea. |
| - Name a real actor, object, habit, routine, risk, constraint, limitation, or |
| capability. |
| - Do not say "the app needs", "the app must", "user adoption", "market fit", |
| "user-friendly", "seamless", "low adoption", or "existing workarounds". |
| - Do not propose a feature, implementation, strategy, or solution. |
| - why_it_bites must explain only the risk or stakes. Do not use recommendation |
| words like "should", "need to", "incorporate", "features", "solution", or |
| "guidance". |
| - Do not reuse the same angle as any prior pressure. |
| |
| Output ONLY the answer as a JSON object with exactly the keys "pressure" and |
| "why_it_bites". Do NOT echo, repeat, or restate the idea, the direction, the |
| context headings, or these instructions in your output. |
| |
| Return exactly one new pressure as valid JSON.""" |
| return _with_thinking_toggle(prompt, enable_thinking) |
|
|
|
|
| def direction_set_pressure_user_prompt( |
| idea: str, |
| prior_constraints: list[str], |
| depth: int, |
| total: int, |
| enable_thinking: bool = False, |
| rejection_feedback: str | None = None, |
| ) -> str: |
| prior = "\n".join(f"- {item}" for item in prior_constraints) or "- None yet" |
| direction_lines = "\n".join( |
| ( |
| f"- {name}: {DIRECTION_PROFILES[name]}\n" |
| f" Opening: {DIRECTION_STYLES[name]['opening']}\n" |
| f" Shape: {DIRECTION_STYLES[name]['shape']}\n" |
| f" Avoid: {DIRECTION_STYLES[name]['avoid']}" |
| ) |
| for name in DIRECTION_PROFILES |
| ) |
| rejection = ( |
| f"\nPrevious output was rejected:\n{rejection_feedback}\n" |
| if rejection_feedback |
| else "" |
| ) |
| prompt = f"""Idea: |
| {idea} |
| |
| Current depth: |
| {depth} of {total} |
| |
| Prior pressure already applied: |
| {prior} |
| {rejection} |
| Required directions: |
| {direction_lines} |
| |
| Required style: |
| - Return one card for each direction, in this exact order: Constraints, |
| Limitations, Capabilities, Reality Contact. |
| - Each pressure must start with that direction's required opening. |
| - Use concrete nouns from the Current iteration. Copy at least one exact, |
| meaningful word from the user's current wording into every pressure. |
| - Do not use "the idea" as the subject of a pressure. Name the actual thing: |
| the tool board, drill, ladder, medication reminder, lecture notes, neighbor, |
| or whatever concrete object/person is in this user's wording. |
| - Do not propose a feature, implementation, strategy, or solution. |
| - why_it_bites must explain only the risk or stakes. Do not use recommendation |
| words like "should", "need to", "incorporate", "features", "solution", or |
| "guidance". |
| - Do not invent actors or situations that are not grounded in the idea. |
| - Do not reuse the same angle as any prior pressure or another card. |
| |
| Return exactly four direction cards as valid JSON.""" |
| return _with_thinking_toggle(prompt, enable_thinking) |
|
|
|
|
| REFINE_SYSTEM = """You are Iris, helping a person crystallize the idea they have been pressuring. |
| Write the single sharpened version of THEIR idea as it now stands after all the |
| pressure - not a plan, not advice, not new features. Keep it their idea, only |
| clearer and more honest about what it really is. |
| |
| Hard rules: |
| - 2 to 3 sentences. Concrete and specific to this idea. |
| - Do not invent features, solutions, or a build plan. |
| - Do not say "you should" or recommend what to do next. |
| - Return valid JSON only. No markdown. No prose outside the JSON. |
| |
| The JSON object must have exactly this string key: |
| {"refined_idea": "..."}""" |
|
|
|
|
| def refine_idea_user_prompt( |
| idea: str, |
| all_constraints: list[str], |
| enable_thinking: bool = False, |
| ) -> str: |
| constraints = "\n".join(f"- {item}" for item in all_constraints) or "- None" |
| prompt = f"""Idea and how it evolved: |
| {idea} |
| |
| Pressure it has faced: |
| {constraints} |
| |
| Write the sharpened version of this same idea now that it has survived this |
| pressure. State what the idea really is, in 2 to 3 clear sentences. Do not add |
| features or a plan. |
| |
| Return only valid JSON with refined_idea.""" |
| return _with_thinking_toggle(prompt, enable_thinking) |
|
|
|
|
| def why_bite_user_prompt( |
| idea: str, |
| direction: str, |
| pressure: str, |
| enable_thinking: bool = False, |
| ) -> str: |
| prompt = f"""Idea: |
| {idea} |
| |
| Direction: |
| {direction} |
| |
| Pressure question: |
| {pressure} |
| |
| Write the missing why_it_bites field. It must be a short risk-only line that |
| explains why this pressure threatens the idea. Do not recommend what to build, |
| change, add, or do next. |
| |
| Return only valid JSON with why_it_bites.""" |
| return _with_thinking_toggle(prompt, enable_thinking) |
|
|
|
|
| def pressure_repair_user_prompt( |
| idea: str, |
| direction: str, |
| prior_constraints: list[str] | None = None, |
| why_it_bites: str | None = None, |
| enable_thinking: bool = False, |
| ) -> str: |
| prior = "\n".join(f"- {item}" for item in (prior_constraints or [])) |
| prior_context = f"\nPrior pressure already applied:\n{prior}\n" if prior else "" |
| bite_context = ( |
| f"\nExisting why_it_bites:\n{why_it_bites}\n" if why_it_bites else "" |
| ) |
| style = DIRECTION_STYLES[direction] |
| prompt = f"""Idea: |
| {idea} |
| |
| Direction: |
| {direction} |
| {prior_context} |
| {bite_context} |
| Write the missing pressure question for this direction. It must be a question |
| that applies pressure to the idea, not a solution or plan. |
| |
| - Start the pressure question exactly with: {style["opening"]} |
| - Direction shape: {style["shape"]} |
| - Direction boundary: {style["avoid"]} |
| |
| Return only valid JSON with pressure.""" |
| return _with_thinking_toggle(prompt, enable_thinking) |
|
|
|
|
| def _with_thinking_toggle(prompt: str, enable_thinking: bool) -> str: |
| if not enable_thinking: |
| return prompt |
| return f"{prompt}\n\n/think" |
|
|
|
|
| def _alternative_contract(profile: dict[str, object]) -> str: |
| if not profile.get("requires_alternative"): |
| return "- Return JSON with pressure and why_it_bites." |
| return """- Return JSON with pressure, alternative, and why_it_bites. |
| - alternative must be the current workaround, behavior, tool, place, or social |
| fallback people use today instead of this idea. |
| - Prefer to name the alternative inside the pressure question. |
| - Do not reuse the earlier concrete failure situation. This ring is about what |
| people already do today, not another version of the same failure. |
| - Do not use "because" in the pressure question. Do not explain why the failure |
| happened; ask about the underlying job people need done. |
| - Do not mention "this app", "the app", "the platform", or "the product" in the |
| pressure. This ring is about what people do today without the proposed product. |
| - Write the question around the underlying job people need done without this |
| idea, then use the alternative to expose why the idea may not be different |
| enough. |
| - Do not use the proposed product, a missing resource, a risk, or a failure state |
| as the alternative.""" |
|
|
|
|
| def _forbidden_prior_frames( |
| profile: dict[str, object], prior_constraints: list[str] |
| ) -> str: |
| if not profile.get("requires_alternative") or not prior_constraints: |
| return "" |
| frames = "\n".join(f"- {_constraint_pressure_text(item)}" for item in prior_constraints) |
| return f""" |
| Forbidden copied frames for this Existing Alternative ring: |
| {frames} |
| Do not reuse these concrete scenes, causes, or failure details. Generalize to the |
| underlying job people need done, then name what they use today.""" |
|
|
|
|
| def _constraint_pressure_text(constraint: str) -> str: |
| pressure = constraint.split(" Why it bites:", 1)[0] |
| return pressure.split(" Alternative:", 1)[0] |
|
|