changes to agent runner

This commit is contained in:
james-m-jordan 2025-05-08 12:58:40 -04:00
parent 42e2402258
commit 31c5a16645
4 changed files with 236 additions and 5 deletions

View File

@ -1,7 +1,7 @@
import os
import yaml
import logging
from typing import Dict, Any
from typing import Dict, Any, List
import shutil
from datetime import datetime
import chromadb
@ -162,6 +162,193 @@ class AgentTaskRunner:
if "cell_line" in args:
self._update_user_profile(researcher, "frequent_cell_lines", args["cell_line"])
self._update_user_profile(researcher, "recent_experiments", filename)
# Extract tasks from experiment and prepare for issue creation
if args.get("tasks"):
self.extract_tasks_and_preview(filename, template)
def extract_tasks_and_preview(self, experiment_filename: str, experiment_data: Dict[str, Any]):
"""
Extract tasks from an experiment and preview them before creating GitHub issues.
Args:
experiment_filename: The filename of the experiment YAML
experiment_data: The experiment data dictionary
"""
tasks = experiment_data.get("tasks", [])
if not tasks:
self.logger.info(f"No tasks found in experiment {experiment_filename}")
return
# Get other relevant experiment metadata
experiment_id = experiment_data.get("experiment_id", experiment_filename)
title = experiment_data.get("title", "Untitled Experiment")
assignees = experiment_data.get("assignees", {})
# Format tasks with assignees for preview
preview_tasks = []
for i, task in enumerate(tasks):
task_description = task.get("description", "No description")
task_assignee = task.get("assignee")
assignee_username = assignees.get(task_assignee, "unassigned") if task_assignee else "unassigned"
preview_tasks.append({
"id": i + 1,
"description": task_description,
"assignee": assignee_username,
"due_date": task.get("due_date", "No due date"),
"experiment_id": experiment_id
})
# Log the preview
self.logger.info(f"Preview of tasks for experiment {experiment_id}:")
for task in preview_tasks:
self.logger.info(f"Task #{task['id']}: {task['description']} (Assignee: {task['assignee']}, Due: {task['due_date']})")
# Store the preview for confirmation
self._store_task_preview(experiment_id, preview_tasks)
# Create a GitHub issue for task preview confirmation
preview_body = f"Experiment: {title} ({experiment_id})\n\n"
preview_body += "The following tasks will be created as GitHub issues:\n\n"
for task in preview_tasks:
preview_body += f"- #{task['id']}: {task['description']} (Assignee: {task['assignee']}, Due: {task['due_date']})\n"
preview_body += "\nTo confirm and create these issues, please comment 'confirm' on this issue."
self.handle_open_issue({
"title": f"Task Preview for {experiment_id}: {title}",
"body": preview_body,
"labels": ["task-preview"]
})
def _store_task_preview(self, experiment_id: str, tasks: List[Dict[str, Any]]):
"""
Store task preview data for later confirmation.
Args:
experiment_id: The experiment identifier
tasks: List of task dictionaries
"""
preview_path = os.path.join("Data", "task_previews")
os.makedirs(preview_path, exist_ok=True)
preview_file = os.path.join(preview_path, f"{experiment_id}_task_preview.json")
with open(preview_file, "w") as f:
json.dump(tasks, f, indent=2)
self.logger.info(f"Stored task preview at {preview_file}")
def handle_confirm_tasks(self, args: Dict[str, Any]):
"""
Create GitHub issues from previously previewed tasks.
Args:
args: Dictionary with experiment_id to confirm tasks for
"""
experiment_id = args.get("experiment_id")
if not experiment_id:
self.logger.error("Missing experiment_id for confirm_tasks.")
return
preview_file = os.path.join("Data", "task_previews", f"{experiment_id}_task_preview.json")
if not os.path.exists(preview_file):
self.logger.error(f"No task preview found for experiment {experiment_id}")
return
with open(preview_file, "r") as f:
tasks = json.load(f)
for task in tasks:
issue_title = f"[{experiment_id}] Task #{task['id']}: {task['description']}"
issue_body = f"Experiment: {experiment_id}\n"
issue_body += f"Description: {task['description']}\n"
issue_body += f"Due Date: {task.get('due_date', 'Not specified')}\n\n"
issue_body += f"This task is part of experiment {experiment_id}."
# Create the issue
self.handle_open_issue({
"title": issue_title,
"body": issue_body,
"assignee": task.get("assignee"),
"labels": ["experiment-task", experiment_id]
})
# Mark as created in TASKS.md
self.add_task_to_tasks_md(task['description'], experiment_id)
# Remove the preview file after creating issues
os.remove(preview_file)
self.logger.info(f"Created {len(tasks)} GitHub issues for experiment {experiment_id}")
# Update the experiment YAML to indicate tasks were created
self.update_experiment_tasks_status(experiment_id)
def add_task_to_tasks_md(self, task_description: str, experiment_id: str):
"""
Add a task entry to TASKS.md.
Args:
task_description: Description of the task
experiment_id: ID of the experiment the task belongs to
"""
if not os.path.exists("TASKS.md"):
with open("TASKS.md", "w") as f:
f.write("# Lab Tasks\n\n")
with open("TASKS.md", "r") as f:
lines = f.readlines()
# Find the Lab Tasks section or create it
lab_tasks_index = -1
for i, line in enumerate(lines):
if line.strip() == "# Lab Tasks" or line.strip() == "## Lab Tasks":
lab_tasks_index = i
break
if lab_tasks_index == -1:
lines.append("\n## Lab Tasks\n\n")
lab_tasks_index = len(lines) - 3
# Add the new task after the Lab Tasks heading
task_line = f"- [ ] {task_description} ({experiment_id})\n"
lines.insert(lab_tasks_index + 2, task_line)
with open("TASKS.md", "w") as f:
f.writelines(lines)
self.logger.info(f"Added task to TASKS.md: {task_description}")
def update_experiment_tasks_status(self, experiment_id: str):
"""
Update the experiment YAML file to indicate tasks were created.
Args:
experiment_id: ID of the experiment
"""
# Find experiment file
exp_dir = "Experiments"
exp_file = None
for fname in os.listdir(exp_dir):
if experiment_id in fname:
exp_file = os.path.join(exp_dir, fname)
break
if not exp_file or not os.path.exists(exp_file):
self.logger.error(f"Experiment file not found for id: {experiment_id}")
return
# Update the experiment YAML
with open(exp_file, "r") as f:
exp = yaml.safe_load(f)
if "tasks" in exp:
for task in exp["tasks"]:
task["github_issue_created"] = True
with open(exp_file, "w") as f:
yaml.dump(exp, f, sort_keys=False)
self.logger.info(f"Updated experiment {experiment_id} to mark tasks as created")
def handle_update_experiment(self, args: Dict[str, Any]):
"""
@ -204,6 +391,12 @@ class AgentTaskRunner:
self.append_changelog(f"Updated experiment {experiment_id}: {list(updates.keys())}")
# Optionally update tasks
self.mark_task_done_for_experiment(experiment_id)
# If tasks have been added or modified, handle task preview
if "tasks" in updates and updates["tasks"]:
with open(exp_file, "r") as f:
updated_exp = yaml.safe_load(f)
self.extract_tasks_and_preview(experiment_id, updated_exp)
def append_changelog(self, entry: str):
with open("CHANGELOG.md", "a") as f:
@ -303,17 +496,51 @@ class AgentTaskRunner:
def handle_open_issue(self, args: Dict[str, Any]):
"""
Handle opening a GitHub issue via CLI (gh).
Args should include: title (str), body (str)
Args should include: title (str), body (str), assignee (str, optional), labels (list, optional)
"""
title = args.get("title", "Lab Agent Issue")
body = args.get("body", "Created by lab agent.")
assignee = args.get("assignee")
labels = args.get("labels", [])
# Build the command
cmd = f'gh issue create --title "{title}" --body "{body}"'
# Add assignee if provided
if assignee and assignee != "unassigned":
cmd += f' --assignee "{assignee}"'
# Add labels if provided
if labels:
label_str = ",".join(labels)
cmd += f' --label "{label_str}"'
result = os.system(cmd)
if result == 0:
self.logger.info(f"Opened GitHub issue: {title}")
# Add to ISSUES_LOG.md
self._add_to_issues_log(title, assignee)
else:
self.logger.error(f"Failed to open GitHub issue: {title}")
def _add_to_issues_log(self, title: str, assignee: str = None):
"""
Add an entry to ISSUES_LOG.md
Args:
title: Issue title
assignee: GitHub username of assignee (optional)
"""
if not os.path.exists("ISSUES_LOG.md"):
with open("ISSUES_LOG.md", "w") as f:
f.write("# GitHub Issues Log\n\n")
with open("ISSUES_LOG.md", "a") as f:
entry = f"- {datetime.now().strftime('%Y-%m-%d %H:%M')}: {title}"
if assignee and assignee != "unassigned":
entry += f" (Assigned to: {assignee})"
f.write(entry + "\n")
def handle_open_pr(self, args: Dict[str, Any]):
"""
Handle opening a draft pull request via CLI (gh).

