Skip to content

feat: LLM-driven mission decomposition with /decompose skill#975

Closed
Koan-Bot wants to merge 3 commits into
Anantys-oss:mainfrom
Koan-Bot:koan.atoomic/fix-issue-739
Closed

feat: LLM-driven mission decomposition with /decompose skill#975
Koan-Bot wants to merge 3 commits into
Anantys-oss:mainfrom
Koan-Bot:koan.atoomic/fix-issue-739

Conversation

@Koan-Bot

@Koan-Bot Koan-Bot commented Mar 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds opt-in LLM-driven task decomposition for complex missions. When a mission is tagged [decompose] (or auto: true in config), a lightweight Claude call classifies it as atomic or composite before execution. Composite missions are automatically split into focused, ordered sub-tasks injected into the Pending queue — each with a [group:ID] tag for lineage tracking.

Fixes #739

Changes

  • koan/system-prompts/decompose-mission.md: Classifier prompt with JSON output schema, anti-over-decomposition bias ("when in doubt, choose atomic"), and a 6 sub-task cap
  • koan/app/decompose.py: decompose_mission(), should_decompose(), is_already_decomposed() — mirrors pr_review_learning.py pattern for lightweight LLM calls
  • koan/app/missions.py: inject_subtasks() — replaces parent mission line with ordered sub-task lines tagged [group:ID], inheriting [project:X] tag
  • koan/app/iteration_manager.py: _maybe_decompose() hook at Step 4b (after mission pick, before execution); returns (None, None) to skip parent when sub-tasks are injected
  • koan/skills/core/decompose/: /decompose Telegram skill (aliased /split) that queues missions with [decompose] tag for automatic classification
  • koan/tests/test_decompose.py: 31 tests covering JSON parsing, tag detection, CLI mocking, and inject_subtasks() mutations

Design decisions

  • Opt-in by default: Only decomposes missions tagged [decompose] unless decompose.auto: true in config
  • Fail-safe: Any error (import, CLI, JSON parse) falls through to normal atomic execution — the queue never stalls
  • Skill missions skipped: Missions starting with / are never decomposed (skill-dispatch bypasses classification)
  • Guard against re-decomposition: [group:*] and [decomposed:*] tags prevent sub-tasks from being re-decomposed
  • Phase 5 (approval gate) skipped: As agreed in issue comments

Test plan

  • Run KOAN_ROOT=/tmp/test-koan .venv/bin/pytest koan/tests/test_decompose.py -v — 31 tests pass
  • Run full suite KOAN_ROOT=/tmp/test-koan .venv/bin/pytest koan/tests/ -q — 10,324 tests pass, no regressions
  • Manual smoke test: add [decompose] tag to a test mission, watch iteration_manager inject sub-tasks

🤖 Generated by Kōan /fix


Quality Report

Changes: 7 files changed, 768 insertions(+)

Code scan: 8 issue(s) found

  • koan/app/decompose.py:60 — debug print statement
  • koan/app/decompose.py:66 — debug print statement
  • koan/app/decompose.py:90 — debug print statement
  • koan/app/decompose.py:97 — debug print statement
  • koan/app/decompose.py:115 — debug print statement
  • koan/app/decompose.py:121 — debug print statement
  • koan/app/decompose.py:130 — debug print statement
  • koan/app/decompose.py:139 — debug print statement

Tests: passed (10 PASSED)

Branch hygiene: clean

Generated by Kōan post-mission quality pipeline

@atoomic atoomic marked this pull request as ready for review March 21, 2026 04:16
@atoomic

atoomic commented Mar 24, 2026

Copy link
Copy Markdown
Collaborator

@Koan-Bot review

@Koan-Bot

Copy link
Copy Markdown
Collaborator Author

PR Review — feat: LLM-driven mission decomposition with /decompose skill

Solid feature with good fail-safe design and thorough tests. However, there is one critical bug: the inject_subtasks fallback path (line 378 of missions.py) only inserts the last subtask when the parent line can't be found, silently dropping all others. Additionally, the greedy JSON regex in the parser could misparse output with multiple brace groups, and the 8 raw print() calls should use the project's logging conventions. The action='autonomous' return after decomposition may cause a wasted exploration cycle instead of immediately picking up the new sub-tasks. Fix the critical subtask-loss bug and the logging before merging.


