Start a single coding agent for a feature. Args: feature_id: ID of the feature to start resume: If True, resume a feature that's already in_progress from a previous session Returns: Tuple of (success, message)
(self, feature_id: int, resume: bool = False)
| 694 | return |
| 695 | |
| 696 | def start_feature(self, feature_id: int, resume: bool = False) -> tuple[bool, str]: |
| 697 | """Start a single coding agent for a feature. |
| 698 | |
| 699 | Args: |
| 700 | feature_id: ID of the feature to start |
| 701 | resume: If True, resume a feature that's already in_progress from a previous session |
| 702 | |
| 703 | Returns: |
| 704 | Tuple of (success, message) |
| 705 | """ |
| 706 | with self._lock: |
| 707 | if feature_id in self.running_coding_agents: |
| 708 | return False, "Feature already running" |
| 709 | if len(self.running_coding_agents) >= self.max_concurrency: |
| 710 | return False, "At max concurrency" |
| 711 | # Enforce hard limit on total agents (coding + testing) |
| 712 | total_agents = len(self.running_coding_agents) + len(self.running_testing_agents) |
| 713 | if total_agents >= MAX_TOTAL_AGENTS: |
| 714 | return False, f"At max total agents ({total_agents}/{MAX_TOTAL_AGENTS})" |
| 715 | |
| 716 | # Mark as in_progress in database (or verify it's resumable) |
| 717 | session = self.get_session() |
| 718 | try: |
| 719 | feature = session.query(Feature).filter(Feature.id == feature_id).first() |
| 720 | if not feature: |
| 721 | return False, "Feature not found" |
| 722 | if feature.passes: |
| 723 | return False, "Feature already complete" |
| 724 | |
| 725 | if resume: |
| 726 | # Resuming: feature should already be in_progress |
| 727 | if not feature.in_progress: |
| 728 | return False, "Feature not in progress, cannot resume" |
| 729 | else: |
| 730 | # Starting fresh: feature should not be in_progress |
| 731 | if feature.in_progress: |
| 732 | return False, "Feature already in progress" |
| 733 | feature.in_progress = True |
| 734 | session.commit() |
| 735 | finally: |
| 736 | session.close() |
| 737 | |
| 738 | # Start coding agent subprocess |
| 739 | success, message = self._spawn_coding_agent(feature_id) |
| 740 | if not success: |
| 741 | return False, message |
| 742 | |
| 743 | # NOTE: Testing agents are now maintained independently via _maintain_testing_agents() |
| 744 | # called in the main loop, rather than being spawned when coding agents start. |
| 745 | |
| 746 | return True, f"Started feature {feature_id}" |
| 747 | |
| 748 | def start_feature_batch(self, feature_ids: list[int], resume: bool = False) -> tuple[bool, str]: |
| 749 | """Start a coding agent for a batch of features. |
no test coverage detected