View File

@ -6,6 +6,7 @@ date: 2025-05-08
researchers:
- james-m-jordan
- linda-onsei
- SanjanaC
protocol_id: PROT-0036
protocol_name: "Adipogenic Induction Treatment"
status: planned # planned | in-progress | completed | failed
@ -24,6 +25,7 @@ condition_map: |
Dish 1-3: 3T3 + Control medium (24h)
Dish 4-6: 3T3 + Adipogenic induction medium (24h)
replicates: 3
sample_location: "-80°C freezer, rack 1-1-4-A"
---
---
@ -250,7 +252,7 @@ Analysis script: `Analysis/EXP-0226_CoIP_quantification.R`
_To be completed after experiment_
## Comparison to Previous Iteration
_Compare results with first iteration of this experiment_
_Compare results with first iteration of this experiment (EXP-0224)_
## Relation to Project Goals
This experiment directly addresses our hypothesis that YBX1 and C/EBPα physically interact during early adipogenesis. By comparing 3T3 cells with and without adipogenic stimulation, we can determine if this interaction is enhanced during the early stages of adipocyte differentiation (24h post-induction).
@ -267,11 +269,11 @@ _Check boxes when complete. These can auto-update TASKS.md._
# 6⃣ Team Discussion
_Use this section for team comments, suggestions, and feedback._
> **james-m-jordan (2025-05-07):** This is the second iteration of this experiment. In the first iteration (EXP-0218), we saw a weak interaction in control conditions that was strongly enhanced after adipogenic stimulation. Let's make sure our lysis conditions are optimal for capturing these interactions.
> **james-m-jordan (2025-05-07):** This is the second iteration of this experiment. In the first iteration (EXP-0224), we saw a weak interaction in control conditions that was strongly enhanced after adipogenic stimulation. Let's make sure our lysis conditions are optimal for capturing these interactions.
> **linda-onsei (2025-05-07):** Should we also check protein levels by straight Western blot? I'm wondering if the increased interaction is partly due to increased expression of either protein.
# 7⃣ References & Related Experiments
- Related protocol: [Adipogenic Induction Treatment](Protocols/adipogenic_induction_treatment_v1.yaml)
- Previous experiment: [EXP-0218](Experiments/EXP-0218_YBX1_CEBPA_interaction_3T3.md)
- Previous experiment: [EXP-0224](Experiments/EXP-0224-YBX1-CEBPA-CoIP-3T3-adipogenesis.md)
- Literature: Girard J, et al. (2018) YBX1 interacts with C/EBP transcription factors to regulate adipogenesis. Cell Reports 25:788-801.

View File

@ -29,6 +29,7 @@ condition_map: |
C1-C6: CELL_LINE2 + TREATMENT1
D1-D6: CELL_LINE2 + TREATMENT2
replicates: 6
sample_location: "-80°C freezer, rack X-X-X-X"
---
---

View File

@ -4,6 +4,7 @@ project: Project_Name
researcher: username
status: in_progress
created: YYYY-MM-DD
sample_location: "-80°C freezer, rack X-X-X-X"
plate:
id: PLATE_ID