Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 19 additions & 17 deletions factory/cli/_ceo_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,8 @@ def _validate_ceo_flags(
raw_path = getattr(args, "path", None)
project_path = Path(raw_path).resolve() if raw_path else Path.cwd()
entries = WorkflowRegistry.discover(project_path)
project_entries = {n for n, e in entries.items() if e.source == "project"}
if mode not in project_entries:
all_workflow_entries = set(entries.keys())
if mode not in all_workflow_entries:
print(
f"Error: unknown mode '{mode}'. "
f"Not a built-in mode and not found in project workflows at "
Expand All @@ -193,13 +193,13 @@ def _validate_ceo_flags(
from_plan: str | None = getattr(args, "from_plan", None)
just_plan: bool = getattr(args, "just_plan", False)

if auto_approve and mode != "design":
print("Error: --auto-approve only applies to --mode design", file=sys.stderr)
if auto_approve and mode not in ("design", "design-v2"):
print("Error: --auto-approve only applies to --mode design or design-v2", file=sys.stderr)
return 1

if just_plan:
if mode != "design":
print("Error: --just-plan requires --mode design", file=sys.stderr)
if mode not in ("design", "design-v2"):
print("Error: --just-plan requires --mode design or design-v2", file=sys.stderr)
return 1
if from_plan:
print("Error: --just-plan and --from-plan are mutually exclusive.", file=sys.stderr)
Expand All @@ -209,8 +209,8 @@ def _validate_ceo_flags(
return 1

if from_plan:
if mode != "design":
print("Error: --from-plan requires --mode design", file=sys.stderr)
if mode not in ("design", "design-v2"):
print("Error: --from-plan requires --mode design or design-v2", file=sys.stderr)
return 1
if focus:
print("Error: --from-plan and --focus are mutually exclusive.", file=sys.stderr)
Expand Down Expand Up @@ -255,10 +255,10 @@ def _validate_ceo_flags(
return 1

_design_is_existing = (
mode == "design" and raw_path and _safe_is_dir(Path(raw_path).expanduser().resolve())
mode in ("design", "design-v2") and raw_path and _safe_is_dir(Path(raw_path).expanduser().resolve())
)

if mode == "design":
if mode in ("design", "design-v2"):
if auto_approve:
headless = True
elif headless:
Expand Down Expand Up @@ -354,7 +354,7 @@ def _resolve_ceo_project(
context: str | None = None

_design_is_existing = (
mode == "design" and raw_path and _safe_is_dir(Path(raw_path).expanduser().resolve())
mode in ("design", "design-v2") and raw_path and _safe_is_dir(Path(raw_path).expanduser().resolve())
)

if mode == "create":
Expand All @@ -377,10 +377,10 @@ def _resolve_ceo_project(
if m.group(1) in registered:
update_existing_mode = m.group(1)
create_description = m.group(2).strip()
elif mode == "design" and _design_is_existing:
elif mode in ("design", "design-v2") and _design_is_existing:
project_path, context = _resolve_input(raw_path, dir_name=dir_name)
design_existing = True
elif mode == "design":
elif mode in ("design", "design-v2"):
resolved_file = Path(raw_path).expanduser()
if _safe_is_file(resolved_file):
design_idea = resolved_file.read_text()
Expand Down Expand Up @@ -485,6 +485,7 @@ def _validate_late_flags(
and mode
not in (
"design",
"design-v2",
"research",
"create",
"evolve",
Expand Down Expand Up @@ -639,8 +640,8 @@ def _execute_ceo(
)
if mode == "create":
ceo_mode = "create"
elif mode == "design":
ceo_mode = "design"
elif mode in ("design", "design-v2"):
ceo_mode = mode
elif interactive:
ceo_mode = "design"
else:
Expand Down Expand Up @@ -711,6 +712,7 @@ def _execute_ceo(
from_plan=resolved_plan.plan if resolved_plan else None,
from_plan_feedback=resolved_plan.feedback if resolved_plan else None,
just_plan=just_plan,
auto_approve=auto_approve,
)

session_name = _derive_session_name(
Expand Down Expand Up @@ -915,7 +917,7 @@ def _run_headless(
min_growth=min_growth,
max_new=max_new,
branch=branch,
already_improved=mode in ("design", "meta") or discover_only,
already_improved=mode in ("design", "design-v2", "meta") or discover_only,
model=model,
no_github=no_github,
use_profile=use_profile,
Expand Down Expand Up @@ -968,7 +970,7 @@ def _run_headless(
min_growth=min_growth,
max_new=max_new,
branch=branch,
already_improved=mode in ("design", "meta") or discover_only,
already_improved=mode in ("design", "design-v2", "meta") or discover_only,
model=model,
no_github=no_github,
use_profile=use_profile,
Expand Down
15 changes: 15 additions & 0 deletions factory/cli/_task_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ def _build_ceo_task(
from_plan: str | None = None,
from_plan_feedback: list[str] | None = None,
just_plan: bool = False,
auto_approve: bool = False,
) -> str:
"""Build the CEO agent task string from mode and optional context."""
shown_mode = display_mode if display_mode is not None else mode
Expand Down Expand Up @@ -491,6 +492,20 @@ def _build_ceo_task(
f"Do NOT skip the review pipeline. Do NOT abbreviate any step.\n"
)

if auto_approve:
task += (
"\n\n## Auto-Approve Mode\n\n"
"auto_approve: true\n\n"
"At user approval gates (like gate_strategy), you act as the user:\n"
"1. Read the plan at .factory/strategy/current.md\n"
"2. Read the user's original intent at .factory/strategy/user-intent.md\n"
"3. Compare: does the plan match what the user asked for?\n"
"4. If YES: approve and proceed (say \"Approved\" and continue to the next step)\n"
"5. If NO: provide specific feedback about what's missing or wrong, then reloop\n\n"
"You are the CEO acting on behalf of the user. Apply judgment — approve good plans, "
"reject bad ones. Do NOT blindly approve everything. Do NOT wait for human input.\n"
)

if clean_pr:
task += (
"\n\n## Clean PR Mode\n\n"
Expand Down
8 changes: 4 additions & 4 deletions factory/cli/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,8 +405,8 @@ def cmd_run(args: argparse.Namespace) -> int:
mode = getattr(args, "mode", "auto")
warn_deprecated_mode(mode)
auto_approve: bool = getattr(args, "auto_approve", False)
if auto_approve and mode != "design":
print("Error: --auto-approve only applies to --mode design", file=sys.stderr)
if auto_approve and mode not in ("design", "design-v2"):
print("Error: --auto-approve only applies to --mode design or design-v2", file=sys.stderr)
return 1
force_fresh = mode == "auto-fresh"
if mode in ("auto", "auto-fresh"):
Expand All @@ -430,7 +430,7 @@ def cmd_run(args: argparse.Namespace) -> int:
file=sys.stderr,
)
return 1
if focus and mode not in ("design", "research"):
if focus and mode not in ("design", "design-v2", "research"):
print(
f"Error: --focus (targeted mode) only works in design or research mode, got '{mode}'. "
"The project must already be built before targeting specific items.",
Expand All @@ -455,7 +455,7 @@ def cmd_run(args: argparse.Namespace) -> int:
print(f" Cleaned {len(pruned)} stale worktree(s)", file=sys.stderr)

budget_kwargs = dict(min_growth=min_growth, max_new=max_new, branch=branch)
skip_improve = mode in ("design", "meta") or discover_only
skip_improve = mode in ("design", "design-v2", "meta") or discover_only

overwrite = getattr(args, "overwrite", None)

Expand Down
38 changes: 38 additions & 0 deletions factory/workflow/contributed/design_v2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# design-v2 Workflow

Design mode with inference-time scaling: dynamic research, multi-strategy, user intent tracking, and parallel adversarial QA.

## Graph

```
init_user_intent (FnNode)
→ gate_has_factory (GateNode)
→ [PROCEED] graph_update → study → graph_explorer → concat_study
→ [HALT] discover → gate_factory_md_exists → factory_init → graph_update
→ concat_study → research_director (AgentNode/CEO)
→ strategy_director (AgentNode/CEO)
→ synthesize_strategy (AgentNode/STRATEGIST)
→ design_doc (AgentNode/STRATEGIST)
→ gate_strategy (GateNode/user)
→ [PROCEED] begin → builder → fork_qa
→ [RELOOP] strategy_director
→ fork_qa → health_checker, code_reviewer, qa_director → join_qa
→ synthesize_qa (FnNode) → gate_qa (GateNode/agent)
→ [PROCEED] gate_doc_freshness → gate_precheck → archivist_build
→ [RELOOP] builder
```

## Key differences from design (v1)

- **Research Director** replaces static fork/join research — dynamically decides N research directions
- **Strategy Director** replaces single strategist — spawns M strategy perspectives
- **QA Director** replaces single adversarial tester — spawns K tailored test approaches
- **User Intent Ledger** tracks the original idea and all feedback through the session
- **Design Doc** rewrites strategy into a human-readable design document
- **Synthesize QA** merges all adversarial reports with confidence scoring

## Usage

```bash
factory ceo /path/to/project --mode design-v2
```
3 changes: 3 additions & 0 deletions factory/workflow/contributed/design_v2/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .workflow import meta, workflow

__all__ = ["meta", "workflow"]
Loading
Loading