Skip to content

Commit 67914ad

Browse files
committed
fix (tasks): removed context verification step before planning
1 parent d25a635 commit 67914ad

File tree

7 files changed

+91
-271
lines changed

7 files changed

+91
-271
lines changed

src/client/components/tasks/TaskDetailsContent.js

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -394,11 +394,6 @@ const TaskDetailsContent = ({
394394
<div className="space-y-6">
395395
{/* --- SWARM DETAILS (if applicable) --- */}
396396
{displayTask.task_type === "swarm" && (
397-
<SwarmDetailsSection swarmDetails={displayTask.swarm_details} />
398-
)}
399-
400-
{/* --- Researched Context (if planning) --- */}
401-
{displayTask.status === "planning" && displayTask.found_context && (
402397
<div>
403398
<label className="text-sm font-medium text-neutral-400 block mb-2">
404399
Researched Context
@@ -465,20 +460,6 @@ const TaskDetailsContent = ({
465460
</div>
466461
</div>
467462

468-
{/* --- Researched Context (if planning) --- */}
469-
{displayTask.status === "planning" && displayTask.found_context && (
470-
<div>
471-
<label className="text-sm font-medium text-neutral-400 block mb-2">
472-
Researched Context
473-
</label>
474-
<div className="bg-neutral-800/50 p-3 rounded-lg text-sm text-neutral-300 whitespace-pre-wrap border border-neutral-700/50">
475-
<ReactMarkdown className="prose prose-sm prose-invert">
476-
{displayTask.found_context}
477-
</ReactMarkdown>
478-
</div>
479-
</div>
480-
)}
481-
482463
{/* --- DESCRIPTION --- */}
483464
<div>
484465
<label className="text-sm font-medium text-neutral-400 block mb-2">

src/server/main/db.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -386,8 +386,7 @@ async def add_task(self, user_id: str, task_data: dict) -> str:
386386
"task_type": task_data.get("task_type", "single"),
387387
"swarm_details": task_data.get("swarm_details") # Will be None for single tasks
388388
}
389-
390-
SENSITIVE_TASK_FIELDS = ["name", "description", "plan", "runs", "original_context", "chat_history", "error", "found_context", "clarifying_questions", "result", "swarm_details"]
389+
SENSITIVE_TASK_FIELDS = ["name", "description", "plan", "runs", "original_context", "chat_history", "error", "clarifying_questions", "result", "swarm_details"]
391390
_encrypt_doc(task_doc, SENSITIVE_TASK_FIELDS)
392391

393392
await self.task_collection.insert_one(task_doc)
@@ -397,22 +396,22 @@ async def add_task(self, user_id: str, task_data: dict) -> str:
397396
async def get_task(self, task_id: str, user_id: str) -> Optional[Dict]:
398397
"""Fetches a single task by its ID, ensuring it belongs to the user."""
399398
doc = await self.task_collection.find_one({"task_id": task_id, "user_id": user_id})
400-
SENSITIVE_TASK_FIELDS = ["name", "description", "plan", "runs", "original_context", "chat_history", "error", "found_context", "clarifying_questions", "result", "swarm_details"]
399+
SENSITIVE_TASK_FIELDS = ["name", "description", "plan", "runs", "original_context", "chat_history", "error", "clarifying_questions", "result", "swarm_details"]
401400
_decrypt_doc(doc, SENSITIVE_TASK_FIELDS)
402401
return doc
403402

404403
async def get_all_tasks_for_user(self, user_id: str) -> List[Dict]:
405404
"""Fetches all tasks for a given user."""
406405
cursor = self.task_collection.find({"user_id": user_id}).sort("created_at", -1)
407406
docs = await cursor.to_list(length=None)
408-
SENSITIVE_TASK_FIELDS = ["name", "description", "plan", "runs", "original_context", "chat_history", "error", "found_context", "clarifying_questions", "result", "swarm_details"]
407+
SENSITIVE_TASK_FIELDS = ["name", "description", "plan", "runs", "original_context", "chat_history", "error", "clarifying_questions", "result", "swarm_details"]
409408
_decrypt_docs(docs, SENSITIVE_TASK_FIELDS)
410409
return docs
411410

412411
async def update_task(self, task_id: str, updates: Dict) -> bool:
413412
"""Updates an existing task document."""
414413
updates["updated_at"] = datetime.datetime.now(datetime.timezone.utc)
415-
SENSITIVE_TASK_FIELDS = ["name", "description", "plan", "runs", "original_context", "chat_history", "error", "found_context", "clarifying_questions", "result", "swarm_details"]
414+
SENSITIVE_TASK_FIELDS = ["name", "description", "plan", "runs", "original_context", "chat_history", "error", "clarifying_questions", "result", "swarm_details"]
416415
_encrypt_doc(updates, SENSITIVE_TASK_FIELDS)
417416
result = await self.task_collection.update_one(
418417
{"task_id": task_id},

src/server/workers/executor/tasks.py

Lines changed: 72 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -266,14 +266,12 @@ async def async_execute_task_plan(task_id: str, user_id: str, run_id: str):
266266

267267
plan_description = task.get("name", "Unnamed plan")
268268
original_context_str = json.dumps(original_context_data, indent=2, default=str) if original_context_data else "No original context provided."
269-
found_context_str = task.get("found_context", "No additional context was found by the research agent.")
270269
block_id_prompt = f"The block_id for this task is '{block_id}'. You MUST pass this ID to the 'update_progress' tool in the 'block_id' parameter." if block_id else "This task did not originate from a tasks block."
271270

272271
full_plan_prompt = (
273272
f"You are Sentient, a resourceful and autonomous executor agent. Your goal is to complete the user's request by intelligently following the provided plan.\n\n" # noqa
274273
f"**User Context:**\n- **User's Name:** {user_name}\n- **User's Location:** {user_location}\n- **Current Date & Time:** {current_user_time}\n\n"
275274
f"{trigger_event_prompt_section}"
276-
f"**Retrieved Context (from research agent):**\n{found_context_str}\n\n"
277275
f"Your task ID is '{task_id}' and the current run ID is '{run_id}'.\n\n"
278276
f"The original context that triggered this plan is:\n---BEGIN CONTEXT---\n{original_context_str}\n---END CONTEXT---\n\n"
279277
f"**Primary Objective:** '{plan_description}'\n\n"
@@ -292,41 +290,91 @@ async def async_execute_task_plan(task_id: str, user_id: str, run_id: str):
292290
)
293291

294292
try:
295-
initial_messages = [{'role': 'user', 'content': "Begin executing the plan. Follow your instructions meticulously."}]
296-
297293
logger.info(f"Task {task_id}: Starting agent run.")
294+
initial_messages = [{'role': 'user', 'content': "Begin executing the plan. Follow your instructions meticulously."}]
298295

299-
final_assistant_content = ""
300-
final_history = []
301-
for current_history in run_main_agent(
296+
# Step 1: Fully exhaust the generator to let the agent run to completion.
297+
# This prevents async UI updates from interfering with the agent's internal loop.
298+
agent_generator = run_main_agent(
302299
system_message=full_plan_prompt,
303300
function_list=tools_config,
304301
messages=initial_messages
305-
):
306-
final_history = current_history
307-
308-
# After the loop, process the complete response
309-
if final_history and final_history[-1].get("role") == "assistant":
310-
final_assistant_content = final_history[-1].get("content", "")
311-
312-
final_answer_content = ""
313-
if final_assistant_content:
314-
parsed_updates = parse_agent_string_to_updates(final_assistant_content)
315-
for update in parsed_updates:
316-
# Per user request, do not push thoughts to the log, but parse everything else.
317-
if update.get("type") == "thought":
318-
continue
319-
await add_progress_update(db, task_id, run_id, user_id, update, block_id)
320-
if update.get("type") == "final_answer":
321-
final_answer_content = update.get("content")
302+
)
303+
all_history_steps = list(agent_generator)
304+
305+
if not all_history_steps:
306+
raise Exception("Agent run produced no history, indicating an immediate failure.")
307+
308+
# Step 2: After completion, process all history steps to send UI updates.
309+
last_history_len = len(initial_messages)
310+
for current_history in all_history_steps:
311+
new_messages = current_history[last_history_len:]
312+
for msg in new_messages:
313+
updates_to_push = []
314+
if msg.get("role") == "assistant":
315+
if msg.get("content"):
316+
updates_to_push.extend(parse_agent_string_to_updates(msg["content"]))
317+
if msg.get("function_call"):
318+
fc = msg["function_call"]
319+
params_str = fc.get("arguments", "{}")
320+
params = JsonExtractor.extract_valid_json(params_str) or {"raw_parameters": params_str}
321+
updates_to_push.append({
322+
"type": "tool_call",
323+
"tool_name": fc.get("name"),
324+
"parameters": params
325+
})
326+
elif msg.get("role") == "function":
327+
result_str = msg.get("content", "{}")
328+
result_content = JsonExtractor.extract_valid_json(result_str) or {"raw_result": result_str}
329+
is_error = isinstance(result_content, dict) and result_content.get("status") == "failure"
330+
updates_to_push.append({
331+
"type": "tool_result",
332+
"tool_name": msg.get("name"),
333+
"result": result_content.get("result", result_content.get("error", result_content)),
334+
"is_error": is_error
335+
})
336+
337+
for update in updates_to_push:
338+
if update.get("type") != "thought":
339+
asyncio.create_task(add_progress_update(db, task_id, run_id, user_id, update, block_id))
340+
last_history_len = len(current_history)
341+
342+
# Step 3: Check the final state for a valid answer.
343+
final_history = all_history_steps[-1]
344+
final_assistant_message = next((msg for msg in reversed(final_history) if msg.get("role") == "assistant"), None)
345+
346+
has_final_answer = False
347+
if final_assistant_message and final_assistant_message.get("content"):
348+
parsed_updates = parse_agent_string_to_updates(final_assistant_message["content"])
349+
if any(upd.get("type") == "final_answer" for upd in parsed_updates):
350+
has_final_answer = True
351+
352+
if not has_final_answer:
353+
error_message = "Agent finished execution without providing a final answer as required by its instructions. The task may be incomplete."
354+
logger.error(f"Task {task_id}: {error_message}. Final history: {final_history}")
355+
await add_progress_update(db, task_id, run_id, user_id, {"type": "error", "content": error_message}, block_id=block_id)
356+
await update_task_run_status(db, task_id, run_id, "error", user_id, details={"error": error_message}, block_id=block_id)
357+
358+
from workers.tasks import calculate_next_run
359+
schedule_type = task.get('schedule', {}).get('type')
360+
if schedule_type == 'recurring':
361+
next_run_time, _ = calculate_next_run(task['schedule'], last_run=datetime.datetime.now(datetime.timezone.utc))
362+
await db.tasks.update_one({"_id": task["_id"]}, {"$set": {"status": "active", "next_execution_at": next_run_time}})
363+
elif schedule_type == 'triggered':
364+
await db.tasks.update_one({"_id": task["_id"]}, {"$set": {"status": "active", "next_execution_at": None}})
365+
else:
366+
await db.tasks.update_one({"_id": task["_id"]}, {"$set": {"status": "error", "next_execution_at": None}})
367+
return {"status": "error", "message": error_message}
322368

369+
# If we have a final answer, the execution was successful.
323370
logger.info(f"Task {task_id} execution phase completed. Dispatching to result generator.")
324371
await add_progress_update(db, task_id, run_id, user_id, {"type": "info", "content": "Execution finished. Generating final report..."}, block_id=block_id)
325372
await update_task_run_status(db, task_id, run_id, "completed", user_id, block_id=block_id)
326373
capture_event(user_id, "task_execution_succeeded", {"task_id": task_id, "run_id": run_id})
327374

328375
# Call the new result generator task
329376
generate_task_result.delay(task_id, run_id, user_id)
377+
330378
from workers.tasks import calculate_next_run
331379
schedule_type = task.get('schedule', {}).get('type')
332380
if schedule_type == 'recurring':

src/server/workers/planner/db.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ async def create_initial_task(self, user_id: str, name: str, description: str, a
110110
"chat_history": [],
111111
}
112112

113-
SENSITIVE_TASK_FIELDS = ["name", "description", "original_context"]
113+
SENSITIVE_TASK_FIELDS = ["name", "description", "plan", "runs", "original_context", "chat_history", "error", "clarifying_questions", "result", "swarm_details"]
114114
_encrypt_doc(task_doc, SENSITIVE_TASK_FIELDS)
115115

116116
await self.tasks_collection.insert_one(task_doc)
@@ -120,7 +120,7 @@ async def create_initial_task(self, user_id: str, name: str, description: str, a
120120
async def update_task_field(self, task_id: str, fields: dict):
121121
"""Updates specific fields of a task document."""
122122
if DB_ENCRYPTION_ENABLED:
123-
SENSITIVE_TASK_FIELDS = ["name", "description", "plan", "runs", "original_context", "chat_history", "error", "found_context", "clarifying_questions", "result", "swarm_details"]
123+
SENSITIVE_TASK_FIELDS = ["name", "description", "plan", "runs", "original_context", "chat_history", "error", "clarifying_questions", "result", "swarm_details"]
124124
encrypted_fields = {}
125125
for field, value in fields.items():
126126
if field in SENSITIVE_TASK_FIELDS:
@@ -166,7 +166,7 @@ async def update_task_with_plan(self, task_id: str, plan_data: dict, is_change_r
166166
async def get_task(self, task_id: str) -> Optional[Dict]:
167167
"""Fetches a single task by its ID."""
168168
doc = await self.tasks_collection.find_one({"task_id": task_id})
169-
SENSITIVE_TASK_FIELDS = ["name", "description", "plan", "runs", "original_context", "chat_history", "error", "found_context", "clarifying_questions", "result", "swarm_details"]
169+
SENSITIVE_TASK_FIELDS = ["name", "description", "plan", "runs", "original_context", "chat_history", "error", "clarifying_questions", "result", "swarm_details"]
170170
_decrypt_doc(doc, SENSITIVE_TASK_FIELDS)
171171
return doc
172172

src/server/workers/planner/llm.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,19 @@
99

1010
logger = logging.getLogger(__name__)
1111

12-
def get_planner_agent(available_tools: dict, current_time_str: str, user_name: str, user_location: str, retrieved_context: dict = None):
12+
def get_planner_agent(available_tools: dict, current_time_str: str, user_name: str, user_location: str):
1313
"""Initializes and returns a Qwen Assistant agent for planning."""
1414

1515
# Format the MCP descriptions for the prompt
1616
# The keys are now the simple names (e.g., 'gmail')
1717
tools_list_str = "\n".join([f"- {name}: {desc}" for name, desc in available_tools.items()])
1818

19-
# Format the retrieved context for the prompt
20-
context_str = json.dumps(retrieved_context, indent=2) if retrieved_context else "No additional context was found in memory."
21-
2219
# Add current time to the prompt for better contextual planning
2320
system_prompt = prompts.SYSTEM_PROMPT.format(
2421
user_name=user_name,
2522
user_location=user_location,
2623
current_time=current_time_str,
27-
available_tools=tools_list_str, # This is the new field
28-
retrieved_context=context_str # This is the new field
24+
available_tools=tools_list_str
2925
)
3026

3127
# This function now returns the necessary components to run the agent with fallback

0 commit comments

Comments
 (0)