🔴 Blocking

1. Fallback only inserts the last subtask, silently drops the rest (koan/app/missions.py, L378)
When the parent mission line cannot be found (neither verbatim nor without timestamps), the fallback calls insert_mission() with only subtask_lines[-1]. All other sub-tasks are silently lost.

This should loop over all subtask_lines and insert each one, or — better — join them all and insert as a block. As written, a decomposition into 4 sub-tasks where the parent line doesn't match exactly will result in only 1 sub-task being queued.

Suggested fix:

# Parent not found — append all sub-tasks at bottom of pending
for line in subtask_lines:
    content = insert_mission(content, line, urgent=False)
return content
return insert_mission(content, subtask_lines[-1], urgent=False)

🟡 Important

1. Greedy regex can match across multiple JSON objects (koan/app/decompose.py, L119)
re.search(r'\{.*\}', output, re.DOTALL) is greedy — it matches from the first { to the last } in the entire output. If Claude produces output like {"type": "atomic"} some text with {braces}, the match will be {"type": "atomic"} some text with {braces}, which will fail JSON parsing.

Use a non-greedy match \{.*?\} first and fall back, or better — try json.loads on progressively larger substrings. The simplest fix:

json_match = re.search(r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}', output)

Or try non-greedy first, then greedy as fallback.

json_match = re.search(r'\{.*\}', output, re.DOTALL)

2. 8 debug print() statements should use structured logging (koan/app/decompose.py, L60-139)
The quality scan flagged 8 print(..., file=sys.stderr) calls. The rest of the codebase uses _log_iteration() (in iteration_manager) or the bridge logging system. Raw print() to stderr bypasses any log level filtering, formatting, or rotation.

Since decompose.py is called from iteration_manager.py, consider either:

  1. Accepting a logger/callback parameter, or
  2. Using Python's logging module with a named logger (logging.getLogger("koan.decompose")), or
  3. At minimum, importing and reusing the existing log pattern from the codebase.

This is the issue the quality report flagged — all 8 occurrences.

print(f"[decompose] Import error: {e}", file=sys.stderr)

3. time.time() in group_id makes decomposition non-deterministic (koan/app/iteration_manager.py, L255-259)
Using time.time() in the hash input means the same mission decomposed twice will produce different group IDs. This makes testing harder and prevents idempotent retries. Consider using a hash of just the mission text + project name, which gives stable IDs for the same mission while still being unique across different missions.

group_id = hashlib.sha256(
    f"{mission_project}:{mission_title}".encode()
).hexdigest()[:8]
group_id = hashlib.sha256(
    f"{mission_title}{_time.time()}".encode()
).hexdigest()[:8]

4. Decomposition skip returns action='autonomous' which may trigger exploration (koan/app/iteration_manager.py, L292)
When decomposition injects sub-tasks and returns (None, None), plan_iteration returns action="autonomous". This signals the run loop to start an autonomous exploration session instead of just re-looping to pick up the newly injected sub-tasks.

A dedicated action like "decomposed" (or "skip") would let the run loop immediately re-iterate to pick up the first sub-task, avoiding a wasted exploration cycle. Alternatively, return action="mission" with an empty title that the caller interprets as "re-poll".

action="autonomous",

5. Nested JSON objects will break the greedy regex + json.loads (koan/app/decompose.py, L119-125)
The prompt asks Claude to return {"type": "composite", "subtasks": [...]} — a flat structure. But if Claude ever returns nested objects (e.g., with a reasoning field containing braces), the greedy \{.*\} regex will grab the outer braces correctly, but a non-greedy fix could break. Consider a more robust approach: try json.loads(output) first (for clean JSON), then fall back to regex extraction only if that fails.

json_match = re.search(r'\{.*\}', output, re.DOTALL)

🟢 Suggestions

1. stdlib imports inside function body (koan/app/iteration_manager.py, L249-250)
import re, import hashlib, and import time are stdlib modules — they should be at the module top level. Lazy imports inside functions are a project convention for app.* modules (to avoid circular imports), but stdlib modules have no such concern and the inline imports just add clutter.

import re
...
import hashlib
import time as _time

