Running your first task end to end
Before the first run#
A first task takes three inputs: a program running on your computer, a folder, and one sentence. In a terminal the program is horon-agent, and horon-agent run "..." sends one message and exits, so start it from the folder the task concerns. From code, HoronAgentClient.launch({ workingDir }) starts a private instance and client.run(session.session_id, "...") sends the sentence; the workingDir you pass is the working directory of the session.
The program also needs an account to call a model with. A private instance inherits your provider logins by default, because an instance with no credentials cannot reach a model; pass inheritLogins: false to start empty and set a key with setApiKey(provider, key). Model calls leave the machine as HTTPS requests to the service you are signed in to; there is no local inference, so the model does not run on your laptop.
Two parts of the product do not exist yet. The desktop app is a test bench with no installer released, so there is nothing to download and double-click. There is no plugin store either; a plugin is installed from a bundle you already have, with horon-agent plugins install <path>. Today you run it from a terminal or from code.
It reads your folder in place#
Nothing is uploaded and nothing is copied into a workspace first. A session gets fourteen tools by default: a shell, read, write, edit, replace, apply_patch, a text search, a directory listing, a web fetch, a web search, skill management, an MCP bridge, undo, and ask. A relative path handed to a file tool resolves against the working directory of the session, and an absolute path passes through as written; either way it is the same file you see in Finder or Explorer. There is no sandbox around that folder. The shell tool reaches any path your account can, and a symlink can point outside.
Keeping files in place is not the same as keeping them on this machine. What leaves is the text the model is shown: the contents of files it read, the output of commands it ran. A file the assistant opens is a file whose contents travel over HTTPS to the model endpoint. If a folder holds keys or client material you would not paste into a chat window, point the task at a copy instead.
The network side is closed off. The program listens on two local sockets, which are files only your user account can open, and it opens no inbound network port. One optional gateway lets another device reach the assistant; it ships off, and turning it on binds 0.0.0.0:7643. Telemetry is off until you run horon-agent telemetry enable.
It says what it is doing, call by call#
A turn usually opens with text rather than a write, so you read what the assistant understood before the first file changes. That order is a convention the runtime supports, not a gate that holds a write back. A client receives the words as they stream, ahead of the tool call that follows them, and every tool definition carries a required intent field, a short label saying why the call is being made, which a client shows beside the call. Nothing in the runtime stops a model from calling a tool as its first act.
For a plan you can watch as a list rather than re-read as a paragraph, enable the todo tool. Its instructions tell the model to write the full plan before starting, mark an item in progress before working on it, and mark it done the moment it is done, not in a batch at the end. todo is not among the default fourteen, so this is a per-session choice: createSession({ workingDir, tools: { enabled: [...] } }).
Read the plan as a proposal. It is written before the model has read your files, so it describes what it expects to find as much as what it will do. That is the value: a wrong plan is cheap to stop and expensive to find in a diff.
The stop for your answer, and when it does not happen#
The approval step exists, and it is narrower than it first appears. When the assistant asks, the question reaches your code as a permission_request event with a request_id, and the turn waits. You answer with respondToPermission(sessionId, requestId, "allow") or "deny". The request carries a description and a rationale, and the action alone rarely says enough.
How long it waits is a setting. approval_timeout_secs defaults to 1800 seconds, and the outcome on timeout defaults to deny. No answer does not become a yes. That is the right failure direction, and the number matters before you walk away from a long run.
What you should not assume is that every write stops here. The tool that asks, request_permission, is not one of the default fourteen. It is registered for ambient runs, and for a run with no client attached it joins the tool list only when [ambient] approval_for_clientless_runs is on. In an ordinary first task the thing that stops a bad command is the shell gate: catastrophic targets such as / or $HOME are denied outright, and a borderline command is held until the model writes a justification. That gate protects your data, and it never asks you.
Two settings remove the stop where it does exist. run(..., { autoApprove: true }) answers allow to every prompt, which is fine for a demo folder and wrong for one you care about. In a headless run the ask tool does not block either: under the default ask_policy = "recommended" it takes the recommended option and continues. Check both before leaving a run alone.
Where the result lands#
The result is written into your files, at the paths they already had. There is no export step, no results folder, no second copy to reconcile later. Shell temp files go to a scratch directory the runtime sets, not /tmp, and that is the only work that happens outside your folder.
The safety net is an edit journal. Every successful write records the bytes before and after as one unit per tool call, and undo reverses the newest unit. The journal lives in the running process for that one session. Restart the program and the history is empty. Its budget is 64 MiB, and when that fills, the oldest units are dropped first.
undo refuses rather than guesses. If the file changed since the write it is being asked to reverse, it declines, unless you pass force: true, and then it lists the paths it overwrote. That is right for a safety net, and it is why the journal is not a backup: it is one process deep and one session wide.
What to check before you trust it#
Read the diff first. In a git repository, git diff and git status show every line the turn changed, and that is the only account the model did not write. The assistant's summary is a claim about its own behavior. The diff is the behavior.
Run the check your project already has, not one the assistant invented. Tests, type check, or build, whichever the folder uses, is the answer to whether the task worked. A run that ended cleanly is not a finished task; a task is finished when your own command passes.
Compare the plan against the diff, and check the tool list you granted. A session's tool policy is kept in memory, not in the transcript, so a reconnect or a restart falls back to the default policy; reconfigure before the next message if the grant mattered. Keep your own copy of anything you cannot re-derive, because the journal does not survive the process.
The last thing to check is what you assumed was finished. The desktop app is not released, there is no plugin store, and a plugin release channel is still missing. A first task works from the terminal or from code today. That does not block a first run, but it changes what you should expect to find when you look for a button.