How do I \write18 a koma var?

As Ulrike said, \usekomavar is not expandable, but a functionally-equivalent expandable version can be easily created. Being non-expandable means that the command does assignments as part of its processing, so it fails when used inside an \edef or a \write. The command below uses only expandable commands, so that inside the \write it expands to the value of the variable.

\documentclass{scrlttr2}

\newcommand\x[1]{hello #1}

\usepackage{xparse}
\ExplSyntaxOn
\NewExpandableDocumentCommand\xusekomavar{sO{\use:n}m}
  {
    \IfBooleanTF{#1}%
      { \__bitmask_usekomathing:nnn {name} }
      { \__bitmask_usekomathing:nnn {var} }
          {#2} {#3}
  }
\cs_new:Npn \__bitmask_usekomathing:nnn #1 #2 #3
  {
    \cs_if_exist:cTF { [email protected]#[email protected] }
      { \exp_args:Nnc \use:n {#2} { [email protected]#[email protected]#1 } }
      { \msg_expandable_error:nnn { bitmask } { undefined-komavar } {#3} }
  }
\msg_new:nnn { bitmask } { undefined-komavar }
  { KOMA-Script~variable~`#1'~not~defined. }
\ExplSyntaxOff

\begin{document}

\begin{letter}{Person} % <- toname should be 'Person'
\opening{Opening}

\immediate\write18{echo 'none' > \jobname-none.txt} % fine
\immediate\write18{echo '\x{world}' > \jobname-x.txt} % fine
\immediate\write18{echo '\xusekomavar{toname}' > \jobname-toname.txt} % error

\closing{Closing}
\end{letter}

\end{document}

It is also possible to use the optional argument of \usekomavar. Therefore I have to define a new macro which will take the content for the file as its last argument.

\newcommand\writetofile[2]{% \writetofile{<file>}{<content>}
  \immediate\write18{echo '#2' > #1}%
}

Then it is possible to use

\usekomavar[\writetofile{\jobname-toname.txt}]{toname}

to get the desired result.

Example:

\documentclass{scrlttr2}
\newcommand\x[1]{hello #1} 

\newcommand\writetofile[2]{% \writetofile{<file>}{<content>}
  \immediate\write18{echo '#2' > #1}%
}

\begin{document}
\begin{letter}{Person} % <- toname should be 'Person'
\opening{Opening}

\immediate\write18{echo 'none' > \jobname-none.txt}% fine
\immediate\write18{echo '\x{world}' > \jobname-x.txt}% fine
\usekomavar[\writetofile{\jobname-toname.txt}]{toname}%

\closing{Closing}
\end{letter}
\end{document}

or

\documentclass{scrlttr2}
\newcommand\x[1]{hello #1} 

\newcommand\writetofile[2]{% writetofile{<file>}{<content>}
  \immediate\write18{echo '#2' > #1}%
}

\newcommand\writekomavar[2]{% \writekomavar{<file>}{<variable>}
  \usekomavar[\writetofile{#1}]{#2}%
}

\begin{document}
\begin{letter}{Person} % <- toname should be 'Person'
\opening{Opening}

\immediate\write18{echo 'none' > \jobname-none.txt}% fine
\immediate\write18{echo '\x{world}' > \jobname-x.txt}% fine
\writekomavar{\jobname-toname.txt}{toname}%

\closing{Closing}
\end{letter}
\end{document}

KoMa variables are basically macros saved under the name \[email protected]@var. \usekomavar is a robust macro that checks whether the variable is defined and whether a star follows, in which case it does something else. If you are ready to give up these checks you can just use \csname [email protected]@var\endcsname. Worst case: the KoMa var is undefined and nothing happens.

\immediate\write18{echo '\csname [email protected]@var\endcsname' > \jobname-toname.txt}