2. Single-project case silently falls through with no project tag (koan/skills/core/decompose/handler.py, L53-63)
When there's only one project (len(known) == 1), or when urgent=True with multiple projects, the mission is queued without a project tag. This is likely intentional (auto-resolved by the mission picker), but it's worth noting that the behavior differs from other skill handlers that explicitly assign the single project. If the default project resolution logic doesn't handle tagless [decompose] missions, they could end up unassigned.

if len(known) > 1 and not urgent:

3. os.environ.setdefault at import time is fragile (koan/tests/test_decompose.py, L549)
Setting KOAN_ROOT via os.environ.setdefault at module level means it persists across the entire test session and could mask issues in other test files that depend on KOAN_ROOT being unset or set differently. The existing test convention in CLAUDE.md is to set it as a prefix (KOAN_ROOT=/tmp/test-koan pytest ...). Consider using a conftest.py fixture with monkeypatch.setenv instead, or at least document why this is needed here.

os.environ.setdefault("KOAN_ROOT", "/tmp/test-koan")

Checklist

  • No hardcoded secrets
  • No command/shell injection
  • No unsafe deserialization
  • Input validation at boundaries
  • No bare except: swallowing errors
  • Resource cleanup in error paths
  • No mutable default arguments
  • No unsafe eval/exec
  • Debug/print statements removed — warning Add crash recovery on startup (US 3.3) #3
  • Test coverage for edge cases
  • Tests validate behavior not implementation
  • Fallback paths handle all data correctly — critical Fix Python 3.8 compat, journal path, BACKLOG status #1

Summary

Solid feature with good fail-safe design and thorough tests. However, there is one critical bug: the inject_subtasks fallback path (line 378 of missions.py) only inserts the last subtask when the parent line can't be found, silently dropping all others. Additionally, the greedy JSON regex in the parser could misparse output with multiple brace groups, and the 8 raw print() calls should use the project's logging conventions. The action='autonomous' return after decomposition may cause a wasted exploration cycle instead of immediately picking up the new sub-tasks. Fix the critical subtask-loss bug and the logging before merging.


Automated review by Kōan

@Koan-Bot

Copy link
Copy Markdown
Collaborator Author

@atoomic: @Koan-Bot review

Here's the review. One critical issue: inject_subtasks() fallback (missions.py:378) only inserts the last subtask when the parent line can't be matched — all other sub-tasks are silently dropped. This needs to loop over all subtask_lines. Additionally: the 8 print() calls in decompose.py should use structured logging, the greedy JSON regex could misparse multi-brace output, and returning action='autonomous' after decomposition may trigger an unnecessary exploration cycle instead of immediately picking up the new sub-tasks.

@atoomic atoomic self-assigned this Mar 25, 2026
@Koan-Bot Koan-Bot force-pushed the koan.atoomic/fix-issue-739 branch from d2c6cd0 to e3d13a3 Compare March 25, 2026 07:03
Koan-Bot added a commit to Koan-Bot/koan that referenced this pull request Mar 25, 2026
review feedback:**

- **Fixed critical bug in `inject_subtasks` fallback** (`koan/app/missions.py:535-537`): When the parent mission line can't be found, the fallback now loops over all `subtask_lines` and inserts each one via `insert_mission()`, instead of only inserting `subtask_lines[-1]` which silently dropped all but the last sub-task.

- **Fixed greedy JSON regex** (`koan/app/decompose.py:113`): Replaced `r'\{.*\}'` (greedy with `re.DOTALL`) with `r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}'` which correctly handles one level of nested braces without greedily matching across unrelated brace groups in surrounding text.

- **Replaced 8 `print()` statements with structured logging** (`koan/app/decompose.py`): Replaced all `print(..., file=sys.stderr)` calls with `logging.getLogger(__name__)` using appropriate log levels (`logger.error` for failures, `logger.warning` for parse issues, `logger.info` for informational caps), consistent with the codebase pattern used in other `koan/app/` modules.
@Koan-Bot

Copy link
Copy Markdown
Collaborator Author

Rebase: feat: LLM-driven mission decomposition with /decompose skill

Branch koan.atoomic/fix-issue-739 rebased onto main and force-pushed.

Diff: 7 files changed, 768 insertions(+)

Review feedback was analyzed and applied.

Changes

