@1mancompany/onemancompany 0.7.48 → 0.7.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -91,6 +91,10 @@ class TaskNode:
|
|
|
91
91
|
# Used to cap infinite retry loops (e.g. EA keeps retrying a failing child).
|
|
92
92
|
retry_count: int = 0
|
|
93
93
|
|
|
94
|
+
# How many times this node was retried due to stall detection
|
|
95
|
+
# (agent promised action but didn't call tools). Capped at MAX_STALL_RETRIES.
|
|
96
|
+
stall_retry_count: int = 0
|
|
97
|
+
|
|
94
98
|
# --- Content externalization tracking (not part of equality/repr) ---
|
|
95
99
|
_content_dirty: bool = field(default=False, init=False, repr=False, compare=False)
|
|
96
100
|
_content_loaded: bool = field(default=False, init=False, repr=False, compare=False)
|
|
@@ -210,6 +214,7 @@ class TaskNode:
|
|
|
210
214
|
"hold_reason": self.hold_reason,
|
|
211
215
|
"hold_started_at": self.hold_started_at,
|
|
212
216
|
"retry_count": self.retry_count,
|
|
217
|
+
"stall_retry_count": self.stall_retry_count,
|
|
213
218
|
"directives_count": len(self.directives),
|
|
214
219
|
}
|
|
215
220
|
|
|
@@ -139,11 +139,14 @@ _PROMISE_PATTERNS = _re.compile(
|
|
|
139
139
|
r"我将|接下来|下一步|现在开始|马上开始|即将开始|准备开始"
|
|
140
140
|
r"|我会(?:立即|马上|开始)"
|
|
141
141
|
r"|下面我(?:来|将|要)"
|
|
142
|
+
r"|分配给\w+处理|派遣给\w+负责"
|
|
142
143
|
# English future-action phrases
|
|
143
144
|
r"|I will (?:now |start |begin )|I'll (?:now |start |begin )"
|
|
144
145
|
r"|Let me (?:start|begin|proceed)"
|
|
145
146
|
r"|Next,? I'?(?:ll| will)"
|
|
146
147
|
r"|I'?m going to (?:start|begin)"
|
|
148
|
+
r"|Going to dispatch"
|
|
149
|
+
r"|I need to dispatch_child"
|
|
147
150
|
r")",
|
|
148
151
|
_re.IGNORECASE,
|
|
149
152
|
)
|
|
@@ -160,6 +163,27 @@ def detect_unfulfilled_promises(output: str | None) -> bool:
|
|
|
160
163
|
return bool(_PROMISE_PATTERNS.search(output))
|
|
161
164
|
|
|
162
165
|
|
|
166
|
+
MAX_STALL_RETRIES: int = 2 # max times to re-run a stalled agent before giving up
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _should_retry_stall(node) -> bool:
|
|
170
|
+
"""Check if a completed node should be retried due to stall detection.
|
|
171
|
+
|
|
172
|
+
Returns True if:
|
|
173
|
+
- Node is not a system node
|
|
174
|
+
- Node has no children (didn't actually dispatch)
|
|
175
|
+
- Node output contains promise patterns
|
|
176
|
+
- stall_retry_count < MAX_STALL_RETRIES
|
|
177
|
+
"""
|
|
178
|
+
if node.node_type in SYSTEM_NODE_TYPES:
|
|
179
|
+
return False
|
|
180
|
+
if node.children_ids:
|
|
181
|
+
return False
|
|
182
|
+
if not detect_unfulfilled_promises(node.result):
|
|
183
|
+
return False
|
|
184
|
+
return getattr(node, 'stall_retry_count', 0) < MAX_STALL_RETRIES
|
|
185
|
+
|
|
186
|
+
|
|
163
187
|
# ---------------------------------------------------------------------------
|
|
164
188
|
# Dependency context builder
|
|
165
189
|
# ---------------------------------------------------------------------------
|
|
@@ -1732,18 +1756,42 @@ class EmployeeManager:
|
|
|
1732
1756
|
employee_id, entry.node_id)
|
|
1733
1757
|
|
|
1734
1758
|
# Stall detection: agent said "I will do X" but dispatched no children
|
|
1735
|
-
if (node
|
|
1759
|
+
if _should_retry_stall(node):
|
|
1760
|
+
node.stall_retry_count = getattr(node, 'stall_retry_count', 0) + 1
|
|
1761
|
+
logger.warning(
|
|
1762
|
+
"[STALL] employee={} node={}: output contains action promises "
|
|
1763
|
+
"but no subtasks dispatched. Retrying ({}/{}).",
|
|
1764
|
+
employee_id, entry.node_id,
|
|
1765
|
+
node.stall_retry_count, MAX_STALL_RETRIES,
|
|
1766
|
+
)
|
|
1767
|
+
# Revert to PROCESSING and re-schedule with explicit nudge
|
|
1768
|
+
node.set_status(TaskPhase.PROCESSING)
|
|
1769
|
+
nudge = (
|
|
1770
|
+
"\n\n[SYSTEM] You said you would dispatch tasks but did NOT "
|
|
1771
|
+
"actually call dispatch_child(). You MUST call the tool now. "
|
|
1772
|
+
"Do NOT describe what you plan to do — invoke dispatch_child() directly."
|
|
1773
|
+
)
|
|
1774
|
+
node.result = (node.result or "") + nudge
|
|
1775
|
+
save_tree_async(entry.tree_path)
|
|
1776
|
+
self.schedule_node(employee_id, entry.node_id, entry.tree_path)
|
|
1777
|
+
self._schedule_next(employee_id)
|
|
1778
|
+
self._log_node(employee_id, entry.node_id, "stall_retry",
|
|
1779
|
+
f"Retrying stalled task (attempt {node.stall_retry_count})")
|
|
1780
|
+
return # skip normal completion flow
|
|
1781
|
+
elif (node.node_type not in SYSTEM_NODE_TYPES
|
|
1736
1782
|
and not node.children_ids
|
|
1737
1783
|
and detect_unfulfilled_promises(node.result)):
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
"
|
|
1784
|
+
# Max retries exhausted — warn CEO
|
|
1785
|
+
logger.warning(
|
|
1786
|
+
"[STALL] employee={} node={}: stall retries exhausted ({}/{}). "
|
|
1787
|
+
"Marking COMPLETED with warning.",
|
|
1741
1788
|
employee_id, entry.node_id,
|
|
1789
|
+
getattr(node, 'stall_retry_count', 0), MAX_STALL_RETRIES,
|
|
1742
1790
|
)
|
|
1743
|
-
self._push_to_conversation(
|
|
1791
|
+
self._push_to_conversation(
|
|
1744
1792
|
node,
|
|
1745
|
-
"⚠️ Agent claimed it would execute follow-up work but
|
|
1746
|
-
"create any tasks
|
|
1793
|
+
"⚠️ Agent repeatedly claimed it would execute follow-up work but "
|
|
1794
|
+
"did not create any tasks after multiple retries. Please review and re-dispatch manually.",
|
|
1747
1795
|
)
|
|
1748
1796
|
|
|
1749
1797
|
save_tree_async(entry.tree_path)
|
|
@@ -1799,6 +1847,11 @@ class EmployeeManager:
|
|
|
1799
1847
|
|
|
1800
1848
|
# Unschedule completed node
|
|
1801
1849
|
self.unschedule(employee_id, entry.node_id)
|
|
1850
|
+
|
|
1851
|
+
# Drain any deferred schedules — ensures child tasks dispatched
|
|
1852
|
+
# by tools (e.g. dispatch_child) that hit a sync/async boundary
|
|
1853
|
+
# actually start executing (I1: prevents silent defer).
|
|
1854
|
+
self.drain_pending()
|
|
1802
1855
|
else:
|
|
1803
1856
|
self._publish_node_update(employee_id, node)
|
|
1804
1857
|
|