aop-launcher 0.0.1.dev0__py3-none-any.whl

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.
@@ -0,0 +1,5 @@
1
+ from aop_launcher.core import Agent, Workflow, WorkflowError, load_workflow, run_workflow
2
+
3
+ __all__ = ["Agent", "Workflow", "WorkflowError", "load_workflow", "run_workflow"]
4
+ __version__ = "0.0.1.dev0"
5
+
aop_launcher/cli.py ADDED
@@ -0,0 +1,48 @@
1
+ import argparse
2
+ import sys
3
+
4
+ from aop_launcher.core import WorkflowError, load_workflow, run_workflow
5
+
6
+
7
+ def build_parser():
8
+ parser = argparse.ArgumentParser(
9
+ prog="aop-launcher",
10
+ description="Run a local YAML agent workflow.",
11
+ )
12
+ parser.add_argument("workflow", help="Path to the YAML workflow file.")
13
+ parser.add_argument("input", nargs="?", default="", help="Initial workflow input.")
14
+ parser.add_argument(
15
+ "--llm-command",
16
+ help="Local LLM command. Overrides llm.command in the workflow file.",
17
+ )
18
+ parser.add_argument(
19
+ "--no-tools",
20
+ action="store_true",
21
+ help="Skip workflow tool commands.",
22
+ )
23
+ return parser
24
+
25
+
26
+ def main(argv=None):
27
+ parser = build_parser()
28
+ args = parser.parse_args(argv)
29
+
30
+ try:
31
+ workflow = load_workflow(args.workflow)
32
+ result = run_workflow(
33
+ workflow,
34
+ args.input,
35
+ llm_command=args.llm_command,
36
+ run_tools=not args.no_tools,
37
+ stream=sys.stdout,
38
+ )
39
+ except WorkflowError as exc:
40
+ print(f"aop-launcher: {exc}", file=sys.stderr)
41
+ return 2
42
+
43
+ return 0 if result else 1
44
+
45
+
46
+ if __name__ == "__main__":
47
+ raise SystemExit(main())
48
+
aop_launcher/core.py ADDED
@@ -0,0 +1,180 @@
1
+ from dataclasses import dataclass, field
2
+ from pathlib import Path
3
+ import shlex
4
+ import subprocess
5
+ import sys
6
+
7
+ import yaml
8
+
9
+
10
+ class WorkflowError(Exception):
11
+ """Raised when a workflow cannot be loaded or executed."""
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class Agent:
16
+ name: str
17
+ prompt: str
18
+ tools: tuple = field(default_factory=tuple)
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class Workflow:
23
+ agents: tuple
24
+ tools: dict = field(default_factory=dict)
25
+ llm_command: str = ""
26
+
27
+
28
+ def load_workflow(path):
29
+ data = _load_yaml(path)
30
+ agents = tuple(_parse_agent(item) for item in data.get("agents", []))
31
+ if not agents:
32
+ raise WorkflowError("workflow must define at least one agent")
33
+
34
+ llm_data = data.get("llm") or {}
35
+ if llm_data and not isinstance(llm_data, dict):
36
+ raise WorkflowError("llm must be a mapping")
37
+
38
+ tools = data.get("tools") or {}
39
+ if not isinstance(tools, dict):
40
+ raise WorkflowError("tools must be a mapping of name to command")
41
+
42
+ return Workflow(
43
+ agents=agents,
44
+ tools={str(name): str(command) for name, command in tools.items()},
45
+ llm_command=str(llm_data.get("command", "")),
46
+ )
47
+
48
+
49
+ def run_workflow(
50
+ workflow,
51
+ initial_input="",
52
+ *,
53
+ llm_command=None,
54
+ run_tools=True,
55
+ stream=None,
56
+ ):
57
+ output = str(initial_input)
58
+ terminal = stream or sys.stdout
59
+ command = llm_command or workflow.llm_command
60
+ if not command:
61
+ raise WorkflowError("missing local LLM command")
62
+
63
+ for agent in workflow.agents:
64
+ print(f"\n== {agent.name} ==", file=terminal)
65
+ tool_results = _run_tools(agent, workflow.tools, enabled=run_tools)
66
+ prompt = _render_prompt(agent.prompt, output, tool_results)
67
+ output = _run_llm(command, prompt, terminal)
68
+
69
+ return output
70
+
71
+
72
+ def _load_yaml(path):
73
+ try:
74
+ with open(path, "r", encoding="utf-8") as handle:
75
+ data = yaml.safe_load(handle) or {}
76
+ except OSError as exc:
77
+ raise WorkflowError(f"cannot read workflow: {exc}") from exc
78
+ except yaml.YAMLError as exc:
79
+ raise WorkflowError(f"invalid YAML: {exc}") from exc
80
+
81
+ if not isinstance(data, dict):
82
+ raise WorkflowError("workflow must be a YAML mapping")
83
+ return data
84
+
85
+
86
+ def _parse_agent(item):
87
+ if not isinstance(item, dict):
88
+ raise WorkflowError("each agent must be a mapping")
89
+
90
+ name = str(item.get("name", "")).strip()
91
+ prompt = str(item.get("prompt", "")).strip()
92
+ if not name:
93
+ raise WorkflowError("agent is missing a name")
94
+ if not prompt:
95
+ raise WorkflowError(f"agent {name!r} is missing a prompt")
96
+
97
+ tools = item.get("tools") or ()
98
+ if isinstance(tools, str):
99
+ tools = (tools,)
100
+ elif isinstance(tools, (list, tuple)):
101
+ tools = tuple(str(tool) for tool in tools)
102
+ else:
103
+ raise WorkflowError(f"agent {name!r} tools must be a string or list")
104
+
105
+ return Agent(name=name, prompt=prompt, tools=tools)
106
+
107
+
108
+ def _run_tools(agent, tools, *, enabled):
109
+ if not enabled:
110
+ return {}
111
+
112
+ results = {}
113
+ for name in agent.tools:
114
+ if name not in tools:
115
+ raise WorkflowError(f"agent {agent.name!r} references unknown tool {name!r}")
116
+ command = tools[name]
117
+ completed = subprocess.run(
118
+ command,
119
+ shell=True,
120
+ text=True,
121
+ capture_output=True,
122
+ check=False,
123
+ )
124
+ results[name] = {
125
+ "returncode": completed.returncode,
126
+ "stdout": completed.stdout.strip(),
127
+ "stderr": completed.stderr.strip(),
128
+ }
129
+ return results
130
+
131
+
132
+ def _render_prompt(template, input_text, tool_results):
133
+ return template.format(
134
+ input=input_text,
135
+ tool_results=_format_tool_results(tool_results),
136
+ )
137
+
138
+
139
+ def _format_tool_results(tool_results):
140
+ if not tool_results:
141
+ return ""
142
+
143
+ lines = []
144
+ for name, result in tool_results.items():
145
+ lines.append(f"[{name}] returncode={result['returncode']}")
146
+ if result["stdout"]:
147
+ lines.append(result["stdout"])
148
+ if result["stderr"]:
149
+ lines.append(f"stderr: {result['stderr']}")
150
+ return "\n".join(lines)
151
+
152
+
153
+ def _run_llm(command, prompt, stream):
154
+ args = shlex.split(command)
155
+ if not args:
156
+ raise WorkflowError("local LLM command is empty")
157
+
158
+ try:
159
+ process = subprocess.Popen(
160
+ args,
161
+ stdin=subprocess.PIPE,
162
+ stdout=subprocess.PIPE,
163
+ stderr=subprocess.PIPE,
164
+ text=True,
165
+ )
166
+ except OSError as exc:
167
+ raise WorkflowError(f"cannot start local LLM command: {exc}") from exc
168
+
169
+ stdout, stderr = process.communicate(prompt)
170
+ if stdout:
171
+ stream.write(stdout)
172
+ if not stdout.endswith("\n"):
173
+ stream.write("\n")
174
+ stream.flush()
175
+
176
+ if process.returncode != 0:
177
+ message = stderr.strip() or f"local LLM exited with {process.returncode}"
178
+ raise WorkflowError(message)
179
+
180
+ return stdout.rstrip()
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.4
2
+ Name: aop-launcher
3
+ Version: 0.0.1.dev0
4
+ Summary: Lightweight launcher for local AI agent workflows
5
+ Author: aone
6
+ License-Expression: MIT
7
+ Keywords: agents,llm,workflow,yaml,terminal
8
+ Classifier: Development Status :: 2 - Pre-Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: Programming Language :: Python :: 3.8
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Software Development
19
+ Classifier: Topic :: Utilities
20
+ Requires-Python: >=3.8
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: PyYAML>=6.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest; extra == "dev"
25
+
26
+ # aop_launcher
27
+
28
+ Agent Orchestration Platform Launcher.
29
+
30
+ `aop_launcher` is a tiny terminal runner for local AI agent workflows. It reads
31
+ a YAML file, runs agents in sequence, optionally runs local shell tools for each
32
+ agent, sends the prompt to a local LLM command, and streams the result to the
33
+ terminal.
34
+
35
+ This first `0.0.1.dev0` release is intentionally small. It does not provide
36
+ remote execution, scheduling, retries, memory stores, or a hosted service.
37
+
38
+ ## Install
39
+
40
+ ```bash
41
+ pip install aop-launcher
42
+ ```
43
+
44
+ ## Example workflow
45
+
46
+ ```yaml
47
+ llm:
48
+ command: "ollama run llama3.2"
49
+
50
+ tools:
51
+ now: "date"
52
+
53
+ agents:
54
+ - name: planner
55
+ tools: ["now"]
56
+ prompt: |
57
+ Create a short plan for this task:
58
+ {input}
59
+
60
+ Tool results:
61
+ {tool_results}
62
+
63
+ - name: writer
64
+ prompt: |
65
+ Write the final answer from this plan:
66
+ {input}
67
+ ```
68
+
69
+ Run it:
70
+
71
+ ```bash
72
+ aop-launcher workflow.yml "Draft a release checklist"
73
+ ```
74
+
75
+ You can also pass the LLM command from the terminal:
76
+
77
+ ```bash
78
+ aop-launcher workflow.yml "hello" --llm-command "ollama run llama3.2"
79
+ ```
80
+
@@ -0,0 +1,8 @@
1
+ aop_launcher/__init__.py,sha256=C-yjkTeNaYmHT3cXYurhnpMemPBo0Ug7lPo_4qXZQG4,201
2
+ aop_launcher/cli.py,sha256=-5V2txWhiLHAA1aL0ivCkzDdFPajsk4XzF5MSZsgV4s,1236
3
+ aop_launcher/core.py,sha256=35FcNvOvMNGHS_gug9feu9sUF7DOkmuXQxNUiyf1mo8,4982
4
+ aop_launcher-0.0.1.dev0.dist-info/METADATA,sha256=OzQYp8XA7X_5unVFGPVTyQFyTjSzh5iLrq0dwNH9uJo,1986
5
+ aop_launcher-0.0.1.dev0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
6
+ aop_launcher-0.0.1.dev0.dist-info/entry_points.txt,sha256=HEjzM1tkXmlutwG2HpIlQVA7fPJVIW9hX4Fg9RSgvLY,55
7
+ aop_launcher-0.0.1.dev0.dist-info/top_level.txt,sha256=7GK9ixyLSob29whXn3NujT_68XsnbNrMp67xwKjSYc8,13
8
+ aop_launcher-0.0.1.dev0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ aop-launcher = aop_launcher.cli:main
@@ -0,0 +1 @@
1
+ aop_launcher