review feedback:**

  • Fixed critical bug in inject_subtasks fallback (koan/app/missions.py:535-537): When the parent mission line can't be found, the fallback now loops over all subtask_lines and inserts each one via insert_mission(), instead of only inserting subtask_lines[-1] which silently dropped all but the last sub-task.

  • Fixed greedy JSON regex (koan/app/decompose.py:113): Replaced r'\{.*\}' (greedy with re.DOTALL) with r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}' which correctly handles one level of nested braces without greedily matching across unrelated brace groups in surrounding text.

  • Replaced 8 print() statements with structured logging (koan/app/decompose.py): Replaced all print(..., file=sys.stderr) calls with logging.getLogger(__name__) using appropriate log levels (logger.error for failures, logger.warning for parse issues, logger.info for informational caps), consistent with the codebase pattern used in other koan/app/ modules.

Actions

  • Rebased koan.atoomic/fix-issue-739 onto upstream/main
  • Applied review feedback
  • Force-pushed koan.atoomic/fix-issue-739 to origin
  • CI passed

CI

CI passed.


Automated by Kōan

@atoomic

atoomic commented Apr 17, 2026

Copy link
Copy Markdown
Collaborator

We need to add some user facing documentation and example usage so we can better understand as a user how to use that feature.

@atoomic

atoomic commented Apr 17, 2026

Copy link
Copy Markdown
Collaborator

@Koan-Bot rebase

@Koan-Bot

Copy link
Copy Markdown
Collaborator Author

Rebase with requested adjustments

Branch koan.atoomic/fix-issue-739 was rebased onto main and review feedback was applied.

Stats

7 files changed, 768 insertions(+)
Actions performed
  • Already-solved check: negative (confidence=high, reasoning=None of the recent commits on main implement mission decomposition, a /decompose skill, or any LLM-d)
  • Resolved merge conflicts (1 round(s))
  • Rebased koan.atoomic/fix-issue-739 onto upstream/main
  • Pre-push CI check: previous run passed
  • Force-pushed koan.atoomic/fix-issue-739 to origin
  • CI check enqueued in ## CI (async)

CI status

CI will be checked asynchronously.


Automated by Kōan

Koan-Bot added a commit to Koan-Bot/koan that referenced this pull request Apr 17, 2026
review feedback:**

- **Fixed critical bug in `inject_subtasks` fallback** (`koan/app/missions.py:535-537`): When the parent mission line can't be found, the fallback now loops over all `subtask_lines` and inserts each one via `insert_mission()`, instead of only inserting `subtask_lines[-1]` which silently dropped all but the last sub-task.

- **Fixed greedy JSON regex** (`koan/app/decompose.py:113`): Replaced `r'\{.*\}'` (greedy with `re.DOTALL`) with `r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}'` which correctly handles one level of nested braces without greedily matching across unrelated brace groups in surrounding text.

- **Replaced 8 `print()` statements with structured logging** (`koan/app/decompose.py`): Replaced all `print(..., file=sys.stderr)` calls with `logging.getLogger(__name__)` using appropriate log levels (`logger.error` for failures, `logger.warning` for parse issues, `logger.info` for informational caps), consistent with the codebase pattern used in other `koan/app/` modules.
@Koan-Bot Koan-Bot force-pushed the koan.atoomic/fix-issue-739 branch from e3d13a3 to 2596e2c Compare April 17, 2026 05:08
@atoomic

atoomic commented May 25, 2026

Copy link
Copy Markdown
Collaborator

@Koan-Bot need

@atoomic

atoomic commented May 25, 2026

Copy link
Copy Markdown
Collaborator

@Koan-Bot rr

@Koan-Bot

Copy link
Copy Markdown
Collaborator Author

@atoomic: We need to add some user facing documentation and example usage so we can better understand as a use...

Agreed — this is also a hard requirement per the project's own conventions in CLAUDE.md. Specifically, three documentation updates are needed before merge:

  1. CLAUDE.md skill list: Add decompose to the alphabetically-ordered core skills list in the "Skills system" section (between delete_project and doc).

  2. docs/user-manual.md: Add /decompose (alias /split) to the "missions" group tier section and the quick-reference appendix. Should cover: what it does, the [decompose] tag, auto-decompose config (decompose.auto: true in config.yaml), the 6 sub-task cap, and how [group:ID] lineage tracking works.

  3. User-facing documentation: A section in docs/user-manual.md or a dedicated docs/decompose.md with examples like:

    • /decompose refactor the auth module — basic usage
    • /decompose [project:koan] add retry logic to all API calls — with project tag
    • Adding [decompose] tag manually to missions.md
    • Enabling auto-decompose in config.yaml
    • How to tell if a mission was decomposed (look for [group:ID] tags)
    • What happens when the classifier says "atomic" (tag is stripped, mission runs as-is)

