Skip to content

Epoch#1

Merged
solatis merged 412 commits into
masterfrom
feat/epoch
Apr 19, 2026
Merged

Epoch#1
solatis merged 412 commits into
masterfrom
feat/epoch

Conversation

@solatis
Copy link
Copy Markdown
Owner

@solatis solatis commented Feb 23, 2026

[WiP]

Repository owner deleted a comment from qodo-code-review Bot Apr 19, 2026
@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented Apr 19, 2026

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (4) 📎 Requirement gaps (0)

Grey Divider


Action required

1. driver_main injects phase_instructions 📘 Rule violation ⛨ Security
Description
The driver injects initial_guidance into task["phase_instructions"] when spawning the
orchestrator, which delivers phase guidance before the step-1 mechanism. This violates the
requirement that phase guidance is delivered only through step-1 guidance after phase transitions
are committed.
Code

koan/driver.py[R62-73]

+    # Inject phase_guidance for the initial phase so intake adapts to workflow scope
+    initial_guidance = workflow.phase_guidance.get(initial_phase, "") if workflow else ""
+
+    task = {
+        "role": "orchestrator",
+        "run_dir": run_dir,
+        "subagent_dir": subagent_dir,
+        "project_dir": app_state.project_dir,
+        "task_description": app_state.task_description,
+        "workflow": workflow_name,
+        "phase_instructions": initial_guidance,   # scope framing for initial phase
+    }
Evidence
PR Compliance ID 5 forbids informing the orchestrator of phase-specific guidance outside the step-1
injection mechanism. The driver sets phase_instructions at spawn time for the initial phase,
bypassing step-1 delivery.

AGENTS.md
koan/driver.py[62-73]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The driver passes `phase_instructions` into the orchestrator task at spawn time, which delivers phase guidance outside the step-1 guidance mechanism.

## Issue Context
Phase guidance should be injected only when `koan_complete_step` returns step-1 content for a committed phase (including the initial phase).

## Fix Focus Areas
- koan/driver.py[62-73]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. write/edit not markdown-only 📘 Rule violation ⛨ Security
Description
The PR grants LLM agents Write/Edit and the permission logic only path-scopes writes, without
blocking .json (or other non-markdown) edits in the run directory. This breaks the required
boundary that LLM-facing edits are markdown-only and must not touch driver-owned JSON state files
like run-state.json or story state.json.
Code

koan/lib/permissions.py[R44-73]

