|
24 | 24 |
|
25 | 25 | import json |
26 | 26 | import os |
| 27 | +import re as _re |
27 | 28 | import threading |
28 | 29 | from typing import Any |
29 | 30 |
|
30 | 31 | _PROGRESS_REL = os.path.join(".olympian", "PROGRESS.md") |
31 | 32 | _CHECKLIST_HEADER = "## Checklist" |
32 | 33 | _FINDINGS_HEADER = "## Findings" |
33 | | -_FINDINGS_BUDGET = 12_000 # max chars kept in Findings (drop oldest beyond this) — keep re-reads cheap |
34 | | -_ENTRY_CAP = 2_000 # max chars of any single subagent report |
| 34 | +_FINDINGS_BUDGET = 50_000 # max chars kept in Findings (drop oldest beyond this) |
| 35 | +_ENTRY_CAP = 12_000 # max chars of any single subagent report (holds a thorough survey whole) |
35 | 36 |
|
36 | 37 | _LOCK = threading.Lock() |
37 | 38 | _STATE: dict[str, Any] = { |
@@ -111,6 +112,10 @@ def _seed() -> None: |
111 | 112 | checklist, findings = _split(_read()) |
112 | 113 | _STATE["checklist"] = checklist |
113 | 114 | _STATE["findings"] = findings |
| 115 | + # Continue Findings numbering from the highest existing entry so a retry (a fresh process |
| 116 | + # that resumes this file) doesn't restart at 1 and produce a jumbled "1, 2, 1" sequence. |
| 117 | + nums = [int(m) for m in _re.findall(r"(?m)^### (\d+)\.", findings or "")] |
| 118 | + _STATE["delegations"] = max(nums) if nums else 0 |
114 | 119 |
|
115 | 120 |
|
116 | 121 | def _flush() -> None: |
@@ -154,29 +159,120 @@ def _render_checklist(result: Any) -> str: |
154 | 159 | return "\n".join(lines) |
155 | 160 |
|
156 | 161 |
|
157 | | -def _summarise_delegation(result: Any) -> str: |
158 | | - def one(r: Any) -> str: |
159 | | - if isinstance(r, dict): |
160 | | - return str( |
161 | | - r.get("summary") |
162 | | - or r.get("error") |
163 | | - or r.get("result") |
164 | | - or json.dumps(r, ensure_ascii=False, default=str) |
165 | | - ) |
| 162 | +_THINK_TAGS = _re.compile(r"<(antThinking|think|thinking|reasoning)>.*?</\\1>", _re.DOTALL | _re.IGNORECASE) |
| 163 | + |
| 164 | + |
| 165 | +def _strip_thinking(text: str) -> str: |
| 166 | + return _THINK_TAGS.sub("", text).strip() |
| 167 | + |
| 168 | + |
| 169 | +_HEADING_RE = _re.compile(r"^[ \t]*(#{1,6})[ \t]+(.+?)[ \t]*#*$") |
| 170 | + |
| 171 | + |
| 172 | +def _relevel_headings(text: str, base: int = 4) -> str: |
| 173 | + """Re-base the headings in captured (model-authored) goal/report text so the shallowest sits at |
| 174 | + ``base`` — one level below a "### N." Findings entry (h3) — while preserving the content's own |
| 175 | + relative hierarchy. This keeps the agent's structure intact AND nested correctly under the entry, |
| 176 | + so its headings never sit above, or collide with, the structural ## Findings / ### N. markers |
| 177 | + when the file is injected into a prompt. Headings inside fenced code blocks are left untouched. |
| 178 | + """ |
| 179 | + lines = text.split("\n") |
| 180 | + |
| 181 | + def heading_levels(): |
| 182 | + in_fence = False |
| 183 | + for line in lines: |
| 184 | + s = line.lstrip() |
| 185 | + if s.startswith("```") or s.startswith("~~~"): |
| 186 | + in_fence = not in_fence |
| 187 | + continue |
| 188 | + m = None if in_fence else _HEADING_RE.match(line) |
| 189 | + if m: |
| 190 | + yield len(m.group(1)) |
| 191 | + |
| 192 | + levels = list(heading_levels()) |
| 193 | + if not levels: |
| 194 | + return text |
| 195 | + shift = base - min(levels) |
| 196 | + |
| 197 | + out: list[str] = [] |
| 198 | + in_fence = False |
| 199 | + for line in lines: |
| 200 | + s = line.lstrip() |
| 201 | + if s.startswith("```") or s.startswith("~~~"): |
| 202 | + in_fence = not in_fence |
| 203 | + out.append(line) |
| 204 | + continue |
| 205 | + m = None if in_fence else _HEADING_RE.match(line) |
| 206 | + if m: |
| 207 | + level = max(1, min(6, len(m.group(1)) + shift)) |
| 208 | + out.append("#" * level + " " + m.group(2).strip()) |
| 209 | + else: |
| 210 | + out.append(line) |
| 211 | + return "\n".join(out) |
| 212 | + |
| 213 | + |
| 214 | +_DONE = {"completed", "complete", "success", "succeeded", "ok"} |
| 215 | + |
| 216 | + |
| 217 | +def _one_result(r: Any) -> str: |
| 218 | + """Render a single child task result. Only a COMPLETED child contributes its summary; an |
| 219 | + incomplete one (timeout / max_iterations / error / interrupted) is recorded as a short marker |
| 220 | + instead of its truncated mid-thought narration — capturing that pollutes Findings.""" |
| 221 | + if not isinstance(r, dict): |
166 | 222 | return str(r) |
| 223 | + status = str(r.get("status") or "").strip().lower() |
| 224 | + exit_reason = str(r.get("exit_reason") or "").strip().lower() |
| 225 | + # No status fields at all (older/other shapes) → treat as a plain result. |
| 226 | + if (status or exit_reason) and status not in _DONE and exit_reason not in _DONE: |
| 227 | + why = status or exit_reason or "unknown" |
| 228 | + detail = str(r.get("error") or "").strip() or "cut off before producing a final report" |
| 229 | + return f"⚠️ Sub-agent did not finish (status: {why}) — {detail[:200]}" |
| 230 | + return str(r.get("summary") or r.get("result") or r.get("error") or "") |
| 231 | + |
| 232 | + |
| 233 | +def _summarise_delegation(result: Any) -> str: |
| 234 | + one = _one_result |
167 | 235 |
|
168 | 236 | if isinstance(result, str): |
169 | 237 | try: |
170 | 238 | result = json.loads(result) |
171 | 239 | except Exception: |
172 | | - return result.strip()[:_ENTRY_CAP] |
173 | | - text = "\n".join(one(r) for r in result) if isinstance(result, list) else one(result) |
174 | | - text = text.strip() |
| 240 | + return _strip_thinking(result)[:_ENTRY_CAP] |
| 241 | + |
| 242 | + # delegate_task returns {"results": [ {summary,...}, ... ], "note": ...}; older/other shapes |
| 243 | + # may be a bare list or dict. |
| 244 | + if isinstance(result, dict) and isinstance(result.get("results"), list): |
| 245 | + items = result["results"] |
| 246 | + elif isinstance(result, list): |
| 247 | + items = result |
| 248 | + else: |
| 249 | + items = [result] |
| 250 | + |
| 251 | + text = "\n".join(p for p in (one(r) for r in items) if p.strip()) |
| 252 | + text = _strip_thinking(text) |
| 253 | + text = _relevel_headings(text) |
175 | 254 | if len(text) > _ENTRY_CAP: |
176 | 255 | text = text[:_ENTRY_CAP].rstrip() + " …[trimmed]" |
177 | 256 | return text |
178 | 257 |
|
179 | 258 |
|
| 259 | +def _heading_label(goal: Any, cap: int = 100) -> str: |
| 260 | + """A short one-line label for a Findings heading. The primary writes the whole task as a single |
| 261 | + long line, so we take the first line, cut it at the first sentence/clause boundary if that is |
| 262 | + short enough, then hard-cap on a word boundary.""" |
| 263 | + line = str(goal or "").strip().splitlines() |
| 264 | + line = line[0].strip() if line else "" |
| 265 | + # Cut at the first clause/sentence boundary that is actually present and short enough. |
| 266 | + for sep in (": ", ". "): |
| 267 | + if sep in line: |
| 268 | + head = line.split(sep, 1)[0] |
| 269 | + if 0 < len(head) <= cap: |
| 270 | + return head |
| 271 | + if len(line) > cap: |
| 272 | + line = line[:cap].rsplit(" ", 1)[0].rstrip(" ,.;:") + "…" |
| 273 | + return line |
| 274 | + |
| 275 | + |
180 | 276 | def _is_dispatch_ack(result): |
181 | 277 | """A background delegation's post-hook result is only a dispatch acknowledgement, not the |
182 | 278 | subagent's report (that arrives asynchronously) — recognise it so we don't record it.""" |
@@ -254,11 +350,14 @@ def on_post_tool_call( |
254 | 350 | if not _is_dispatch_ack(result): |
255 | 351 | _STATE["delegations"] += 1 |
256 | 352 | n = _STATE["delegations"] |
257 | | - goal = "" |
258 | | - if isinstance(args, dict): |
259 | | - first = str(args.get("goal", "")).strip().splitlines() |
260 | | - goal = first[0] if first else "" |
261 | | - entry = (f"### {n}. {goal}".rstrip()) + "\n" + _summarise_delegation(result) + "\n" |
| 353 | + goal_full = str(args.get("goal", "")).strip() if isinstance(args, dict) else "" |
| 354 | + label = _heading_label(goal_full) |
| 355 | + # Short heading for readability; the full goal is kept as body so no context is |
| 356 | + # lost, then the subagent's report. |
| 357 | + entry = f"### {n}. {label}".rstrip() + "\n" |
| 358 | + if goal_full and goal_full != label: |
| 359 | + entry += f"**Goal:** {_relevel_headings(goal_full)}\n\n" |
| 360 | + entry += _summarise_delegation(result) + "\n" |
262 | 361 | prior = _STATE["findings"] or "" |
263 | 362 | _STATE["findings"] = (prior + "\n\n" + entry) if prior else entry |
264 | 363 | _flush() |
|
0 commit comments