The conventions state: "undocumented features are invisible to users."

@Koan-Bot

Copy link
Copy Markdown
Collaborator Author

PR Review — feat: LLM-driven mission decomposition with /decompose skill

The decomposition feature is well-designed overall — opt-in by default, fail-safe pattern, clean separation between classifier/injector/skill. However, there is one critical bug: the fallback path in inject_subtasks (when the parent line can't be matched) appends subtasks without removing the parent, creating an infinite decomposition loop that burns LLM calls and accumulates duplicate missions. This must be fixed before merge. Additionally, the PR is missing all required documentation updates (CLAUDE.md skill list, user-manual.md, user-facing docs) which were explicitly requested by the maintainer. The quality report's 8 "debug print statement" flags should be verified against the actual committed code. After fixing the inject_subtasks fallback and adding documentation, this is merge-ready.


🔴 Blocking

1. Fallback path creates infinite decomposition loop (`koan/app/missions.py`, L535-539)

When the parent line cannot be matched (either verbatim or without timestamps), the fallback appends all subtasks to Pending but never removes the parent. Since the parent still carries its [decompose] tag and has no [group:*] tag, _maybe_decompose will pick it up again on the next iteration, call the LLM again, append more duplicate subtasks, return (None, None), and repeat — burning one classifier LLM call per iteration indefinitely while accumulating duplicate subtask lines.

The safe fix is to not inject subtasks when the parent can't be located, log a warning, and return the content unchanged so the mission runs atomically as a fail-safe:

# Parent not found — fall through to atomic execution
logger.warning("inject_subtasks: parent line not found — skipping injection")
return content

Alternatively, if you want the fallback to still inject, you must also remove or mark the parent (e.g., move it to Done with a [decomposed:GROUP_ID] tag).

    # Parent not found — append all sub-tasks at bottom of pending
    for line in subtask_lines:
        content = insert_mission(content, line, urgent=False)
    return content

🟡 Important

1. lstrip('- ') is character-based, not prefix-based (`koan/app/missions.py`, L530)

lstrip("- ") strips all leading characters in the set {'-', ' '}, not the literal string "- ". For a mission like "- --verbose flag fix", it would strip past the content boundary. Since the project requires Python 3.11+, use removeprefix("- ") which removes exactly one prefix occurrence:

line_bare = ts_pattern.sub("", line.strip().removeprefix("- ")).strip()
line_bare = ts_pattern.sub("", line.strip().lstrip("- ")).strip()
2. Inline imports of re, hashlib, time inside _maybe_decompose (`koan/app/iteration_manager.py`, L850-854)

Three stdlib modules (re, hashlib, time) are imported inline inside _maybe_decompose. Unlike the lazy imports for app.* modules (which are conditional on feature availability), stdlib modules are always available and should be at the module top level. This matches the pattern in the rest of iteration_manager.py where re, json, hashlib etc. are top-level imports.

import re
cleaned = re.sub(...)
...
import hashlib
import time as _time
3. Missing required documentation updates per project conventions

Per CLAUDE.md conventions for adding a new core skill, this PR is missing:

  1. CLAUDE.md skill list update — The "Core skills" line in the Skills system section must include decompose in alphabetical order.
  2. docs/user-manual.md update — The skill must be added to the appropriate tier section and quick-reference appendix.
  3. User-facing documentation@atoomic explicitly requested documentation and example usage in the PR comments.

All three are required before merge per project conventions ("undocumented features are invisible to users").

🟢 Suggestions

1. Missing test coverage for integration and edge cases

The test suite covers decompose.py and inject_subtasks well but is missing:

  1. _maybe_decompose integration — no tests for the iteration_manager hook, including the auto-decompose config path, skill-dispatch skip (/ prefix), and the (None, None) return flow.
  2. inject_subtasks fallback path — no test for the case where the parent line cannot be found at all (the append-at-bottom fallback, which is currently buggy).
  3. Handler edge cases — the /decompose handler's multi-project disambiguation path is untested.
2. Inline import of re inside function body (`koan/skills/core/decompose/handler.py`, L59)

import re is a stdlib module and should be at the top of the file rather than inline at line 59. This is inconsistent with the handler pattern — other handlers import stdlib modules at module level.

import re
mission_text = re.sub(r'\s*\[decompose\]\s*', ' ', mission_text, flags=re.IGNORECASE).strip()

Checklist

  • No hardcoded secrets or credentials
  • No command injection or shell interpolation of user input
  • Input validation at system boundaries
  • No bare except: that swallows errors silently
  • Error paths have proper cleanup
  • No unbounded collections or infinite loops — critical #1 — inject_subtasks fallback creates infinite decomposition loop
  • No mutable default arguments
  • Tests verify behavior not implementation
  • Test coverage for new code paths — suggestion #3 — missing _maybe_decompose and fallback path tests
  • Documentation updated per project conventions — warning #5 — missing CLAUDE.md, user-manual.md, and user-facing docs

Summary

The decomposition feature is well-designed overall — opt-in by default, fail-safe pattern, clean separation between classifier/injector/skill. However, there is one critical bug: the fallback path in inject_subtasks (when the parent line can't be matched) appends subtasks without removing the parent, creating an infinite decomposition loop that burns LLM calls and accumulates duplicate missions. This must be fixed before merge. Additionally, the PR is missing all required documentation updates (CLAUDE.md skill list, user-manual.md, user-facing docs) which were explicitly requested by the maintainer. The quality report's 8 "debug print statement" flags should be verified against the actual committed code. After fixing the inject_subtasks fallback and adding documentation, this is merge-ready.


To rebase specific severity levels, mention me: @Koan-Bot rebase critical (fixes 🔴 only), @Koan-Bot rebase important (fixes 🔴 + 🟡), or just @Koan-Bot rebase for all.


Silent Failure Analysis

🔴 **CRITICAL** — silent fallback causes infinite loop (`koan/app/missions.py:533-535`)

Risk: When the parent mission line cannot be located, subtasks are appended but the parent is NOT removed — the caller (_maybe_decompose) returns (None, None) signaling success, so next iteration the unchanged parent is picked again, decomposed again, and more duplicate subtasks are appended, creating an unbounded loop.

# Parent not found — append all sub-tasks at bottom of pending
for line in subtask_lines:
    content = insert_mission(content, line, urgent=False)
return content

Fix: Log a warning and return the content unchanged (no subtask injection) so the caller falls back to running the parent as-is, or raise an exception so the caller knows injection failed.

🟠 **HIGH** — error conflated with success path (`koan/app/iteration_manager.py:841-846`)

Risk: decompose_mission returns None for both 'atomic' and all error paths (CLI timeout, import failure, parse error); the caller strips the [decompose] tag and logs 'classified as atomic', permanently losing the signal that decomposition was requested but failed — no retry is possible and the operator sees a false-positive success message.

subtasks = decompose_mission(mission_title, project_path)

if subtasks is None:
    cleaned = re.sub(r'\s*\[decompose\]\s*', ' ', mission_title, flags=re.IGNORECASE).strip()
    _log_iteration("koan", "Mission classified as atomic — proceeding normally")
    return mission_project, cleaned

Fix: Have decompose_mission raise or return a distinct sentinel for errors (e.g. a (classification, subtasks) tuple or a custom result object) so the caller can preserve the [decompose] tag on failure and log accurately.

🟡 **MEDIUM** — silent degradation on lookup failure (`koan/app/iteration_manager.py:830-836`)

Risk: If the project path lookup fails or the project name isn't found, project_path stays as empty string and the LLM classifier runs with no project context (arbitrary CWD), potentially producing wrong atomic/composite classification without any signal to the operator that context was missing.

project_path = ""
try:
    from app.utils import get_known_projects
    for name, path in get_known_projects():
        if name == mission_project:
            project_path = path
            break
except Exception as e:
    _log_iteration("error", f"Decompose project path lookup error: {e}")

Fix: When project_path is empty after lookup, either skip decomposition and let the mission run as-is, or pass a flag to decompose_mission indicating missing context so it can adjust its behavior.

🟡 **MEDIUM** — catch-all exception swallowed (`koan/app/decompose.py:80-84`)

Risk: A bare 'except Exception' around the CLI call catches unexpected errors (RuntimeError, TypeError, etc.) and converts them to None (treated as 'atomic'), masking bugs in run_cli_with_retry or command building that should surface as crashes during development.

except Exception as e:
    logger.error("CLI error: %s", e)
    return None

Fix: Narrow the catch to expected exceptions (OSError, subprocess.TimeoutExpired, subprocess.SubprocessError) and let unexpected errors propagate.


Automated review by Kōan382ba33
2596e2c

Koan-Bot and others added 3 commits May 25, 2026 00:47
Adds opt-in task decomposition: missions tagged [decompose] (or with
auto-decompose enabled in config) are classified by a lightweight Claude
call before execution. Composite missions are split into focused sub-tasks
injected back into the Pending queue; atomic missions run as-is.

Changes:
- koan/system-prompts/decompose-mission.md: classifier prompt with
  anti-over-decomposition bias ("when in doubt, choose atomic"), JSON
  output schema, max 6 sub-tasks cap
- koan/app/decompose.py: decompose_mission(), should_decompose(),
  is_already_decomposed() — mirrors pr_review_learning.py LLM call pattern
- koan/app/missions.py: inject_subtasks() — replaces parent mission line
  with ordered sub-task lines tagged [group:ID], inheriting [project:X] tag
- koan/app/iteration_manager.py: _maybe_decompose() hook after mission
  pick (Step 4b); returns (None, None) to skip parent when sub-tasks injected
- koan/skills/core/decompose/: /decompose Telegram skill that queues
  missions with [decompose] tag; aliased as /split
- koan/tests/test_decompose.py: 31 tests covering parse, tag detection,
  CLI mocking, and inject_subtasks mutations

Closes Anantys-oss#739

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
review feedback:**

- **Fixed critical bug in `inject_subtasks` fallback** (`koan/app/missions.py:535-537`): When the parent mission line can't be found, the fallback now loops over all `subtask_lines` and inserts each one via `insert_mission()`, instead of only inserting `subtask_lines[-1]` which silently dropped all but the last sub-task.

- **Fixed greedy JSON regex** (`koan/app/decompose.py:113`): Replaced `r'\{.*\}'` (greedy with `re.DOTALL`) with `r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}'` which correctly handles one level of nested braces without greedily matching across unrelated brace groups in surrounding text.

- **Replaced 8 `print()` statements with structured logging** (`koan/app/decompose.py`): Replaced all `print(..., file=sys.stderr)` calls with `logging.getLogger(__name__)` using appropriate log levels (`logger.error` for failures, `logger.warning` for parse issues, `logger.info` for informational caps), consistent with the codebase pattern used in other `koan/app/` modules.
@Koan-Bot

Copy link
Copy Markdown
Collaborator Author

Rebase with requested adjustments

Branch koan.atoomic/fix-issue-739 was rebased onto main and review feedback was applied.

Changes applied

  • Summary of changes:
  • docs/user-manual.md: Added /decompose skill documentation in Intermediate — Code Operations section with usage, aliases, behavior description, guards, and example use cases. Added /decompose row to Quick Reference table. Updated skill count from 44 to 45.
  • docs/skills.md: Added /decompose row to Mission Management table.
  • CLAUDE.md: Added decompose to core skills list (alphabetical order).
  • instance.example/config.yaml: Added decompose: config section documenting auto flag, between stagnation and autonomous_health sections.
  • All per reviewer request: "add some user facing documentation and example usage so we can better understand as a user how to use that feature."

Stats

11 files changed, 808 insertions(+), 2 deletions(-)
Actions performed
  • Already-solved check: negative (confidence=high, reasoning=No commit on main implements LLM-driven mission decomposition — none of the 30 recent commits mentio)
  • Resolved merge conflicts (1 round(s))
  • Rebased koan.atoomic/fix-issue-739 onto upstream/main
  • Applied review feedback
  • Pre-push CI check: previous run passed
  • Force-pushed koan.atoomic/fix-issue-739 to origin
  • CI check enqueued in ## CI (async)

CI status

CI will be checked asynchronously.


Automated by Kōan

@atoomic

atoomic commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

superseded by #1533

@atoomic atoomic closed this Jun 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

LLM-driven task decomposition for complex missions

2 participants