Make it yours
An instance has a handful of knobs, and this page is all of them. What you add to the personas, the one JSON file that describes the instance, the models, the permission rules, the switches on the Leader's head, and the port. Everything here is yours: an update replaces the payload and leaves every one of these alone.
The personas
A persona is rendered once, when a seat's process starts, from the templates the instance has
at that moment, and kept beside that seat's conversation under
desks/<Name>/. Every part of it says where it came from. It opens with
ovai's own mechanics for every seat, in <ovai
source="lib/templates/common.md">, then the role's template in <ovai
source="lib/templates/leader.md"> (or worker.md), then what you add.
What you add to it is yours: three files in the instance directory, each reaching the session in a
frame that names the file it came from. customization/common.md goes to every
session here and comes first, so the role's own file reads as the narrower thing under it;
customization/leader.md and customization/worker.md go to that kind
of session. A Leader is given the Worker's file after both, marked as the Worker's rather than
as its own, so that it knows what every Worker already holds and briefs the task rather than
the method. A Worker is never given the Leader's. A subagent a session starts is handed
customization/common.md alone, by a hook, in the same frame and word for word: it
is neither the Leader nor a Worker, so the role files stay with the seats.
<customization source="customization/worker.md"> Commit messages are one line, imperative, under 72 characters, and say what changed rather than what you did. A design goes in your desk directory as design.md before any code is written, and you say so to the Leader before you start on it. </customization>
The content goes between the tags exactly as it was read — nothing trimmed, nothing re-wrapped, no newline added and none taken away — because these are your words going into every session, and a frame that tidied them would be worse than no frame. A file you have not written is no frame at all; a file that is there and empty is a frame with nothing in it, which is what it says. And because the persona is rendered when the process starts, an edit changes nothing for a process already running and reaches every process started after it — the next Worker hired or restarted, the Leader after its next restart, and every subagent started after it.
This is where what your team is held to lives: the language the team writes in, the shape of
a commit message, where a design goes before it is built. The test for whether a line belongs
here is what kind of line it is — one that can be obeyed or broken is customization, and
one that is true or false is a note. The directory is yours, and a
session asks before it writes there: Edit(/customization/**) is in the
ask list, so a session told to add a rule for the team stops for
your press.
openovai.json
The instance's description of itself, in the instance directory. The installer writes it — who works
here, on which models, on which port, how it signs in; no absolute path, so an instance can
be moved and still be itself — and an install over the instance with --force
writes any option you give into it. Everything below the first group is optional: absent, the
default holds. The server reads the file when it starts, so ovai restart after
an edit.
| key | default | what |
|---|---|---|
| written by the installer | ||
user | — | the person the team works for |
leader | — | the name of the session that leads the team |
models.leader, models.worker | — | the models those sessions run on; a Worker whose desk was given a model at hire is the exception |
port | — | the port the page listens on, 127.0.0.1 only; 0 is whatever is free |
auth | — | login or inherit |
schema, createdAt | — | the file's version and when the instance was installed; ovai configuration reports the second |
| read when present | ||
quota | { "5h": [90, 95], "7d": [97, 99] } | the two stages of the quota gate, in percent of each window; the 7d pair holds every weekly window, the one over all models and fable's own alike |
idle | { "fyi": [10, 50], "force": 55, "all": 1 } | minutes: when the Leader is told about a silent Worker, twice, and when a silent Worker is closed and a silent Leader told to write its desk and stop; and how long every running Worker has to sit idle before the Leader is told nobody is doing anything |
context | { "warning": 200000, "step": 20000, "error": 300000 } | tokens: the request size at which the Leader is first told how big it has grown, how often it is told again above that, and the size at which the telling turns into an instruction to hand over; for a Worker, the two sizes at which the Leader is told how big that Worker has grown, the second being when to restart it |
park | { "deadline": 1800, "timeout": 20, "margin": 15 } | seconds: how long the park tool waits for the Workers when its caller gives no deadline, how long a server stop waits for the whole room, and how much longer ovai stop waits past that for the sessions to go |
typing | { "quiet": 10, "cap": 30 } | seconds: a seat waits for you while you type in its box. quiet is how long after your last keystroke you still count as typing, cap the longest a waiting turn is held; 0 in either turns the hold off, and a value that is not a number or is below 0 is taken as the default. What is held, with a worked timeline |
desk | { "limit": 131072 } | bytes: when write_desk finds a desk file past the limit, the server saves it whole beside itself as STATE-<time>.md and tells the seat, once per crossing, to archive what current work no longer needs, or, with most of it still open, to ask you for a higher limit |
That is the whole list. A key not on it is not read; a value of the wrong shape — a string where a number goes, a pair with three numbers — falls back to the default for that key and nothing else. There is no quiet-hours setting, no log level, no cache. What is fixed in the code instead, and why, is on the defaults page.
Models
The instance was installed with a model for the Leader and one for the Workers, and
ovai configuration lists every desk with what it resolves to. A Worker hired
with a model beside its name is one that should not run on what the workspace runs its
Workers on; that word is kept in desks/<Name>/MODEL, its own file rather
than a line of the desk — the desk file is written by its session, and a model kept in
there would be a model that session could raise for itself. It goes away with the desk.
A model given at hire on a desk a Worker had before runs it on that model and is
written down for the desk from then on. Leaving the model out at hire writes
nothing down, so changing models.worker moves everybody who was never named one,
from their next process.
Each of these takes model or model/effort: opus/high
runs the seat on opus at high effort, and with no effort written Claude Code picks
its own for the model. A new instance offers opus for the Leader and
sonnet for the Workers; the defaults page has the
whole check.
The rules are yours
.claude/settings.json in the instance directory is seeded once and then belongs to
you: every permission popup you answer with the third button and every rule the Leader settles with
permission — a rule naming one file, like
Edit(/knowledge/common.md), included — is written there, and
.claude/allowed.md beside it keeps one line per rule — who asked, when, what
for. An update touches it for two things and no other: it wires the subagent hook into a file
that lacks it, taking out the shell hook versions before 0.26.0 ran, and turns Claude Code's
commit attribution off in a file that says nothing about it. It adds no rule; it says which
of the rules a fresh instance is born with the file has not got, and leaves that to you. A
file that says something else about attribution is yours, and an update leaves it. The
permissions page has the seeded set and the shape of a rule.
Notifications, messages and theme
Two icon buttons on the Leader's head, beside the theme toggle, are the whole of the
notification configuration: an exclamation mark for the action-needed notification
(<name> requires your action — a permission popup appeared on some panel) and a
bell for the end-of-turn notification (<name> finished their turn —
the Leader's turn ended, however it ended). Each is lit while on and slashed across while off,
both off to begin with, kept by the browser for that page. The first switch you turn on asks
the browser for its permission, the way any site does, and lights only once the browser has
granted it; a prompt refused or dismissed leaves the switch off. Nothing is sent while the
page is visible — a tab you are looking at tells you itself. A click on a notification
brings the page to the front and the panel it came from into view; a burst on one panel
replaces its own, never stacks. Sound, and whether anything shows while you are in another
app, are the browser's and the system's, not the page's.
Paul requires your action, and the tab's icon with the amber ring beside a plain one
A third switch, after those two, hides the messages between the Leader and the Workers, and
one Worker's to another, from the Leader's panel; what you typed to a Worker stays. They are
shown to begin with, and the choice is kept by the browser as openovai-comms. A
hidden row a click reaches — its Sent or Received line on a Worker's panel, a
reference — is shown opened, and hides again when it is folded.
The two bolt switches are Claude Code's auto mode, one for the Leader and one for the Workers. Both are on whenever the server starts, as the CLI starts in auto: a call no rule settles is the classifier's to settle. Turn one off and that role is in Manual, where every such call asks you on a permission popup, until the server stops; the switches are the server's, shared by every page, and kept nowhere. Permissions has what each mode stops for.
At the right edge of the same head, a toggle between light and dark. The page starts light; a choice is kept by the browser for that page, the same way the switches are.
A fixed port, and the page as an app
--port 0 at install means whatever is free, and ovai start prints
the address it actually got. Pick a number when you want the same one every time: a bookmark,
or the page installed as a web app. The page ships a manifest and three icons — one of
them for a platform that masks icons to its own shape — so the browser's "install" puts
it on your desktop like any app, which is what a fixed port is for. The app is named as the
page's title names it, such as OpenOv AI ~/workspace, so the apps of two
instances are told apart in a launcher; an app installed before takes the name when the
browser next reads the manifest. The port is on 127.0.0.1 only, whatever the
number.
FAQ: the eleven questions a first evening raises, and what each one means.