+ROLE_PERMISSIONS: dict[str, frozenset[str]] = {
+    "intake": frozenset({
+        "koan_complete_step",
+        "koan_ask_question",
+        "koan_request_scouts",
+        "edit",
+        "write",
+    }),
+    "scout": frozenset({
+        "koan_complete_step",
+    }),
+    "orchestrator": frozenset({
+        # Base set; actual permissions are phase-aware — see _check_orchestrator_permission
+        "koan_complete_step",
+        "koan_set_phase",
+        "koan_yield",
+        "koan_ask_question",
+        "koan_request_scouts",
+        "koan_request_executor",
+        "koan_select_story",
+        "koan_complete_story",
+        "koan_retry_story",
+        "koan_skip_story",
+        "koan_memorize",
+        "koan_forget",
+        "koan_memory_status",
+        "koan_search",
+        "edit",
+        "write",
+        "bash",
Evidence
PR Compliance ID 1 requires LLM edits be limited to markdown and forbids LLM logic from
reading/writing JSON state files. The PR explicitly enables edit/write for LLM roles and only
enforces a run-dir path scope (no .json/non-markdown restriction), while the Claude CLI tool
whitelist exposes Write/Edit to the orchestrator.

AGENTS.md
koan/lib/permissions.py[44-88]
koan/lib/permissions.py[283-306]
koan/subagent.py[106-110]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
LLM agents are granted `write`/`edit` capability, and the current permission model only path-scopes writes to `run_dir` (and for orchestrator), without restricting file types. This allows modifying driver-owned JSON state files (e.g., `run-state.json`, `stories/*/state.json`) and other non-markdown artifacts, violating the markdown-only boundary.

## Issue Context
The compliance contract requires LLM-facing edits to be limited to markdown artifacts and forbids direct JSON state edits by agents.

## Fix Focus Areas
- koan/subagent.py[106-110]
- koan/lib/permissions.py[44-88]
- koan/lib/permissions.py[283-306]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Non-orchestrator bash always allowed 📘 Rule violation ⛨ Security
Description
check_permission() allows bash for all non-orchestrator roles regardless of phase, and the scout
tool whitelist includes Bash. This violates the requirement that bash be phase-restricted
(execution/implementation-validation) and not callable in other phases.
Code

koan/lib/permissions.py[R259-268]

+    # bash always allowed for non-orchestrator roles.
+    if tool_name == "bash":
+        return {"allowed": True, "reason": None}
+
+    # brief-generation step 1 (Read) is read-only — phase-aware gate.
+    if current_phase == "brief-generation" and current_step == 1 and tool_name in STEP_1_BLOCKED_TOOLS:
+        return {
+            "allowed": False,
+            "reason": (
+                f"{tool_name} is not available during the Read step (step 1). "
Evidence
PR Compliance ID 4 requires phase restrictions for bash. The new permission logic explicitly
returns allowed for bash for non-orchestrator roles without checking phase, and scouts are
provisioned with Bash in the Claude tool whitelist.

AGENTS.md
koan/lib/permissions.py[259-268]
koan/subagent.py[106-110]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`bash` is permitted for non-orchestrator roles in all phases, and the scout CLI whitelist includes `Bash`, enabling shell access during planning phases.

## Issue Context
The compliance model requires `bash` be restricted to execution and implementation-validation phases.

## Fix Focus Areas
- koan/lib/permissions.py[259-268]
- koan/subagent.py[106-110]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
4. Artifact path escape 🐞 Bug ⛨ Security
Description
api_artifact_content() uses a raw string prefix check (startswith) to enforce run-directory
containment, which is not a path-boundary-safe check. A crafted artifact path can resolve to a
sibling path whose absolute path shares the run directory prefix and still pass the guard, enabling
unintended file reads outside the run directory.
Code

koan/web/app.py[R448-450]

+    run = Path(st.run_dir).resolve()
+    target = (run / req_path).resolve()
+    if not str(target).startswith(str(run)):
Evidence
The endpoint is mounted as "/api/artifacts/{path:path}", so the path parameter can contain slashes
and traversal segments. The handler resolves the user-controlled path against the run directory and
then checks containment using str(target).startswith(str(run)), which can be bypassed when
target is outside run but begins with the same string (e.g., /home/.../run vs
/home/.../run_backup).

koan/web/app.py[440-454]
koan/web/app.py[1104-1112]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`api_artifact_content` tries to prevent path traversal by checking `str(target).startswith(str(run))`. This is not a safe containment check because it does not enforce a path boundary; paths like `/.../run_backup/...` can incorrectly pass when `run` is `/.../run`.

### Issue Context
The route uses `{path:path}`, so the captured parameter can include `/` and `..` segments. The code resolves the path, but resolution alone does not guarantee containment; the guard must do a boundary-aware containment check.

### Fix Focus Areas
- koan/web/app.py[440-467]
- koan/web/app.py[1104-1112]

### Suggested fix
- Replace the `startswith` guard with a boundary-safe check:
 - Prefer:
   - `try: target.relative_to(run)` (deny on `ValueError`), or
   - `if not target.is_relative_to(run): ...` (Python 3.9+).
- Keep the `.resolve()` call, but ensure the containment check is done after resolving.
- Add a small unit test to confirm `/api/artifacts/../<run_prefix>_other/file` is rejected.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

5. Claude uses --mcp-config flag 📘 Rule violation ⚙ Maintainability
Description
The Claude runner injects MCP configuration via a CLI flag (--mcp-config) built from a passed-in
mcp_url, rather than relying solely on the subagent directory contract files for structured
configuration. This conflicts with the requirement to avoid structured configuration via CLI flags.
Code

koan/runners/claude.py[R138-177]

+    ) -> list[str]:
+        if thinking not in self.supported_thinking_modes:
+            raise RunnerError(RunnerDiagnostic(
+                code="unsupported_thinking_mode",
+                runner="claude",
+                stage="build_command",
+                message=f"Thinking mode '{thinking}' is not supported by claude",
+            ))
+
+        config_dir = Path(self.subagent_dir)
+        config_path = config_dir / "mcp-config.json"
+        config_data = {"mcpServers": {"koan": {"type": "http", "url": mcp_url}}}
+
+        try:
+            config_dir.mkdir(parents=True, exist_ok=True)
+            tmp = config_path.with_suffix(".json.tmp")
+            tmp.write_text(json.dumps(config_data, indent=2) + "\n", "utf-8")
+            tmp.rename(config_path)
+        except OSError as e:
+            raise RunnerError(RunnerDiagnostic(
+                code="mcp_inject_failed",
+                runner="claude",
+                stage="build_command",
+                message=f"Failed to write MCP config: {e}",
+            )) from e
+
+        cmd = [
+            installation.binary, "-p", boot_prompt,
+            "--output-format", "stream-json",
+            "--verbose",
+            "--include-partial-messages",
+            "--mcp-config", str(config_path),
+        ]
+        if system_prompt:
+            cmd.extend(["--system-prompt", system_prompt])
+        if thinking != "disabled":
+            cmd.extend(["--effort", _EFFORT_MAP[thinking]])
+        cmd.extend(["--model", model])
+        # Opus 4.7+ suppresses thinking tokens by default; summarized mode
+        # preserves visibility without overwhelming the stream.
Evidence
PR Compliance ID 6 requires honoring the directory-as-contract approach and avoiding structured
configuration via CLI flags. The runner writes mcp-config.json and passes it through
--mcp-config, with mcp_url provided as a build argument instead of being consumed from the
subagent contract directory files.

AGENTS.md
koan/runners/claude.py[138-188]
koan/subagent.py[240-305]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The Claude runner currently receives `mcp_url` as an argument and passes MCP config via the CLI `--mcp-config` flag. The compliance contract expects structured configuration to be carried via the subagent directory contract (e.g., `task.json`) rather than CLI flags.

## Issue Context
If the underlying runner requires a flag to point at a config file, consider making the config file itself part of the directory contract and derived exclusively from `task.json` (with the runner reading `task.json`), so structured values are not provided via CLI/build-time arguments.

## Fix Focus Areas
- koan/runners/claude.py[138-188]
- koan/subagent.py[240-305]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Secret args logged 🐞 Bug ⛨ Security
Description
spawn_subagent() logs the full command line using " ".join(cmd), which includes
installation.extra_args. Since extra_args are user-provided strings and appended into runner
commands, this can leak tokens/credentials passed as CLI flags into server logs.
Code

koan/subagent.py[R327-328]

+    spawn_cwd = task.get("project_dir") or subagent_dir
+    log.info("spawning %s (agent_id=%s) cwd=%s: %s", role, agent_id, spawn_cwd, " ".join(cmd))
Evidence
The web API accepts and stores extra_args for agent installations. Runner implementations append
installation.extra_args directly into the spawned command. spawn_subagent then logs the entire
command string, exposing those arguments verbatim in logs.

koan/web/app.py[784-821]
koan/runners/claude.py[164-181]
koan/subagent.py[324-329]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`spawn_subagent` logs the fully-expanded spawn command (`" ".join(cmd)`). Runner commands include `installation.extra_args`, which are user-provided and can include secrets (tokens, API keys, etc.). Logging them leaks those secrets to any log consumer.

### Issue Context
- `api_agents_create` and `api_agents_update` accept `extra_args` from requests and persist them.
- Runner `build_command` methods append `installation.extra_args`.
- The log line prints the entire command.

### Fix Focus Areas
- koan/subagent.py[324-329]
- koan/web/app.py[784-850]
- koan/runners/claude.py[164-181]

### Suggested fix
- Change the log line to avoid printing arguments:
 - Log only binary + role + agent_id + cwd, or
 - Log a redacted version of args (e.g., mask values following flags like `--api-key`, `--token`, `--auth`, etc.).
- If you keep arg logging for debugging, gate it behind `debug` and still redact sensitive patterns.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Repository owner deleted a comment from qodo-code-review Bot Apr 19, 2026
Comment thread koan/driver.py
Comment on lines +62 to +73
# Inject phase_guidance for the initial phase so intake adapts to workflow scope
initial_guidance = workflow.phase_guidance.get(initial_phase, "") if workflow else ""

task = {
"role": "orchestrator",
"run_dir": run_dir,
"subagent_dir": subagent_dir,
"project_dir": app_state.project_dir,
"task_description": app_state.task_description,
"workflow": workflow_name,
"phase_instructions": initial_guidance, # scope framing for initial phase
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. driver_main injects phase_instructions 📘 Rule violation ⛨ Security

The driver injects initial_guidance into task["phase_instructions"] when spawning the
orchestrator, which delivers phase guidance before the step-1 mechanism. This violates the
requirement that phase guidance is delivered only through step-1 guidance after phase transitions
are committed.
Agent Prompt
## Issue description
The driver passes `phase_instructions` into the orchestrator task at spawn time, which delivers phase guidance outside the step-1 guidance mechanism.

## Issue Context
Phase guidance should be injected only when `koan_complete_step` returns step-1 content for a committed phase (including the initial phase).

## Fix Focus Areas
- koan/driver.py[62-73]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread koan/lib/permissions.py
Comment on lines +44 to +73
ROLE_PERMISSIONS: dict[str, frozenset[str]] = {
"intake": frozenset({
"koan_complete_step",
"koan_ask_question",
"koan_request_scouts",
"edit",
"write",
}),
"scout": frozenset({
"koan_complete_step",
}),
"orchestrator": frozenset({
# Base set; actual permissions are phase-aware — see _check_orchestrator_permission
"koan_complete_step",
"koan_set_phase",
"koan_yield",
"koan_ask_question",
"koan_request_scouts",
"koan_request_executor",
"koan_select_story",
"koan_complete_story",
"koan_retry_story",
"koan_skip_story",
"koan_memorize",
"koan_forget",
"koan_memory_status",
"koan_search",
"edit",
"write",
"bash",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. write/edit not markdown-only 📘 Rule violation ⛨ Security

The PR grants LLM agents Write/Edit and the permission logic only path-scopes writes, without
blocking .json (or other non-markdown) edits in the run directory. This breaks the required
boundary that LLM-facing edits are markdown-only and must not touch driver-owned JSON state files
like run-state.json or story state.json.
Agent Prompt
## Issue description
LLM agents are granted `write`/`edit` capability, and the current permission model only path-scopes writes to `run_dir` (and for orchestrator), without restricting file types. This allows modifying driver-owned JSON state files (e.g., `run-state.json`, `stories/*/state.json`) and other non-markdown artifacts, violating the markdown-only boundary.

## Issue Context
The compliance contract requires LLM-facing edits to be limited to markdown artifacts and forbids direct JSON state edits by agents.

## Fix Focus Areas
- koan/subagent.py[106-110]
- koan/lib/permissions.py[44-88]
- koan/lib/permissions.py[283-306]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread koan/lib/permissions.py
Comment on lines +259 to +268
# bash always allowed for non-orchestrator roles.
if tool_name == "bash":
return {"allowed": True, "reason": None}

# brief-generation step 1 (Read) is read-only — phase-aware gate.
if current_phase == "brief-generation" and current_step == 1 and tool_name in STEP_1_BLOCKED_TOOLS:
return {
"allowed": False,
"reason": (
f"{tool_name} is not available during the Read step (step 1). "
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

3. Non-orchestrator bash always allowed 📘 Rule violation ⛨ Security

check_permission() allows bash for all non-orchestrator roles regardless of phase, and the scout
tool whitelist includes Bash. This violates the requirement that bash be phase-restricted
(execution/implementation-validation) and not callable in other phases.
Agent Prompt
## Issue description
`bash` is permitted for non-orchestrator roles in all phases, and the scout CLI whitelist includes `Bash`, enabling shell access during planning phases.

## Issue Context
The compliance model requires `bash` be restricted to execution and implementation-validation phases.

## Fix Focus Areas
- koan/lib/permissions.py[259-268]
- koan/subagent.py[106-110]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread koan/web/app.py
Comment on lines +448 to +450
run = Path(st.run_dir).resolve()
target = (run / req_path).resolve()
if not str(target).startswith(str(run)):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

4. Artifact path escape 🐞 Bug ⛨ Security

api_artifact_content() uses a raw string prefix check (startswith) to enforce run-directory
containment, which is not a path-boundary-safe check. A crafted artifact path can resolve to a
sibling path whose absolute path shares the run directory prefix and still pass the guard, enabling
unintended file reads outside the run directory.
Agent Prompt
### Issue description
`api_artifact_content` tries to prevent path traversal by checking `str(target).startswith(str(run))`. This is not a safe containment check because it does not enforce a path boundary; paths like `/.../run_backup/...` can incorrectly pass when `run` is `/.../run`.

### Issue Context
The route uses `{path:path}`, so the captured parameter can include `/` and `..` segments. The code resolves the path, but resolution alone does not guarantee containment; the guard must do a boundary-aware containment check.

### Fix Focus Areas
- koan/web/app.py[440-467]
- koan/web/app.py[1104-1112]

### Suggested fix
- Replace the `startswith` guard with a boundary-safe check:
  - Prefer:
    - `try: target.relative_to(run)` (deny on `ValueError`), or
    - `if not target.is_relative_to(run): ...` (Python 3.9+).
- Keep the `.resolve()` call, but ensure the containment check is done after resolving.
- Add a small unit test to confirm `/api/artifacts/../<run_prefix>_other/file` is rejected.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@solatis solatis merged commit fc35116 into master Apr 19, 2026
2 checks passed
@solatis solatis deleted the feat/epoch branch April 19, 2026 07:33
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.

1 participant