From 16ee990a5d8104333a816a76aab1f9d013bab116 Mon Sep 17 00:00:00 2001 From: Codespace Bot Date: Fri, 5 Jun 2026 14:00:19 +0000 Subject: [PATCH] chore(harness): add harness runner, agents, logging and defaults --- harness/README.md | 19 +++ harness/__init__.py | 1 + harness/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 171 bytes harness/__pycache__/harness.cpython-312.pyc | Bin 0 -> 2744 bytes .../__pycache__/logging_setup.cpython-312.pyc | Bin 0 -> 1632 bytes harness/agent_team.md | 34 ++++++ harness/agents/__init__.py | 1 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 143 bytes .../example_agents.cpython-312.pyc | Bin 0 -> 3056 bytes .../__pycache__/repo_agents.cpython-312.pyc | Bin 0 -> 7410 bytes harness/agents/example_agents.py | 49 ++++++++ harness/agents/repo_agents.py | 102 ++++++++++++++++ harness/config/agents.json | 14 +++ harness/config/harness_defaults.json | 23 ++++ harness/harness.py | 115 ++++++++++++++++++ harness/logging_setup.py | 31 +++++ 16 files changed, 389 insertions(+) create mode 100644 harness/README.md create mode 100644 harness/__init__.py create mode 100644 harness/__pycache__/__init__.cpython-312.pyc create mode 100644 harness/__pycache__/harness.cpython-312.pyc create mode 100644 harness/__pycache__/logging_setup.cpython-312.pyc create mode 100644 harness/agent_team.md create mode 100644 harness/agents/__init__.py create mode 100644 harness/agents/__pycache__/__init__.cpython-312.pyc create mode 100644 harness/agents/__pycache__/example_agents.cpython-312.pyc create mode 100644 harness/agents/__pycache__/repo_agents.cpython-312.pyc create mode 100644 harness/agents/example_agents.py create mode 100644 harness/agents/repo_agents.py create mode 100644 harness/config/agents.json create mode 100644 harness/config/harness_defaults.json create mode 100644 harness/harness.py create mode 100644 harness/logging_setup.py diff --git a/harness/README.md b/harness/README.md new file mode 100644 index 0000000..18f72f2 --- /dev/null +++ b/harness/README.md @@ -0,0 +1,19 @@ +Minimal Harness for revfactory/harness + +Quick start + +1. Run the harness: + +```bash +python3 harness/harness.py --config harness/config/agents.json +``` + +What this does + +- Loads `harness/config/agents.json` which declares the agent team and simple parameters. +- Dynamically imports example agent modules from `harness/agents/` and calls their `run(config)` function. + +Next steps + +- Replace or extend the example agents in `harness/agents/` with real implementations. +- Hook the harness into CI or orchestration for automated runs. diff --git a/harness/__init__.py b/harness/__init__.py new file mode 100644 index 0000000..c2f1b92 --- /dev/null +++ b/harness/__init__.py @@ -0,0 +1 @@ +"""harness package init""" diff --git a/harness/__pycache__/__init__.cpython-312.pyc b/harness/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..114ab04a3060a7b40483361f9d2c5aebbd62c1da GIT binary patch literal 171 zcmX@j%ge<81Wzt0Wl00+#~=<2FhUuhIe?7m3@Hpz43&(UOjRNoiA8y-#l;E*iOJcC z>8T2td6^}CnvA#DB+d?ujBz z9@)Jk95Q6f0xc>b0YpEEMN*VS9;(1W>z|N5_r)|-NZF`Bfb=DAs#G9=U)tH@9VHkx z^3suRmOC^1-R^fYJAXF}9fADq(Tj!uDhPcpCV@!)kG-G6k2OS58c|H8B2Hs(l+uzn z%4yjfNt(bY7nPEdRxqOEHz=)A1$2n2pfxK@LwCN85Yr@NY4?42jA~L%ifi@uLcUZf zTCpXQ*_PwPSk<;IHXkdNP3pwV*!LH1q+;2!y_nC%Oq<56x2$Z{wZN6LY&VuQi$(Kx z(Q*>H{u2jm^RY_JT`Joz2Z%`@IZ>&_=H`4P-^ zEublM4mtR)gwaie;jf0?l5e8BI8IU=v$|4s7w5h$wrSbfGR@n$xXekpV%eOCb8sRU zj7ywUOm~UnMMq%AVvo-x@0Qser($L;C*bDE5xC+Ju|6{(h#9a}JkCp?T4+0LEYtSo z?#xDbb_ZeQ_`110dU&Ga{sb$fo>^2su9_w@JR@v+ScOZYbDALutst=V_{LIItJI zQVM2+F-Y=)pzH|=e3aC|(+o+dp*9Glg3n7+`@dm_pTUI^u+<1P!VQh8?~?@-52d(T zD${Dw;#yEuT&6(?NriiInVYciM1mQA_JET>v@;~_5RESHC&qRY%J z=5KRj@H68Bhrn&RE@PrDxLS3ycKI&EJLSbPE17Ow=eh^)+M>O@U!t1bzUjg|^0zuO;p! zy2twCv#lR&9XYXoLswg?^gF}f8rYttdB20T)5ebq#u&>9&8h3g$hKZ zsNi!CK?)tbkqQS(&5sV!uPKU6l;0B~?sT}r{FPWl4NG7@bcllYleK}LS_*zaL+MIS zD+fw)`-Le}g{m}^$2ml`_vI#O$XC$MEGQGj6<^pB8g3|0Mbd+`eh>X&Si%^&B0)TX z5+=dPnox8!@*LO;hkf^fp`iv?z<~PHw?%^pz%c~QSMBNo#Jrm$=)g%v0u0#Y@O-XF zU{4AnfuE3I*L3uwL%~`0QEDhYma1z(oBOe756t>SHP0-l(m|K2tFGzXnLEb`vnu7f zlt@D3kn)tBgjeyaPtX3a=9U3uol|i4Kk1;_Ln-$6{E)iWnszVfXXVJl3D-pXs zSUmrQ$RBvQnzt!*>3EcDCd=O3N$;)M04Q^t(GbdTtK+OiCD~uSjT3-IXDQgj>u;JIv89A zq-5r8HV@_#@Hn4?f(EA@+SE?=w3B`9RO_`ZqWmuP>r{JwowQOLWNs@GUA?e!;hjsZ zEB}g2w%xV2@4elROtr3T8Dpz&th~`)>PmfMs&#coMUyAiUw!b(dg9~Pdn4Cd*FG~2 z?;=IHw2ZekqdnTie^&mabQeD`I=^~2lHAZ<+8#O98S7l@k4!JiTiR$l+|!=l)K2xZ zQ{AZz?aXIqlYhPT;k5_X`)4mMhdgiR+#k>X{(N`xy^D{u_)Y}C|1*LV{h>a+K~8Uz z@VnEivn#XhpLTBcCT1Ss4gH({ATP6NI49RK8FBhd1}@%ECPT~FOoq*Yhlz|HP%Aq% z$Gfk+@8NOpu_YFZ`#6*N5P4`GosYE(gS+|?`z}C;>UX{aYDdNx|C`8|>}d!e`V_%` vo6TPsjO8EG$9>#Ct-D8LGD?4Wqi7wY4$`d%^zz#`Irb literal 0 HcmV?d00001 diff --git a/harness/__pycache__/logging_setup.cpython-312.pyc b/harness/__pycache__/logging_setup.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..59c31e8e39478149ccd39f2f3fac73de90e6d272 GIT binary patch literal 1632 zcmcIkO>7%g5Pr}5_iV>W+&U$$+B5`=vIXKwaNtn&kc0p#B&0b|1xmEpw|4BIrV^;xA7)vh}08rwQpwL zd^2xmc4mKx#UcpSkB>(3f{4%`TnLuZWp=Itvw{KXBXRm@Iav0Tl~nc1ma!FXRYX~D3W zQn0cakXg8>eX+1ub`6L1ENc34X=OpTN~Wtr*!zozEWo?-dB_hLS49^G+B}>cT{LvR ztFKC)l$Va`5%N%gz{m|sf&hC=!U1v;T|0?TRR&b_7>{y*!|1C45gm%K=f7yTH#p1p z2iyS-JXIA>c~SI$fT)hHL$@c6>L7bDxFMjJ=l;?$NaYX$+ol(?PkA9KEKee2$K7OL z;bAIzAy1&v5AywPy{p9Re;z>vc%DOe|202yC_iZqsmgTaLTXfVbT?NtRA)Ff zS}<0Pf~on^r>W6B?iwhHhT~{i05N4rvrUj}-ir)t6{+NA-h4+DnOL-_!3cEuxZmW_ zy(XXFJ>K293P-Ff?WPxSjtl@rWWwS6P!~vGy&cs5_0sPY5_&6+P8%1k%f1F}- zu5D<=V7dgS>@eAJsa0}W4>*~!?4stn;4*x`Ml?#h7B-(QHOwSGSDVkC%|D_o^L&I% z(Pc7BERP8}6*~(+wxxt;as)#*o|s#8O%rTPd@ioSAbM zy2tj*;E7-5`~zDJbs`sQ6Hj7^c5J8-8`>;4V`^=xBSvn{+?ZK^wF2061i`-q&|7T|D~ph&zO9%_V#?l|99zO6LF=WF8~QNDTk z#^rUXC7$9uc>U(Rb|TeCq_*^C;(SYd9WHB&0}XLteX%7bpUQ~z{TfYdCbwq4JNqR5 z>eeR@-ufw7ze?-YYAb$yZT!#fS-k%7Z2dF66{okxJ0$XTbS(-2lhd5 Auditor -> Strategist -> ContentGenerator -> QA in a pipeline for each work item. +- Agents exchange structured JSON payloads; each agent reads the previous step's output and appends `result` and `metadata`. +- The Orchestrator keeps an audit log and can re-run or branch tasks for parallel experimentation. + +Data contracts + +- Input: {"id": "", "payload": {...}, "history": [...]} +- Agent output: {"id": "", "result": {...}, "metadata": {"agent":"","timestamp":"..."}} + +Failure and retries + +- Agents should return exit codes and error objects; Orchestrator retries transient failures with exponential backoff. +- Critical failures are surfaced to a human reviewer via the Orchestrator's notification hook. + +Extensibility + +- Add new agents by registering them in the config and implementing `run(config, input)`. +- Support for remote agents (HTTP/gRPC) can be added by providing an adapter layer behind the same interface. diff --git a/harness/agents/__init__.py b/harness/agents/__init__.py new file mode 100644 index 0000000..1ec7dea --- /dev/null +++ b/harness/agents/__init__.py @@ -0,0 +1 @@ +# agents package diff --git a/harness/agents/__pycache__/__init__.cpython-312.pyc b/harness/agents/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..502f3ab4c671f56d766b637613ff665e51a2a06b GIT binary patch literal 143 zcmX@j%ge<81V=9^WeEc5#~=^JijQrxF9h( zwOBtRu_!OK7(pkdr{&M4u=4F<|$LkeT{^GF7%}*)KNwq6t1!`vm;$jfvBQql- JV-Yiu1pv=HA^HFS literal 0 HcmV?d00001 diff --git a/harness/agents/__pycache__/example_agents.cpython-312.pyc b/harness/agents/__pycache__/example_agents.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7f664746dade7684ba760d53168e77708e35d878 GIT binary patch literal 3056 zcmb_eO>7fK6y9C0z5aQi%htDwPN%ii8A}Dz(Q-y?C{jY^_?UQcu0L$&FLr?0W6|(Fm21_RZ|P z**CM__syI6H5l|WP<}CvB$qr4^Cx|AimTe#K+w3$AZC_9EaFn^EDN^ziMFLnK@`CjvAK1VW7xCx68owc>vneb}37pYH znYPo=k~CS#sA5jVNW8ivYV%l}mq-SysyL?*5#cnrHDV3x_xs1C_`DdGQYq;wjTMjM ztfq=ry0a3I(&~GnoXO_2SXNp}DH4iY5v2?g3D$B1=8!}c6In{hdFx|>)m%#J_y0xz zB^qH(FOoE@$!Q$tHvKXMi#{8RVD2(n&6RXuto1MeqXP}gAWm~TuH9gKSal|S&Zv6r z_>k)!_c0UUqxoiq#OJYv1i+$d64CPEoqZTZIFrvP8a93OS72Z|yXH1MDJ7A>#B?Kk zHJ2cC>82;A#WTuv(k3&=vr-(Z167`CwZ*JD zfNi2<_G^E3$!yrXPSOaW8sJfTz$`Pr@%}GnKAS0qI!b(JCEWAl!JiKQaM&1`Dvw;$ zM=ly8pXh;0MgGzTjAphl!$XI~ObHr$GgDw5R(WNa0+Xz9L*v>7ameM2xyd0u!4^2= zzQ^5k?ZLXIQ2P#IyEMUxnDjcWdhKwV4AlFamg7T$#k)6}KcL2yoMsa)+T6U~p_;sk zF*1c*W&s$?%7qAp?1P5sw&o(O&<;`WWiXqV?tai&;*VAWp*tsT zpIDh&m5o66@>u22(c+uOo{#CF)5}veRdhb^m-wMdTl>@DXQNL>jiIsfP*fj^8bcrI zfeS_c0%?NaTXaF0wRF(}X=-m>)OT!x0oJ(!yQ?-_a!o(G`g!Dbw6n;NaDhwKQ~>L# z7UV$!^4{a>bMB!mUx9O?g0eWRmMfB0dUxX5wWFRSC1+c4e8_JpD-g{Gr5utqCz+T+ zbvv6vHG7&oRgcM-yquX+@-6kWoI;q0S;(J|ziGPBma^CsAlay}=kp;auB@s#tlBd3 z*e|xkgu28?3#`-hNZBmTpzTs~ZIYS@<5bFOO=LoK9&!YF-=OYmU}`d3Ib7o3sRSFB zCn`eI%K37*Ll1Y{m%nc|!pFGtdvaJbdum8{ggf`j*i(T<$ufcbzf1#`M5=kstpL zB=ZpMRggs7>qfD)icqWynX_;5ZD*?(B2mqy(-K*-E-sp+E&!53weKZh(j#yyn4ul> zkX{%>aovt-Pl@lX?CV)RTWNj!(dEanwV2U&s@ylJ_e~moQ+i;!$WQOYlxAv6odsyT zI;IKcwP@32-)g+qB>QN)2%_Ya1=CHhF6v9r>Z2fR&H#M!Ntz78(=?Rt(pD>m4}1` literal 0 HcmV?d00001 diff --git a/harness/agents/__pycache__/repo_agents.cpython-312.pyc b/harness/agents/__pycache__/repo_agents.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..08c7f6044b1a2fe8d090cfe35952a270a52e4349 GIT binary patch literal 7410 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 defaults > error + config_path = args.config or defaults.get("agents_config") + if not config_path: + raise SystemExit("No agents config provided (use --config or set agents_config in defaults)") + + # resolve logging options: CLI values override defaults + resolved_log_file = args.log_file if args.log_file is not None else defaults.get("logging", {}).get("log_file") + resolved_log_level = (args.log_level or defaults.get("logging", {}).get("log_level", "INFO")).upper() + resolved_max_bytes = args.log_max_bytes if args.log_max_bytes not in (None, 0) else defaults.get("logging", {}).get("log_max_bytes", 0) + resolved_backup_count = args.log_backup_count if args.log_backup_count not in (None, 0) else defaults.get("logging", {}).get("log_backup_count", 0) + + # initialize logging according to resolved values + try: + lvl = getattr(__import__("logging"), resolved_log_level) + except Exception: + lvl = getattr(__import__("logging"), "INFO") + global logger + logger = init_logging(level=lvl, logfile=resolved_log_file, max_bytes=resolved_max_bytes or 0, backup_count=resolved_backup_count or 0) + + if config_path: + conf = load_config(config_path) + else: + # support embedding agents directly in defaults under key 'agents' + conf = defaults.get("agents") or {} + agents = conf.get("agents", []) + + # initial payload + payload = {"id": "task-1", "payload": {"repo": "./"}, "history": []} + + for a in agents: + print(f"Running agent: {a.get('name')}") + out = call_agent(a, payload) + payload["history"].append(out) + # optionally pass the last result as the new payload + payload["payload"][a.get("name")] = out.get("result") + + print("\nRun complete. History:") + print(json.dumps(payload["history"], indent=2)) + + +if __name__ == "__main__": + main() diff --git a/harness/logging_setup.py b/harness/logging_setup.py new file mode 100644 index 0000000..e54c1c2 --- /dev/null +++ b/harness/logging_setup.py @@ -0,0 +1,31 @@ +import logging +import sys +from logging.handlers import RotatingFileHandler + + +def init_logging(level=logging.INFO, logfile: str | None = None, max_bytes: int = 0, backup_count: int = 0): + fmt = "%(asctime)s %(levelname)s [%(name)s] %(message)s" + logger = logging.getLogger("harness") + logger.setLevel(level) + + # clear existing handlers + for h in list(logger.handlers): + logger.removeHandler(h) + + # console handler + ch = logging.StreamHandler(sys.stdout) + ch.setLevel(level) + ch.setFormatter(logging.Formatter(fmt)) + logger.addHandler(ch) + + # optional rotating file handler + if logfile: + if max_bytes and max_bytes > 0: + fh = RotatingFileHandler(logfile, mode="a", maxBytes=max_bytes, backupCount=backup_count, encoding="utf-8") + else: + fh = RotatingFileHandler(logfile, mode="a", maxBytes=0, backupCount=0, encoding="utf-8") + fh.setLevel(level) + fh.setFormatter(logging.Formatter(fmt)) + logger.addHandler(fh) + + return logger