diff --git a/src/deriver/deriver.py b/src/deriver/deriver.py
index b4e746d3..c95105eb 100644
--- a/src/deriver/deriver.py
+++ b/src/deriver/deriver.py
@@ -331,10 +331,15 @@ class CertaintyReasoner:
latest_message.created_at,
)
- # Step 2: Deductive reasoning (receives explicit observations)
+ # Step 2: Deductive reasoning (receives atomic propositions)
+ # Extract atomic propositions from explicit and implicit observations
+ atomic_propositions = [
+ obs.content for obs in explicit_observations.explicit
+ ] + [obs.content for obs in explicit_observations.implicit]
+
deductive_response = await self.deductive_reasoner.reason(
working_representation=working_representation,
- explicit_observations=explicit_observations,
+ atomic_propositions=atomic_propositions,
history=history,
speaker_peer_card=speaker_peer_card,
)
@@ -357,7 +362,7 @@ class CertaintyReasoner:
analysis_duration_ms = (time.perf_counter() - analysis_start) * 1000
accumulate_metric(
f"deriver_{latest_message.id}_{self.observer}",
- "critical_analysis_duration",
+ "reasoning_duration",
analysis_duration_ms,
"ms",
)
diff --git a/src/deriver/prompts.py b/src/deriver/prompts.py
index ff25be10..2998c9e5 100644
--- a/src/deriver/prompts.py
+++ b/src/deriver/prompts.py
@@ -100,7 +100,7 @@ def estimate_base_prompt_tokens() -> int:
peer_card=None,
message_created_at=datetime.datetime.now(datetime.timezone.utc),
working_representation=Representation(),
- explicit_observations=Representation(),
+ atomic_propositions=[],
history="",
new_turns=[],
)
@@ -153,7 +153,7 @@ def deductive_reasoning_prompt(
peer_card: list[str] | None,
message_created_at: datetime.datetime,
working_representation: Representation,
- explicit_observations: Representation,
+ atomic_propositions: list[str],
history: str,
new_turns: list[str],
) -> str:
@@ -165,23 +165,29 @@ def deductive_reasoning_prompt(
peer_card (list[str] | None): The bio card of the user being analyzed.
message_created_at (datetime.datetime): Timestamp of the message.
working_representation (Representation): Current user understanding context.
- explicit_observations (Representation): New explicit observations from current batch
- (includes both explicit and implicit propositions).
+ atomic_propositions (list[str]): New atomic propositions from explicit reasoning
+ (includes both explicit and implicit observations as content strings).
history (str): Recent conversation history.
new_turns (list[str]): New conversation turns to analyze.
Returns:
Formatted prompt string for deductive reasoning
"""
- # Format atomic propositions (includes both explicit and implicit from ExplicitReasoner)
- # as numbered list - combine both lists
- all_atomic_propositions = [
- obs.content for obs in explicit_observations.explicit
- ] + [obs.content for obs in explicit_observations.implicit]
+ # Format atomic propositions as numbered list
atomic_propositions_section = "\n".join(
- [f"{i}. {prop}" for i, prop in enumerate(all_atomic_propositions, 1)]
+ [f"{i}. {prop}" for i, prop in enumerate(atomic_propositions, 1)]
)
+ # Format existing deductions from working representation
+ # Uses the same format as Representation.__str__() for DEDUCTIVE section
+ existing_deductions_section = ""
+ if working_representation.deductive:
+ deduction_strings = [
+ f"{i}. {deduction}"
+ for i, deduction in enumerate(working_representation.deductive, 1)
+ ]
+ existing_deductions_section = "\n".join(deduction_strings)
+
return render_template(
settings.DERIVER.DEDUCTIVE_REASONING_TEMPLATE,
{
@@ -190,9 +196,8 @@ def deductive_reasoning_prompt(
"message_created_at": message_created_at,
"working_representation": str(working_representation),
"has_working_representation": not working_representation.is_empty(),
- "explicit_observations": str(explicit_observations),
- "has_explicit_observations": not explicit_observations.is_empty(),
"atomic_propositions_section": atomic_propositions_section,
+ "existing_deductions_section": existing_deductions_section,
"history": history,
"new_turns": new_turns,
},
diff --git a/src/deriver/reasoners/deductive.py b/src/deriver/reasoners/deductive.py
index cbe71d93..d5632615 100644
--- a/src/deriver/reasoners/deductive.py
+++ b/src/deriver/reasoners/deductive.py
@@ -55,7 +55,7 @@ class DeductiveReasoner(BaseReasoner):
async def reason(
self,
working_representation: Representation,
- explicit_observations: Representation,
+ atomic_propositions: list[str],
history: str,
speaker_peer_card: list[str] | None,
) -> DeductiveResponse:
@@ -63,7 +63,8 @@ class DeductiveReasoner(BaseReasoner):
Args:
working_representation: Current representation context
- explicit_observations: New explicit observations from current batch
+ atomic_propositions: New atomic propositions from explicit reasoning
+ (both explicit and implicit observations as content strings)
history: Recent conversation history
speaker_peer_card: Peer card for the observed peer
@@ -81,7 +82,7 @@ class DeductiveReasoner(BaseReasoner):
peer_card=speaker_peer_card,
message_created_at=latest_message.created_at,
working_representation=working_representation,
- explicit_observations=explicit_observations,
+ atomic_propositions=atomic_propositions,
history=history,
new_turns=new_turns,
)
diff --git a/src/templates/deriver/deductive.jinja b/src/templates/deriver/deductive.jinja
index 666f68c2..319933c7 100644
--- a/src/templates/deriver/deductive.jinja
+++ b/src/templates/deriver/deductive.jinja
@@ -65,7 +65,7 @@ Therefore, you must ONLY generate deductions that:
* "Carlos has a daughter starting kindergarten" + "Kindergarten typically starts at age 5" → "Carlos's daughter is approximately 5 years old" (domain-specific knowledge application)
* "Elena graduated with a PhD in neuroscience" + "PhDs require bachelor's degrees" → "Elena completed a bachelor's degree in a relevant field" (non-obvious educational prerequisite)
-**KEY PRINCIPLE:** Only generate deductions that add substantive information that is semantically differentiated from the atomic propositions. Ask yourself:
+**KEY PRINCIPLE:** Only generate deductions that add substantive information that is semantically differentiated from the atomic propositions. Ask yourself:
1. "Would the explicit/implicit extraction have already captured this?" If yes, don't generate it.
2. "Does this conclusion connect or build on multiple propositions in a non-obvious way?" If no, don't generate it.
3. "Does this add meaningful context about who {{ peer_id }} is that isn't already present?" If no, don't generate it.
@@ -118,7 +118,7 @@ Common valid deductive patterns include:
**EXAMPLES OF VALID DEDUCTIONS:**
Example 1 - Categorical Syllogism:
-- PREMISES:
+- PREMISES:
* "Maria attended college" (atomic proposition)
* All people who attended college completed high school or equivalent (general knowledge)
- CONCLUSION: "Maria completed high school or equivalent education"
@@ -148,7 +148,7 @@ Example 5 - Multi-step Scaffolding:
* "Elena graduated with a PhD in neuroscience" (atomic proposition)
* A PhD requires completing a bachelor's degree (general knowledge)
* A bachelor's degree requires completing high school (general knowledge)
-- CONCLUSIONS:
+- CONCLUSIONS:
* "Elena completed a bachelor's degree"
* "Elena completed high school or equivalent education"
@@ -177,7 +177,7 @@ Each deduction must be self-contained and include sufficient context:
- Disambiguating details that make the conclusion independently meaningful
- All necessary qualifiers to ensure accuracy
-{{ peer_card }}
+{{ peer_card|join('\n') }}
{{ existing_deductions_section }}
@@ -228,4 +228,4 @@ Generate ALL valid deductions that can be derived from the available premises. S
},
]
}
-```
\ No newline at end of file
+```
diff --git a/src/templates/deriver/explicit.jinja b/src/templates/deriver/explicit.jinja
index 809e68be..53fcf76f 100644
--- a/src/templates/deriver/explicit.jinja
+++ b/src/templates/deriver/explicit.jinja
@@ -6,7 +6,7 @@ Extract atomic propositions from the peer's message. An atomic proposition is:
**The Critical Balance:**
Each proposition must be atomic (indivisible) yet contain enough semantic context to be interpretable without reference to other propositions.
-- ❌ TOO ATOMIC (lacks context):
+- ❌ TOO ATOMIC (lacks context):
* "Maria is happy" → Happy about what?
* "James said hi" → Said hi to whom? In what context?
* "Sarah went there" → Went where?
@@ -27,7 +27,7 @@ Each proposition must be atomic (indivisible) yet contain enough semantic contex
1. **EXPLICIT EXTRACTION** - Directly stated facts:
- Extract propositions directly asserted in the message
- Each claim becomes a separate atomic proposition
-
+
2. **IMPLICIT EXTRACTION** - Clearly implied facts:
- Extract propositions that are obviously implied by the message
- Only include implications that are certain, not speculative
@@ -96,7 +96,7 @@ Example 4 - Implicit Extraction:
{{ peer_id }}'s known biographical information:
-{{ peer_card }}
+{{ peer_card|join('\n') }}
Current understanding of {{ peer_id }}:
@@ -111,23 +111,23 @@ Recent conversation history for context:
New conversation turns to analyze:
-{{ new_turns }}
+{{ new_turns|join('\n') }}
Extract ALL atomic propositions (both explicit and clearly implied) from the latest peer message. Output your response in JSON structured format:
```json
{
- "explicit":[
- "explicit proposition 1",
- "explicit proposition 2",
+ "explicit": [
+ {"content": "explicit proposition 1"},
+ {"content": "explicit proposition 2"},
...
- "explicit proposition n"
+ {"content": "explicit proposition n"}
],
- "implicit":[
- "implicit proposition 1",
- "implicit proposition 2",
+ "implicit": [
+ {"content": "implicit proposition 1"},
+ {"content": "implicit proposition 2"},
...
- "implicit proposition n"
+ {"content": "implicit proposition n"}
]
}
-```
\ No newline at end of file
+```