pi-dynamic-workflows-py 0.1.0__tar.gz
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.
- pi_dynamic_workflows_py-0.1.0/.gitignore +34 -0
- pi_dynamic_workflows_py-0.1.0/PKG-INFO +38 -0
- pi_dynamic_workflows_py-0.1.0/README.md +25 -0
- pi_dynamic_workflows_py-0.1.0/pi_dynamic_workflows/__init__.py +133 -0
- pi_dynamic_workflows_py-0.1.0/pi_dynamic_workflows/budget.py +42 -0
- pi_dynamic_workflows_py-0.1.0/pi_dynamic_workflows/builtin_workflows.py +344 -0
- pi_dynamic_workflows_py-0.1.0/pi_dynamic_workflows/journal.py +154 -0
- pi_dynamic_workflows_py-0.1.0/pi_dynamic_workflows/manager.py +123 -0
- pi_dynamic_workflows_py-0.1.0/pi_dynamic_workflows/model_routing.py +51 -0
- pi_dynamic_workflows_py-0.1.0/pi_dynamic_workflows/runtime.py +329 -0
- pi_dynamic_workflows_py-0.1.0/pi_dynamic_workflows/store.py +162 -0
- pi_dynamic_workflows_py-0.1.0/pi_dynamic_workflows/subagent.py +199 -0
- pi_dynamic_workflows_py-0.1.0/pi_dynamic_workflows/workflow_tool.py +336 -0
- pi_dynamic_workflows_py-0.1.0/pi_dynamic_workflows/worktree.py +127 -0
- pi_dynamic_workflows_py-0.1.0/pyproject.toml +31 -0
- pi_dynamic_workflows_py-0.1.0/tests/test_dynamic_workflows.py +1075 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*.egg-info/
|
|
4
|
+
.eggs/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
!tui/crates/build/
|
|
8
|
+
.pytest_cache/
|
|
9
|
+
.pytest-audit/
|
|
10
|
+
.mypy_cache/
|
|
11
|
+
.ruff_cache/
|
|
12
|
+
.venv/
|
|
13
|
+
.venv-*/
|
|
14
|
+
venv/
|
|
15
|
+
.env
|
|
16
|
+
# Rust TUI workspace (Apache-2.0 fork under tui/)
|
|
17
|
+
tui/target/
|
|
18
|
+
**/*.rs.bk
|
|
19
|
+
|
|
20
|
+
# Benchmark evaluation cache and trial artifacts
|
|
21
|
+
.cache/
|
|
22
|
+
.pi-eval/
|
|
23
|
+
|
|
24
|
+
# Temporary files, coverage and logs
|
|
25
|
+
*.log
|
|
26
|
+
*.tmp
|
|
27
|
+
*.bak
|
|
28
|
+
*.swp
|
|
29
|
+
*.orig
|
|
30
|
+
.coverage
|
|
31
|
+
.coverage.*
|
|
32
|
+
coverage.xml
|
|
33
|
+
htmlcov/
|
|
34
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pi-dynamic-workflows-py
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Dynamic workflow orchestration extension for pi-python (port of pi-dynamic-workflows)
|
|
5
|
+
Project-URL: Homepage, https://github.com/zy1233/pi-python
|
|
6
|
+
Project-URL: Repository, https://github.com/zy1233/pi-python
|
|
7
|
+
Author-email: zy1233 <zy1233@users.noreply.github.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Keywords: agent,extension,orchestration,pi,subagent,workflow
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Requires-Dist: pi-agent-core-lc>=0.3.0
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# pi-dynamic-workflows-py
|
|
15
|
+
|
|
16
|
+
Dynamic workflow orchestration extension for [pi-python](https://github.com/zy1233/pi-python).
|
|
17
|
+
|
|
18
|
+
Python port of [@quintinshaw/pi-dynamic-workflows](https://github.com/QuintinShaw/pi-dynamic-workflows).
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install pi-dynamic-workflows-py
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Overview
|
|
27
|
+
|
|
28
|
+
The `workflow` tool lets the LLM write a Python orchestration script that
|
|
29
|
+
fans work out across isolated subagents via `agent()`, `parallel()`,
|
|
30
|
+
`pipeline()`, and `phase()`.
|
|
31
|
+
|
|
32
|
+
## Built-in Patterns
|
|
33
|
+
|
|
34
|
+
- `/deep-research` — Research a question across the web with cross-checked sources
|
|
35
|
+
- `/adversarial-review` — Investigate then cross-check findings with skeptical reviewers
|
|
36
|
+
- `/code-review` — Multi-angle parallel code review
|
|
37
|
+
- `/multi-perspective` — Analyze a topic from several perspectives, then synthesize
|
|
38
|
+
- `/codebase-audit` — Run parallel checks against a codebase scope
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# pi-dynamic-workflows-py
|
|
2
|
+
|
|
3
|
+
Dynamic workflow orchestration extension for [pi-python](https://github.com/zy1233/pi-python).
|
|
4
|
+
|
|
5
|
+
Python port of [@quintinshaw/pi-dynamic-workflows](https://github.com/QuintinShaw/pi-dynamic-workflows).
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install pi-dynamic-workflows-py
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Overview
|
|
14
|
+
|
|
15
|
+
The `workflow` tool lets the LLM write a Python orchestration script that
|
|
16
|
+
fans work out across isolated subagents via `agent()`, `parallel()`,
|
|
17
|
+
`pipeline()`, and `phase()`.
|
|
18
|
+
|
|
19
|
+
## Built-in Patterns
|
|
20
|
+
|
|
21
|
+
- `/deep-research` — Research a question across the web with cross-checked sources
|
|
22
|
+
- `/adversarial-review` — Investigate then cross-check findings with skeptical reviewers
|
|
23
|
+
- `/code-review` — Multi-angle parallel code review
|
|
24
|
+
- `/multi-perspective` — Analyze a topic from several perspectives, then synthesize
|
|
25
|
+
- `/codebase-audit` — Run parallel checks against a codebase scope
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""pi-dynamic-workflows — Dynamic workflow orchestration for pi-python.
|
|
2
|
+
|
|
3
|
+
Provides:
|
|
4
|
+
- ``workflow`` tool for multi-agent orchestration via Python scripts
|
|
5
|
+
- ``/workflows`` command for listing and managing runs
|
|
6
|
+
- 5 built-in workflow patterns: deep-research, adversarial-review,
|
|
7
|
+
code-review, multi-perspective, codebase-audit
|
|
8
|
+
- Model tier routing (small/medium/big)
|
|
9
|
+
- Token budget tracking
|
|
10
|
+
|
|
11
|
+
Install: ``pip install pi-dynamic-workflows-py``
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
from typing import TYPE_CHECKING
|
|
18
|
+
|
|
19
|
+
from pi_dynamic_workflows.builtin_workflows import (
|
|
20
|
+
BUILTIN_WORKFLOW_NAMES,
|
|
21
|
+
BUILTIN_WORKFLOWS,
|
|
22
|
+
)
|
|
23
|
+
from pi_dynamic_workflows.builtin_workflows import (
|
|
24
|
+
resolve_builtin_workflow as resolve_builtin_workflow,
|
|
25
|
+
)
|
|
26
|
+
from pi_dynamic_workflows.workflow_tool import create_workflow_tool
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from pi_agent_core.extensions import ExtensionAPI
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _handle_workflows_command(pi: ExtensionAPI, args: str) -> None:
|
|
35
|
+
"""Handler for ``/workflows`` command."""
|
|
36
|
+
parts = args.strip().split(maxsplit=1)
|
|
37
|
+
sub = parts[0] if parts else "list"
|
|
38
|
+
|
|
39
|
+
if sub == "list" or not sub:
|
|
40
|
+
names = ", ".join(BUILTIN_WORKFLOW_NAMES)
|
|
41
|
+
pi.send_message(f"Available built-in workflows: {names}")
|
|
42
|
+
elif sub == "help":
|
|
43
|
+
lines = ["## Built-in Workflow Patterns\n"]
|
|
44
|
+
for name, desc in BUILTIN_WORKFLOWS.items():
|
|
45
|
+
lines.append(f"- **{name}**: {desc.description}")
|
|
46
|
+
lines.append("\nUse the `workflow` tool with `name` parameter to run a built-in pattern.")
|
|
47
|
+
pi.send_message("\n".join(lines))
|
|
48
|
+
else:
|
|
49
|
+
pi.send_message(f"Unknown subcommand: {sub}\nUsage: /workflows [list|help]")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _register_builtin_commands(pi: ExtensionAPI) -> None:
|
|
53
|
+
"""Register slash commands for each built-in workflow pattern.
|
|
54
|
+
|
|
55
|
+
These are passthrough commands: advertised for client autocomplete
|
|
56
|
+
but forwarded to the LLM so it invokes the ``workflow`` tool.
|
|
57
|
+
"""
|
|
58
|
+
_noop = lambda args: None # noqa: E731
|
|
59
|
+
for name, desc in BUILTIN_WORKFLOWS.items():
|
|
60
|
+
pi.register_command(
|
|
61
|
+
name,
|
|
62
|
+
description=desc.description,
|
|
63
|
+
handler=_noop,
|
|
64
|
+
passthrough=True,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _try_build_real_executor(pi: ExtensionAPI) -> tuple:
|
|
69
|
+
"""Try to build a HarnessSubagentExecutor from the bridge. Returns (executor, manager)."""
|
|
70
|
+
try:
|
|
71
|
+
bridge = pi._require_bridge()
|
|
72
|
+
stream_fn = getattr(bridge, "stream_fn", None)
|
|
73
|
+
model = getattr(bridge, "model", None)
|
|
74
|
+
get_api_key = getattr(bridge, "get_api_key_fn", None)
|
|
75
|
+
if stream_fn is None or model is None:
|
|
76
|
+
return None, None
|
|
77
|
+
|
|
78
|
+
from pi_dynamic_workflows.manager import WorkflowManager
|
|
79
|
+
from pi_dynamic_workflows.subagent import HarnessSubagentExecutor
|
|
80
|
+
|
|
81
|
+
executor = HarnessSubagentExecutor(
|
|
82
|
+
stream_fn=stream_fn,
|
|
83
|
+
parent_model=model,
|
|
84
|
+
cwd=pi.cwd,
|
|
85
|
+
get_api_key=get_api_key,
|
|
86
|
+
)
|
|
87
|
+
manager = WorkflowManager(bridge)
|
|
88
|
+
return executor, manager
|
|
89
|
+
except Exception:
|
|
90
|
+
logger.debug("Could not build real executor; falling back to mock", exc_info=True)
|
|
91
|
+
return None, None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _register_saved_workflows(pi: ExtensionAPI) -> None:
|
|
95
|
+
"""Scan saved workflow directories and register slash commands."""
|
|
96
|
+
try:
|
|
97
|
+
from pi_dynamic_workflows.store import WorkflowStore
|
|
98
|
+
|
|
99
|
+
store = WorkflowStore(cwd=pi.cwd)
|
|
100
|
+
for wf in store.scan():
|
|
101
|
+
if wf.name in BUILTIN_WORKFLOW_NAMES:
|
|
102
|
+
continue
|
|
103
|
+
pi.register_command(
|
|
104
|
+
wf.name,
|
|
105
|
+
description=wf.description or f"Run saved workflow: {wf.name}",
|
|
106
|
+
handler=lambda args, _wf=wf: pi.send_message(
|
|
107
|
+
f"To run the saved workflow **{_wf.name}**, use the `workflow` tool with:\n"
|
|
108
|
+
f" script=<contents of {_wf.path}>\n\n"
|
|
109
|
+
f"Description: {_wf.description}"
|
|
110
|
+
),
|
|
111
|
+
)
|
|
112
|
+
except Exception:
|
|
113
|
+
logger.debug("Saved workflow scan failed", exc_info=True)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def activate(pi: ExtensionAPI) -> None:
|
|
117
|
+
"""Extension entry point — called by the ExtensionLoader."""
|
|
118
|
+
executor, manager = _try_build_real_executor(pi)
|
|
119
|
+
|
|
120
|
+
pi.register_tool(create_workflow_tool(executor=executor, cwd=pi.cwd, manager=manager))
|
|
121
|
+
|
|
122
|
+
pi.register_command(
|
|
123
|
+
"workflows",
|
|
124
|
+
description="List and manage dynamic workflows",
|
|
125
|
+
handler=lambda args: _handle_workflows_command(pi, args if isinstance(args, str) else ""),
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
_register_builtin_commands(pi)
|
|
129
|
+
_register_saved_workflows(pi)
|
|
130
|
+
|
|
131
|
+
if manager is not None:
|
|
132
|
+
bridge = pi._require_bridge()
|
|
133
|
+
bridge.register_cleanup(manager.shutdown)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Token budget tracking for workflow runs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import threading
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class TokenBudget:
|
|
11
|
+
"""Tracks token usage against an optional budget."""
|
|
12
|
+
|
|
13
|
+
total: int | None = None
|
|
14
|
+
_spent: int = field(default=0, init=False)
|
|
15
|
+
_lock: threading.Lock = field(default_factory=threading.Lock, init=False, repr=False)
|
|
16
|
+
|
|
17
|
+
def spent(self) -> int:
|
|
18
|
+
with self._lock:
|
|
19
|
+
return self._spent
|
|
20
|
+
|
|
21
|
+
def remaining(self) -> int | None:
|
|
22
|
+
if self.total is None:
|
|
23
|
+
return None
|
|
24
|
+
with self._lock:
|
|
25
|
+
return max(0, self.total - self._spent)
|
|
26
|
+
|
|
27
|
+
def add(self, tokens: int) -> None:
|
|
28
|
+
with self._lock:
|
|
29
|
+
self._spent += tokens
|
|
30
|
+
|
|
31
|
+
def exceeded(self) -> bool:
|
|
32
|
+
if self.total is None:
|
|
33
|
+
return False
|
|
34
|
+
with self._lock:
|
|
35
|
+
return self._spent >= self.total
|
|
36
|
+
|
|
37
|
+
def to_dict(self) -> dict[str, int | None]:
|
|
38
|
+
return {
|
|
39
|
+
"total": self.total,
|
|
40
|
+
"spent": self.spent(),
|
|
41
|
+
"remaining": self.remaining(),
|
|
42
|
+
}
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
"""Built-in workflow patterns (5 curated patterns from upstream)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
DEFAULT_MULTI_PERSPECTIVES = [
|
|
9
|
+
"technical",
|
|
10
|
+
"product",
|
|
11
|
+
"security",
|
|
12
|
+
"user experience",
|
|
13
|
+
"maintainability",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class BuiltinWorkflowInvocation:
|
|
19
|
+
script: str
|
|
20
|
+
name: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class BuiltinWorkflowDescriptor:
|
|
25
|
+
name: str
|
|
26
|
+
description: str
|
|
27
|
+
resolve: Any # (args) -> BuiltinWorkflowInvocation
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _require_str(args: dict[str, Any], key: str, pattern_name: str) -> str:
|
|
31
|
+
v = args.get(key)
|
|
32
|
+
if not isinstance(v, str) or not v.strip():
|
|
33
|
+
raise ValueError(
|
|
34
|
+
f'Built-in workflow "{pattern_name}" requires args.{key} to be a non-empty string.'
|
|
35
|
+
)
|
|
36
|
+
return v
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _require_str_list(args: dict[str, Any], key: str, pattern_name: str) -> list[str]:
|
|
40
|
+
v = args.get(key)
|
|
41
|
+
if not isinstance(v, list) or not v or not all(isinstance(s, str) and s.strip() for s in v):
|
|
42
|
+
raise ValueError(
|
|
43
|
+
f'Built-in workflow "{pattern_name}" requires args.{key} '
|
|
44
|
+
"to be a non-empty list of non-empty strings."
|
|
45
|
+
)
|
|
46
|
+
return v
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _as_record(args: Any) -> dict[str, Any]:
|
|
50
|
+
return args if isinstance(args, dict) else {}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
54
|
+
# Pattern generators — produce Python workflow scripts
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def generate_deep_research() -> str:
|
|
59
|
+
return """\
|
|
60
|
+
meta = {"name": "deep_research", "description": "Research a question with cross-checked sources"}
|
|
61
|
+
|
|
62
|
+
async def main():
|
|
63
|
+
question = args["question"]
|
|
64
|
+
phase("Generate search angles")
|
|
65
|
+
angles = await agent(
|
|
66
|
+
f"Generate 5 diverse search angles for researching: {question}\\n"
|
|
67
|
+
"Return each angle on a new line, numbered 1-5.",
|
|
68
|
+
tier="small",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
angle_list = [line.strip() for line in (angles or "").split("\\n") if line.strip() and line.strip()[0].isdigit()]
|
|
72
|
+
if not angle_list:
|
|
73
|
+
angle_list = [question]
|
|
74
|
+
|
|
75
|
+
phase("Research")
|
|
76
|
+
findings = await parallel([
|
|
77
|
+
lambda a=angle: agent(
|
|
78
|
+
f"Research this angle thoroughly: {a}\\n"
|
|
79
|
+
f"Original question: {question}\\n"
|
|
80
|
+
"Provide detailed findings with specific facts, data, and sources.",
|
|
81
|
+
tier="medium",
|
|
82
|
+
)
|
|
83
|
+
for angle in angle_list[:5]
|
|
84
|
+
])
|
|
85
|
+
|
|
86
|
+
phase("Cross-check")
|
|
87
|
+
combined = "\\n\\n---\\n\\n".join(f"Angle: {a}\\nFindings: {f}" for a, f in zip(angle_list, findings) if f)
|
|
88
|
+
verified = await agent(
|
|
89
|
+
f"Cross-check these research findings for accuracy and consistency:\\n\\n{combined}\\n\\n"
|
|
90
|
+
"Flag any contradictions, unsupported claims, or areas needing more evidence. "
|
|
91
|
+
"Rate confidence for each finding.",
|
|
92
|
+
tier="big",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
phase("Synthesize")
|
|
96
|
+
report = await agent(
|
|
97
|
+
f"Synthesize a comprehensive research report on: {question}\\n\\n"
|
|
98
|
+
f"Research findings:\\n{combined}\\n\\n"
|
|
99
|
+
f"Cross-check results:\\n{verified}\\n\\n"
|
|
100
|
+
"Write a well-structured report with an executive summary, key findings, "
|
|
101
|
+
"confidence levels, and areas for further research.",
|
|
102
|
+
tier="big",
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
result(report)
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def generate_adversarial_review() -> str:
|
|
110
|
+
return """\
|
|
111
|
+
meta = {"name": "adversarial_review", "description": "Investigate then cross-check with skeptical reviewers"}
|
|
112
|
+
|
|
113
|
+
async def main():
|
|
114
|
+
task = args["task"]
|
|
115
|
+
reviewers = args.get("reviewers", 3)
|
|
116
|
+
|
|
117
|
+
phase("Investigate")
|
|
118
|
+
investigation = await agent(
|
|
119
|
+
f"Thoroughly investigate: {task}\\n"
|
|
120
|
+
"Provide detailed findings with evidence and reasoning.",
|
|
121
|
+
tier="big",
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
phase("Adversarial review")
|
|
125
|
+
reviews = await parallel([
|
|
126
|
+
lambda i=i: agent(
|
|
127
|
+
f"You are Reviewer #{i+1}. Critically review this investigation:\\n\\n{investigation}\\n\\n"
|
|
128
|
+
"Be skeptical. Challenge assumptions, find gaps, identify weak evidence, "
|
|
129
|
+
"and suggest what was missed. Rate each finding as: confirmed / questionable / refuted.",
|
|
130
|
+
tier="medium",
|
|
131
|
+
)
|
|
132
|
+
for i in range(reviewers)
|
|
133
|
+
])
|
|
134
|
+
|
|
135
|
+
phase("Synthesize")
|
|
136
|
+
all_reviews = "\\n\\n---\\n\\n".join(f"Reviewer #{i+1}:\\n{r}" for i, r in enumerate(reviews) if r)
|
|
137
|
+
synthesis = await agent(
|
|
138
|
+
f"Synthesize the investigation and all reviewer feedback:\\n\\n"
|
|
139
|
+
f"Original investigation:\\n{investigation}\\n\\n"
|
|
140
|
+
f"Reviews:\\n{all_reviews}\\n\\n"
|
|
141
|
+
"Produce a final report with confidence-rated findings. "
|
|
142
|
+
"Only include findings that survived adversarial review.",
|
|
143
|
+
tier="big",
|
|
144
|
+
)
|
|
145
|
+
result(synthesis)
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def generate_code_review() -> str:
|
|
150
|
+
return """\
|
|
151
|
+
meta = {"name": "code_review", "description": "Multi-angle parallel code review"}
|
|
152
|
+
|
|
153
|
+
async def main():
|
|
154
|
+
diff = args["diff"]
|
|
155
|
+
diff_source = args.get("diff_source", "unknown")
|
|
156
|
+
|
|
157
|
+
phase("Parallel review")
|
|
158
|
+
angles = [
|
|
159
|
+
("correctness", "Find bugs, logic errors, off-by-one mistakes, race conditions, and unhandled edge cases."),
|
|
160
|
+
("security", "Find security vulnerabilities: injection, auth bypass, data exposure, unsafe deserialization."),
|
|
161
|
+
("performance", "Find performance issues: N+1 queries, unnecessary allocations, missing caching, O(n²) loops."),
|
|
162
|
+
("simplification", "Find unnecessary complexity: dead code, over-abstraction, code that could be simplified."),
|
|
163
|
+
("reuse", "Find code duplication and opportunities to use existing utilities or extract shared helpers."),
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
findings = await parallel([
|
|
167
|
+
lambda name=name, instruction=instruction: agent(
|
|
168
|
+
f"Review this diff as a {name} specialist:\\n\\n```diff\\n{diff[:20000]}\\n```\\n\\n{instruction}\\n"
|
|
169
|
+
"List each finding with file, line, severity (critical/major/minor), and a fix suggestion.",
|
|
170
|
+
tier="medium",
|
|
171
|
+
)
|
|
172
|
+
for name, instruction in angles
|
|
173
|
+
])
|
|
174
|
+
|
|
175
|
+
phase("Verify and rank")
|
|
176
|
+
combined = "\\n\\n---\\n\\n".join(
|
|
177
|
+
f"**{name}** reviewer:\\n{f}"
|
|
178
|
+
for (name, _), f in zip(angles, findings)
|
|
179
|
+
if f
|
|
180
|
+
)
|
|
181
|
+
verified = await agent(
|
|
182
|
+
f"You received findings from {len(angles)} code reviewers. Verify and rank them:\\n\\n{combined}\\n\\n"
|
|
183
|
+
"Remove duplicates and false positives. Rank remaining findings by impact. "
|
|
184
|
+
"Output a numbered list, most critical first, with actionable fix suggestions.",
|
|
185
|
+
tier="big",
|
|
186
|
+
)
|
|
187
|
+
result(verified)
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def generate_multi_perspective(topic: str, perspectives: list[str]) -> str:
|
|
192
|
+
persp_list = repr(perspectives)
|
|
193
|
+
return f"""\
|
|
194
|
+
meta = {{"name": "multi_perspective", "description": "Analyze from multiple perspectives"}}
|
|
195
|
+
|
|
196
|
+
async def main():
|
|
197
|
+
topic = args.get("topic", {topic!r})
|
|
198
|
+
perspectives = args.get("perspectives", {persp_list})
|
|
199
|
+
|
|
200
|
+
phase("Parallel analysis")
|
|
201
|
+
analyses = await parallel([
|
|
202
|
+
lambda p=p: agent(
|
|
203
|
+
f"Analyze this topic from a {{p}} perspective:\\n\\n{{topic}}\\n\\n"
|
|
204
|
+
f"Focus exclusively on {{p}} concerns, trade-offs, and implications. "
|
|
205
|
+
"Be specific and provide actionable insights.",
|
|
206
|
+
tier="medium",
|
|
207
|
+
)
|
|
208
|
+
for p in perspectives
|
|
209
|
+
])
|
|
210
|
+
|
|
211
|
+
phase("Synthesize")
|
|
212
|
+
combined = "\\n\\n---\\n\\n".join(
|
|
213
|
+
f"**{{p}}** perspective:\\n{{a}}"
|
|
214
|
+
for p, a in zip(perspectives, analyses)
|
|
215
|
+
if a
|
|
216
|
+
)
|
|
217
|
+
synthesis = await agent(
|
|
218
|
+
f"Synthesize these multi-perspective analyses into a unified assessment:\\n\\n{{combined}}\\n\\n"
|
|
219
|
+
"Identify consensus, tensions, and trade-offs. "
|
|
220
|
+
"Provide a balanced recommendation that accounts for all perspectives.",
|
|
221
|
+
tier="big",
|
|
222
|
+
)
|
|
223
|
+
result(synthesis)
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def generate_codebase_audit(scope: str, checks: list[str]) -> str:
|
|
228
|
+
return f"""\
|
|
229
|
+
meta = {{"name": "codebase_audit", "description": "Parallel codebase audit"}}
|
|
230
|
+
|
|
231
|
+
async def main():
|
|
232
|
+
scope = args.get("scope", {scope!r})
|
|
233
|
+
checks = args.get("checks", {checks!r})
|
|
234
|
+
|
|
235
|
+
phase("Parallel checks")
|
|
236
|
+
findings = await parallel([
|
|
237
|
+
lambda check=check: agent(
|
|
238
|
+
f"Audit the codebase (scope: {{scope}}) for: {{check}}\\n\\n"
|
|
239
|
+
"Examine the code thoroughly. List each finding with file path, "
|
|
240
|
+
"line numbers, severity, and a recommended fix.",
|
|
241
|
+
tier="medium",
|
|
242
|
+
)
|
|
243
|
+
for check in checks
|
|
244
|
+
])
|
|
245
|
+
|
|
246
|
+
phase("Cross-validate")
|
|
247
|
+
combined = "\\n\\n---\\n\\n".join(
|
|
248
|
+
f"**{{check}}**:\\n{{f}}"
|
|
249
|
+
for check, f in zip(checks, findings)
|
|
250
|
+
if f
|
|
251
|
+
)
|
|
252
|
+
validated = await agent(
|
|
253
|
+
f"Cross-validate and compile these audit findings:\\n\\n{{combined}}\\n\\n"
|
|
254
|
+
"Remove false positives, merge overlapping findings, and produce "
|
|
255
|
+
"a prioritized report with actionable recommendations.",
|
|
256
|
+
tier="big",
|
|
257
|
+
)
|
|
258
|
+
result(validated)
|
|
259
|
+
"""
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
# ---------------------------------------------------------------------------
|
|
263
|
+
# Registry
|
|
264
|
+
# ---------------------------------------------------------------------------
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _resolve_multi_perspective(args: Any) -> BuiltinWorkflowInvocation:
|
|
268
|
+
a = _as_record(args)
|
|
269
|
+
return BuiltinWorkflowInvocation(
|
|
270
|
+
script=generate_multi_perspective(
|
|
271
|
+
_require_str(a, "topic", "multi-perspective"),
|
|
272
|
+
a.get("perspectives", DEFAULT_MULTI_PERSPECTIVES),
|
|
273
|
+
),
|
|
274
|
+
name="multi-perspective",
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _resolve_codebase_audit(args: Any) -> BuiltinWorkflowInvocation:
|
|
279
|
+
a = _as_record(args)
|
|
280
|
+
return BuiltinWorkflowInvocation(
|
|
281
|
+
script=generate_codebase_audit(
|
|
282
|
+
_require_str(a, "scope", "codebase-audit"),
|
|
283
|
+
_require_str_list(a, "checks", "codebase-audit"),
|
|
284
|
+
),
|
|
285
|
+
name="codebase-audit",
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
BUILTIN_WORKFLOWS: dict[str, BuiltinWorkflowDescriptor] = {
|
|
290
|
+
"deep-research": BuiltinWorkflowDescriptor(
|
|
291
|
+
name="deep-research",
|
|
292
|
+
description=(
|
|
293
|
+
"Research a question across the web with "
|
|
294
|
+
"cross-checked sources. args: { question: str }."
|
|
295
|
+
),
|
|
296
|
+
resolve=lambda args: BuiltinWorkflowInvocation(
|
|
297
|
+
script=generate_deep_research(),
|
|
298
|
+
name="deep-research",
|
|
299
|
+
),
|
|
300
|
+
),
|
|
301
|
+
"adversarial-review": BuiltinWorkflowDescriptor(
|
|
302
|
+
name="adversarial-review",
|
|
303
|
+
description=(
|
|
304
|
+
"Investigate then cross-check with skeptical reviewers. "
|
|
305
|
+
"args: { task: str, reviewers?: int }."
|
|
306
|
+
),
|
|
307
|
+
resolve=lambda args: BuiltinWorkflowInvocation(
|
|
308
|
+
script=generate_adversarial_review(),
|
|
309
|
+
name="adversarial-review",
|
|
310
|
+
),
|
|
311
|
+
),
|
|
312
|
+
"code-review": BuiltinWorkflowDescriptor(
|
|
313
|
+
name="code-review",
|
|
314
|
+
description="Multi-angle parallel code review. args: { diff: str }.",
|
|
315
|
+
resolve=lambda args: BuiltinWorkflowInvocation(
|
|
316
|
+
script=generate_code_review(),
|
|
317
|
+
name="code-review",
|
|
318
|
+
),
|
|
319
|
+
),
|
|
320
|
+
"multi-perspective": BuiltinWorkflowDescriptor(
|
|
321
|
+
name="multi-perspective",
|
|
322
|
+
description=(
|
|
323
|
+
"Analyze a topic from several perspectives, then synthesize. "
|
|
324
|
+
"args: { topic: str, perspectives?: list[str] }."
|
|
325
|
+
),
|
|
326
|
+
resolve=_resolve_multi_perspective,
|
|
327
|
+
),
|
|
328
|
+
"codebase-audit": BuiltinWorkflowDescriptor(
|
|
329
|
+
name="codebase-audit",
|
|
330
|
+
description=(
|
|
331
|
+
"Run parallel checks against a codebase scope. args: { scope: str, checks: list[str] }."
|
|
332
|
+
),
|
|
333
|
+
resolve=_resolve_codebase_audit,
|
|
334
|
+
),
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
BUILTIN_WORKFLOW_NAMES = list(BUILTIN_WORKFLOWS.keys())
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def resolve_builtin_workflow(name: str, args: Any = None) -> BuiltinWorkflowInvocation | None:
|
|
341
|
+
desc = BUILTIN_WORKFLOWS.get(name)
|
|
342
|
+
if desc is None:
|
|
343
|
+
return None
|
|
344
|
+
return desc.resolve(args)
|