The interface speaks Slovak
969 strings, an instance default and a per-person choice, and no half-done corner: the admin prose is translated too. Design and reasoning: LLeMbas.wiki/Translations. KEYED BY THE ENGLISH SENTENCE A missing entry renders the key, which is the English -- so an untranslated string looks as it always did, an English instance is byte-for-byte 1.6.0, and a half-finished catalogue is a half-translated page rather than a page of dotted key names. The cost is that editing an English sentence orphans its translation silently, which is what tests/test_translations.py asserts in both directions. No gettext: .po -> .mo is a build step and this project does not have one. A JINJA GLOBAL, AND THEREFORE A CONTEXTVAR `t()` is a global for the reason `brand` already documents -- render() is bypassed by 25 TemplateResponse calls and 8 get_template().render() calls, the latter being the SSE frames, which have no Request at all. A global is bound once at import and the language is per person, so the active language is a ContextVar set per request. 🚨 `get_current_user` had to become `async def`. FastAPI runs a sync dependency in a threadpool, and anyio copies the context in and discards it on the way out -- so the language was set where nothing could see it and every page rendered in the instance's language whatever anybody had chosen, with no error anywhere. NOT TRANSLATED, ON PURPOSE Everything a model reads: the 60 prompt fragments, and the dates in harness.py, schedule/runner.py and schedule/compile.py. Only `i18n.stamp` is localised, and only where a person reads it -- with the *format* translatable as well as the words, because "26. septembra 2026" is a different pattern rather than the same one with different words in it. A process locale is not an option: global, not thread-safe, two people's pages at once. A second fragment telling models to answer in the reader's language was written during this work and removed: `core.style` has said it since long before, and test_an_empty_override_turns_a_fragment_off caught the duplicate. THE BULK PASS 1213 sites wrapped by a one-off script that only touched patterns it could not misread. It got three wrong in a way that mattered -- `t('…')` inside `attr="…"` where the sentence held an apostrophe, closing the Jinja string and 500ing two pages whose partials no test renders. tests/test_translations.py now compiles all 110 templates. It also wrapped the product's own name, an SSH key header, a keystroke hint and an example URL, all taken back out: a string is not translatable just because it is a string. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,9 +7,9 @@
|
||||
|
||||
{% block admin_content %}
|
||||
<p class="admin-lede">
|
||||
An <strong>Agent</strong> chat can read files, write files and run commands on
|
||||
An <strong>{{ t("Agent") }}</strong> chat can read files, write files and run commands on
|
||||
a machine reached over SSH. Nothing runs on this server. People add their own
|
||||
connections under <strong>Connections</strong>; what you decide here is
|
||||
connections under <strong>{{ t("Connections") }}</strong>; what you decide here is
|
||||
whether the feature exists and what one reply may spend.
|
||||
</p>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
whatever host somebody points a connection at. A container built for the
|
||||
job is a very different thing from a key to a live server, and {{ brand.name }}
|
||||
cannot tell them apart. What a model reads — a web page, a file, the output
|
||||
of the last command — is untrusted, and in <strong>Auto</strong> mode
|
||||
of the last command — is untrusted, and in <strong>{{ t("Auto") }}</strong> mode
|
||||
nothing stands between that and a command running.
|
||||
</span>
|
||||
</div>
|
||||
@@ -30,17 +30,17 @@
|
||||
{% endif %}
|
||||
|
||||
{% if saved %}
|
||||
<div class="alert alert--success">{{ icon("check", "icon--sm") }} <span>Saved.</span></div>
|
||||
<div class="alert alert--success">{{ icon("check", "icon--sm") }} <span>{{ t("Saved.") }}</span></div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="/admin/agents" class="form-grid">
|
||||
|
||||
<section class="card">
|
||||
<h2 class="card__title">Switch</h2>
|
||||
<h2 class="card__title">{{ t("Switch") }}</h2>
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="enabled" value="true" {{ 'checked' if values.enabled }}>
|
||||
<span>Allow agent chats</span>
|
||||
<span>{{ t("Allow agent chats") }}</span>
|
||||
</label>
|
||||
<p class="field__hint">
|
||||
Off, nobody can start one and no agent tool is offered, whatever
|
||||
@@ -49,13 +49,13 @@
|
||||
</p>
|
||||
</div>
|
||||
<p class="field__hint">
|
||||
People also need the <strong>Run commands</strong> permission, a model
|
||||
flagged <strong>Agent execution</strong>, and a connection of their own.
|
||||
People also need the <strong>{{ t("Run commands") }}</strong> permission, a model
|
||||
flagged <strong>{{ t("Agent execution") }}</strong>, and a connection of their own.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2 class="card__title">Connections to this machine</h2>
|
||||
<h2 class="card__title">{{ t("Connections to this machine") }}</h2>
|
||||
<p class="card__lede">
|
||||
Agent chats reach a machine over SSH, and the point of that is that it is
|
||||
not this one — nothing runs on the host holding the database and the
|
||||
@@ -76,11 +76,11 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field__label" for="loopback_port">The allowed port</label>
|
||||
<label class="field__label" for="loopback_port">{{ t("The allowed port") }}</label>
|
||||
<input class="input" type="number" id="loopback_port" name="loopback_port"
|
||||
min="0" max="65535" step="1" value="{{ values.loopback_port or 0 }}">
|
||||
<p class="field__hint">
|
||||
Only read when the position above is <strong>Only on one port</strong>.
|
||||
Only read when the position above is <strong>{{ t("Only on one port") }}</strong>.
|
||||
Port 22 is refused whatever is typed here — that one is this host's own
|
||||
sshd, not a container that published its port on the loopback interface.
|
||||
</p>
|
||||
@@ -88,11 +88,8 @@
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2 class="card__title">The modes</h2>
|
||||
<p class="field__hint">
|
||||
Set per chat and switchable at any time. This is what each one means; the
|
||||
two lists below adjust them.
|
||||
</p>
|
||||
<h2 class="card__title">{{ t("The modes") }}</h2>
|
||||
<p class="field__hint">{{ t("Set per chat and switchable at any time. This is what each one means; the two lists below adjust them.") }}</p>
|
||||
<dl class="mode-list">
|
||||
{% for value, label, hint in modes %}
|
||||
<div class="mode-list__row">
|
||||
@@ -104,9 +101,9 @@
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2 class="card__title">What never needs asking</h2>
|
||||
<h2 class="card__title">{{ t("What never needs asking") }}</h2>
|
||||
<div class="field">
|
||||
<label class="field__label" for="allow_default">Always allow</label>
|
||||
<label class="field__label" for="allow_default">{{ t("Always allow") }}</label>
|
||||
<textarea class="textarea input--mono" id="allow_default" name="allow_default" rows="5"
|
||||
spellcheck="false">{{ allow_text }}</textarea>
|
||||
<p class="field__hint">
|
||||
@@ -120,18 +117,18 @@
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2 class="card__title">What always needs asking</h2>
|
||||
<h2 class="card__title">{{ t("What always needs asking") }}</h2>
|
||||
<div class="field">
|
||||
<label class="field__label" for="deny_default">Always ask</label>
|
||||
<label class="field__label" for="deny_default">{{ t("Always ask") }}</label>
|
||||
<textarea class="textarea input--mono" id="deny_default" name="deny_default" rows="5"
|
||||
spellcheck="false">{{ deny_text }}</textarea>
|
||||
<p class="field__hint">
|
||||
Checked before everything, including <strong>Auto</strong>. Treat it as
|
||||
Checked before everything, including <strong>{{ t("Auto") }}</strong>. Treat it as
|
||||
a guard against an accident rather than against an adversary:
|
||||
<code>rm -rf /*</code> here does not stop <code>/bin/rm -rf /</code>, and
|
||||
nothing pattern-shaped could. The same limit as above applies, and it
|
||||
cuts the other way here: a command line that runs more than one thing
|
||||
matches none of these, so in <strong>Auto</strong>
|
||||
matches none of these, so in <strong>{{ t("Auto") }}</strong>
|
||||
<code>shutdown -h now</code> asks and <code>shutdown -h now &</code>
|
||||
runs. Anything that must never happen belongs on the far side, in that
|
||||
account’s own permissions.
|
||||
@@ -140,40 +137,34 @@
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2 class="card__title">What one command may spend</h2>
|
||||
<h2 class="card__title">{{ t("What one command may spend") }}</h2>
|
||||
|
||||
<div class="field">
|
||||
<label class="field__label" for="default_timeout">Timeout (seconds)</label>
|
||||
<label class="field__label" for="default_timeout">{{ t("Timeout (seconds)") }}</label>
|
||||
<input class="input" id="default_timeout" name="default_timeout"
|
||||
value="{{ values.default_timeout }}" inputmode="numeric">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field__label" for="max_timeout">Longest a command may ask for</label>
|
||||
<label class="field__label" for="max_timeout">{{ t("Longest a command may ask for") }}</label>
|
||||
<input class="input" id="max_timeout" name="max_timeout"
|
||||
value="{{ values.max_timeout }}" inputmode="numeric">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field__label" for="max_output_bytes">Most output to keep</label>
|
||||
<label class="field__label" for="max_output_bytes">{{ t("Most output to keep") }}</label>
|
||||
<input class="input" id="max_output_bytes" name="max_output_bytes"
|
||||
value="{{ values.max_output_bytes }}" inputmode="numeric">
|
||||
<p class="field__hint">
|
||||
Characters. The rest is cut off and the model is told so.
|
||||
</p>
|
||||
<p class="field__hint">{{ t("Characters. The rest is cut off and the model is told so.") }}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2 class="card__title">Background commands</h2>
|
||||
<p class="card__lede">
|
||||
A command that would outlast its timeout can be left running instead of
|
||||
killed — detached on the far side, checked on later. It is how a long
|
||||
install, build or download becomes possible at all.
|
||||
</p>
|
||||
<h2 class="card__title">{{ t("Background commands") }}</h2>
|
||||
<p class="card__lede">{{ t("A command that would outlast its timeout can be left running instead of killed — detached on the far side, checked on later. It is how a long install, build or download becomes possible at all.") }}</p>
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="background_enabled"
|
||||
{{ 'checked' if values.background_enabled }}>
|
||||
<span>Allow commands to run in the background</span>
|
||||
<span>{{ t("Allow commands to run in the background") }}</span>
|
||||
</label>
|
||||
<p class="field__hint">
|
||||
Off means byte-for-byte the old behaviour: a command that hits its
|
||||
@@ -188,63 +179,44 @@
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="background_on_timeout"
|
||||
{{ 'checked' if values.background_on_timeout }}>
|
||||
<span>Keep a timed-out command running instead of killing it</span>
|
||||
<span>{{ t("Keep a timed-out command running instead of killing it") }}</span>
|
||||
</label>
|
||||
<p class="field__hint">
|
||||
Off leaves the timeout a hard stop; the model can still choose to
|
||||
background a command up front.
|
||||
</p>
|
||||
<p class="field__hint">{{ t("Off leaves the timeout a hard stop; the model can still choose to background a command up front.") }}</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="background_notify"
|
||||
{{ 'checked' if values.background_notify }}>
|
||||
<span>Wake the model when a background job finishes</span>
|
||||
<span>{{ t("Wake the model when a background job finishes") }}</span>
|
||||
</label>
|
||||
<p class="field__hint">
|
||||
On, a finished job starts (or joins) a reply carrying its result. Off,
|
||||
the model only sees it the next time it runs of its own accord.
|
||||
</p>
|
||||
<p class="field__hint">{{ t("On, a finished job starts (or joins) a reply carrying its result. Off, the model only sees it the next time it runs of its own accord.") }}</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field__label" for="background_max_jobs">Most jobs watched at once</label>
|
||||
<label class="field__label" for="background_max_jobs">{{ t("Most jobs watched at once") }}</label>
|
||||
<input class="input" id="background_max_jobs" name="background_max_jobs"
|
||||
value="{{ values.background_max_jobs }}" inputmode="numeric">
|
||||
<p class="field__hint">
|
||||
Each is a periodic reconnect to the machine. Jobs past this still run;
|
||||
they are simply not watched, and the model is not woken for them.
|
||||
</p>
|
||||
<p class="field__hint">{{ t("Each is a periodic reconnect to the machine. Jobs past this still run; they are simply not watched, and the model is not woken for them.") }}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2 class="card__title">What one reply may spend</h2>
|
||||
<p class="field__hint">
|
||||
Four separate bounds, because they fail differently: the clock stops one
|
||||
slow command eating an afternoon, tool output stops a model filling its own
|
||||
context with build logs and having no room to answer, written tokens stop
|
||||
one that keeps going, and the step count is a backstop against a runaway.
|
||||
</p>
|
||||
<h2 class="card__title">{{ t("What one reply may spend") }}</h2>
|
||||
<p class="field__hint">{{ t("Four separate bounds, because they fail differently: the clock stops one slow command eating an afternoon, tool output stops a model filling its own context with build logs and having no room to answer, written tokens stop one that keeps going, and the step count is a backstop against a runaway.") }}</p>
|
||||
|
||||
<div class="field">
|
||||
<label class="field__label" for="max_completion_tokens">
|
||||
Most a reply may write
|
||||
</label>
|
||||
<label class="field__label" for="max_completion_tokens">{{ t("Most a reply may write") }}</label>
|
||||
<input class="input" id="max_completion_tokens" name="max_completion_tokens"
|
||||
value="{{ values.max_completion_tokens }}" inputmode="numeric">
|
||||
<p class="field__hint">
|
||||
In tokens, across every round of one reply. This is the bound that
|
||||
normally ends a long piece of work. Zero means no ceiling.
|
||||
</p>
|
||||
<p class="field__hint">{{ t("In tokens, across every round of one reply. This is the bound that normally ends a long piece of work. Zero means no ceiling.") }}</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field__label" for="max_wall_seconds">Longest a reply may take</label>
|
||||
<label class="field__label" for="max_wall_seconds">{{ t("Longest a reply may take") }}</label>
|
||||
<input class="input" id="max_wall_seconds" name="max_wall_seconds"
|
||||
value="{{ values.max_wall_seconds }}" inputmode="numeric">
|
||||
<p class="field__hint">Time spent waiting for you to answer does not count.</p>
|
||||
<p class="field__hint">{{ t("Time spent waiting for you to answer does not count.") }}</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field__label" for="max_total_output_bytes">Most output across a reply</label>
|
||||
<label class="field__label" for="max_total_output_bytes">{{ t("Most output across a reply") }}</label>
|
||||
<input class="input" id="max_total_output_bytes" name="max_total_output_bytes"
|
||||
value="{{ values.max_total_output_bytes }}" inputmode="numeric">
|
||||
</div>
|
||||
@@ -252,61 +224,44 @@
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="nudge_unfinished"
|
||||
{{ 'checked' if values.nudge_unfinished }}>
|
||||
<span>Ask it to carry on when it stops with tasks outstanding</span>
|
||||
<span>{{ t("Ask it to carry on when it stops with tasks outstanding") }}</span>
|
||||
</label>
|
||||
<p class="field__hint">
|
||||
Only ever against a plan, and only while tasks on it are still open —
|
||||
that is the one thing there is to be objectively wrong about. A reply
|
||||
with no plan that says it has finished is believed. It is asked at most
|
||||
twice in a row, and if it stops a third time that is recorded in the
|
||||
transcript rather than argued with.
|
||||
</p>
|
||||
<p class="field__hint">{{ t("Only ever against a plan, and only while tasks on it are still open — that is the one thing there is to be objectively wrong about. A reply with no plan that says it has finished is believed. It is asked at most twice in a row, and if it stops a third time that is recorded in the transcript rather than argued with.") }}</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="field__label" for="max_steps">Most rounds of tool calls</label>
|
||||
<label class="field__label" for="max_steps">{{ t("Most rounds of tool calls") }}</label>
|
||||
<input class="input" id="max_steps" name="max_steps"
|
||||
value="{{ values.max_steps }}" inputmode="numeric">
|
||||
<p class="field__hint">
|
||||
A backstop, not a working budget. An agent reply is meant to run until
|
||||
the task is done, so a number low enough to be what stops it is a number
|
||||
that stops it halfway. Use the token ceiling above for a real limit.
|
||||
</p>
|
||||
<p class="field__hint">{{ t("A backstop, not a working budget. An agent reply is meant to run until the task is done, so a number low enough to be what stops it is a number that stops it halfway. Use the token ceiling above for a real limit.") }}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2 class="card__title">Asking you things</h2>
|
||||
<h2 class="card__title">{{ t("Asking you things") }}</h2>
|
||||
|
||||
<div class="field">
|
||||
<label class="field__label" for="approval_timeout">How long a question waits</label>
|
||||
<label class="field__label" for="approval_timeout">{{ t("How long a question waits") }}</label>
|
||||
<input class="input" id="approval_timeout" name="approval_timeout"
|
||||
value="{{ values.approval_timeout }}" inputmode="numeric">
|
||||
<p class="field__hint">
|
||||
Seconds. After this the reply carries on without an answer and says so.
|
||||
At least a minute, whatever is typed here.
|
||||
</p>
|
||||
<p class="field__hint">{{ t("Seconds. After this the reply carries on without an answer and says so. At least a minute, whatever is typed here.") }}</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="ask_free_text" value="true"
|
||||
{{ 'checked' if values.ask_free_text }}>
|
||||
<span>Let people write their own answer</span>
|
||||
<span>{{ t("Let people write their own answer") }}</span>
|
||||
</label>
|
||||
<p class="field__hint">
|
||||
When a model asks a question it can offer answers to pick from, and by
|
||||
default a box to write something else. Turn this off if you would rather
|
||||
nobody typed free text into a prompt a model composed.
|
||||
</p>
|
||||
<p class="field__hint">{{ t("When a model asks a question it can offer answers to pick from, and by default a box to write something else. Turn this off if you would rather nobody typed free text into a prompt a model composed.") }}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2 class="card__title">The terminal</h2>
|
||||
<h2 class="card__title">{{ t("The terminal") }}</h2>
|
||||
<p class="field__hint">
|
||||
A panel beside an agent chat holding an interactive shell on that chat's
|
||||
own connection. What somebody types there is <em>theirs</em>: the modes and
|
||||
own connection. What somebody types there is <em>{{ t("theirs") }}</em>: the modes and
|
||||
the two lists above govern the model, not the person at the keyboard, who
|
||||
could open the same shell with an ssh client. The model cannot see the
|
||||
panel; sending it something is a button they press.
|
||||
@@ -316,47 +271,45 @@
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="terminal_enabled" value="true"
|
||||
{{ 'checked' if values.terminal_enabled }}>
|
||||
<span>Allow the terminal panel</span>
|
||||
<span>{{ t("Allow the terminal panel") }}</span>
|
||||
</label>
|
||||
<p class="field__hint">
|
||||
People also need the <strong>Open a terminal</strong> permission.
|
||||
People also need the <strong>{{ t("Open a terminal") }}</strong> permission.
|
||||
{{ terminal_count }} shell{{ '' if terminal_count == 1 else 's' }} open right now.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="field__label" for="terminal_idle_timeout">Close a shell after</label>
|
||||
<label class="field__label" for="terminal_idle_timeout">{{ t("Close a shell after") }}</label>
|
||||
<input class="input" id="terminal_idle_timeout" name="terminal_idle_timeout"
|
||||
value="{{ values.terminal_idle_timeout }}" inputmode="numeric">
|
||||
<p class="field__hint">
|
||||
Seconds with nobody watching <em>and</em> nothing typed. Closing the
|
||||
Seconds with nobody watching <em>{{ t("and") }}</em> nothing typed. Closing the
|
||||
panel does not end the session — a build carries on and is still there
|
||||
on the way back — so this is what eventually ends one.
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field__label" for="terminal_max_sessions">Most shells at once</label>
|
||||
<label class="field__label" for="terminal_max_sessions">{{ t("Most shells at once") }}</label>
|
||||
<input class="input" id="terminal_max_sessions" name="terminal_max_sessions"
|
||||
value="{{ values.terminal_max_sessions }}" inputmode="numeric">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field__label" for="terminal_max_per_user">Most shells per person</label>
|
||||
<label class="field__label" for="terminal_max_per_user">{{ t("Most shells per person") }}</label>
|
||||
<input class="input" id="terminal_max_per_user" name="terminal_max_per_user"
|
||||
value="{{ values.terminal_max_per_user }}" inputmode="numeric">
|
||||
<p class="field__hint">
|
||||
One per chat. Each holds an SSH connection open on the far machine.
|
||||
</p>
|
||||
<p class="field__hint">{{ t("One per chat. Each holds an SSH connection open on the far machine.") }}</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="terminal_integration"
|
||||
{{ 'checked' if values.terminal_integration }}>
|
||||
<span>Mark where commands begin and end</span>
|
||||
<span>{{ t("Mark where commands begin and end") }}</span>
|
||||
</label>
|
||||
<p class="field__hint">
|
||||
Gives bash and zsh the same invisible markers VS Code and WezTerm use,
|
||||
so <strong>Copy</strong>, <strong>Send</strong> and the automatic
|
||||
so <strong>{{ t("Copy") }}</strong>, <strong>{{ t("Send") }}</strong> and the automatic
|
||||
toggle know which output belongs to which command. Written by the shell
|
||||
into a temporary file it deletes itself, and any other shell is started
|
||||
exactly as it was before. Off means those buttons fall back to copying
|
||||
@@ -366,7 +319,7 @@
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2 class="section-title">The project directory</h2>
|
||||
<h2 class="section-title">{{ t("The project directory") }}</h2>
|
||||
<p class="muted">
|
||||
A listing of the directory a chat works in, so a reply does not spend its
|
||||
first rounds finding out what is there — and so files can be attached by
|
||||
@@ -379,20 +332,17 @@
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="index_enabled"
|
||||
{{ 'checked' if values.index_enabled }}>
|
||||
<span>List the project directory</span>
|
||||
<span>{{ t("List the project directory") }}</span>
|
||||
</label>
|
||||
<p class="field__hint">
|
||||
Off means no listing is built at all, and the file picker offers only
|
||||
what is in the library.
|
||||
</p>
|
||||
<p class="field__hint">{{ t("Off means no listing is built at all, and the file picker offers only what is in the library.") }}</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="field__label" for="index_chars">Characters of it in the prompt</label>
|
||||
<label class="field__label" for="index_chars">{{ t("Characters of it in the prompt") }}</label>
|
||||
<input class="input" id="index_chars" name="index_chars"
|
||||
value="{{ values.index_chars }}" inputmode="numeric">
|
||||
<p class="field__hint">
|
||||
This is spent on <em>every</em> request in an agent chat, so it is a
|
||||
This is spent on <em>{{ t("every") }}</em> request in an agent chat, so it is a
|
||||
budget rather than a limit: directories that will not fit are shown as
|
||||
a count and the model is told to look inside them itself.
|
||||
<strong>0</strong> keeps the listing for the file picker and puts none
|
||||
@@ -404,7 +354,7 @@
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="instructions_enabled"
|
||||
{{ 'checked' if values.instructions_enabled }}>
|
||||
<span>Read the project's own instructions</span>
|
||||
<span>{{ t("Read the project's own instructions") }}</span>
|
||||
</label>
|
||||
<p class="field__hint">
|
||||
Looks for <code>AGENTS.md</code> or <code>CLAUDE.md</code> in the root
|
||||
@@ -412,14 +362,14 @@
|
||||
the conventions of the project it is working in. The file is written by
|
||||
whoever works on that project, so it is treated as untrusted: it can say
|
||||
how to work, and cannot grant permission for anything. The exact wording
|
||||
around it is the <em>The project's own instructions</em> fragment on
|
||||
around it is the <em>{{ t("The project's own instructions") }}</em> fragment on
|
||||
<a href="/admin/prompts">Prompts</a>, and clearing that fragment removes
|
||||
the only path by which the file reaches a model.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="field__label" for="instructions_chars">Characters of it to use</label>
|
||||
<label class="field__label" for="instructions_chars">{{ t("Characters of it to use") }}</label>
|
||||
<input class="input" id="instructions_chars" name="instructions_chars"
|
||||
value="{{ values.instructions_chars }}" inputmode="numeric">
|
||||
<p class="field__hint">
|
||||
@@ -430,7 +380,7 @@
|
||||
</section>
|
||||
|
||||
<div class="btn-row">
|
||||
<button class="btn btn--primary" type="submit">Save changes</button>
|
||||
<button class="btn btn--primary" type="submit">{{ t("Save changes") }}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -450,10 +400,10 @@
|
||||
page somebody opens to find out what one turn may set going. #}
|
||||
<form method="post" action="/admin/agents/crowd" class="form-grid">
|
||||
<section class="card">
|
||||
<h2 class="card__title">A crowd</h2>
|
||||
<h2 class="card__title">{{ t("A crowd") }}</h2>
|
||||
<p class="field__hint">
|
||||
A chat can have more than one model in it. The chat's own model answers, then
|
||||
each of the others in turn; then the order runs <strong>backwards</strong>,
|
||||
each of the others in turn; then the order runs <strong>{{ t("backwards") }}</strong>,
|
||||
each one asked whether it disagrees with anything; and it ends back at the
|
||||
first, which either closes or sends them round again.
|
||||
</p>
|
||||
@@ -461,7 +411,7 @@
|
||||
<div class="alert">
|
||||
{{ icon("warning", "icon--sm") }}
|
||||
<span>
|
||||
One turn costs <strong>models × rounds × 2 − 1</strong> replies — four
|
||||
One turn costs <strong>{{ t("models × rounds × 2 − 1") }}</strong> replies — four
|
||||
models over two rounds is fifteen — and on a single local endpoint every
|
||||
change of speaker also loads a different model. Larger crowds of smaller
|
||||
models, and sometimes of bigger ones, start going round in circles: that is
|
||||
@@ -474,78 +424,55 @@
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="enabled" value="true"
|
||||
{{ 'checked' if crowd.enabled }}>
|
||||
<span>Let a chat have a crowd</span>
|
||||
<span>{{ t("Let a chat have a crowd") }}</span>
|
||||
</label>
|
||||
<p class="field__hint">
|
||||
Off by default. With it on, each chat's settings panel offers the other
|
||||
models; a chat with none ticked behaves exactly as it always has.
|
||||
</p>
|
||||
<p class="field__hint">{{ t("Off by default. With it on, each chat's settings panel offers the other models; a chat with none ticked behaves exactly as it always has.") }}</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="field__label" for="crowd_max_models">Most models besides the chat's own</label>
|
||||
<label class="field__label" for="crowd_max_models">{{ t("Most models besides the chat's own") }}</label>
|
||||
<input class="input" id="crowd_max_models" name="max_models"
|
||||
type="number" min="1" max="8" step="1" value="{{ crowd.max_models }}">
|
||||
<p class="field__hint">
|
||||
Four is already eight replies a turn at one round each. More voices past
|
||||
that tend to repeat each other rather than add anything.
|
||||
</p>
|
||||
<p class="field__hint">{{ t("Four is already eight replies a turn at one round each. More voices past that tend to repeat each other rather than add anything.") }}</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="field__label" for="crowd_max_rounds">Most rounds</label>
|
||||
<label class="field__label" for="crowd_max_rounds">{{ t("Most rounds") }}</label>
|
||||
<input class="input" id="crowd_max_rounds" name="max_rounds"
|
||||
type="number" min="1" max="5" step="1" value="{{ crowd.max_rounds }}">
|
||||
<p class="field__hint">
|
||||
A round is out and back. Two gives the first model one chance to change its
|
||||
mind after hearing the objections, which is the point of the whole thing;
|
||||
three is where going in circles starts.
|
||||
</p>
|
||||
<p class="field__hint">{{ t("A round is out and back. Two gives the first model one chance to change its mind after hearing the objections, which is the point of the whole thing; three is where going in circles starts.") }}</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="field__label" for="crowd_wall_seconds">Longest a turn may take</label>
|
||||
<label class="field__label" for="crowd_wall_seconds">{{ t("Longest a turn may take") }}</label>
|
||||
<input class="input" id="crowd_wall_seconds" name="wall_seconds"
|
||||
type="number" min="60" max="7200" step="30" value="{{ crowd.wall_seconds }}">
|
||||
<p class="field__hint">
|
||||
Across every speaker, not each. A member whose endpoint has stalled cannot
|
||||
then hold the round open all afternoon.
|
||||
</p>
|
||||
<p class="field__hint">{{ t("Across every speaker, not each. A member whose endpoint has stalled cannot then hold the round open all afternoon.") }}</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="collapse_agreement" value="true"
|
||||
{{ 'checked' if crowd.collapse_agreement }}>
|
||||
<span>Fold away a short "I agree" on the way back</span>
|
||||
<span>{{ t('Fold away a short "I agree" on the way back') }}</span>
|
||||
</label>
|
||||
<p class="field__hint">
|
||||
The disagreements are what a crowd is for; a column of bubbles saying
|
||||
nothing is what makes somebody switch it off. The text is still there
|
||||
behind a disclosure.
|
||||
</p>
|
||||
<p class="field__hint">{{ t("The disagreements are what a crowd is for; a column of bubbles saying nothing is what makes somebody switch it off. The text is still there behind a disclosure.") }}</p>
|
||||
</div>
|
||||
|
||||
<div class="btn-row">
|
||||
<button class="btn btn--primary" type="submit">Save</button>
|
||||
<button class="btn btn--primary" type="submit">{{ t("Save") }}</button>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
|
||||
<form method="post" action="/admin/agents/subagents" class="form-grid">
|
||||
<section class="card">
|
||||
<h2 class="card__title">Helpers</h2>
|
||||
<h2 class="card__title">{{ t("Helpers") }}</h2>
|
||||
<p class="field__hint">{{ t("A reply can hand a self-contained piece of work to a second model that runs on its own and reports back — several at once, which is what makes research fan out instead of queueing. This applies to ordinary chats as much as agent ones.") }}</p>
|
||||
<p class="field__hint">
|
||||
A reply can hand a self-contained piece of work to a second model that
|
||||
runs on its own and reports back — several at once, which is what makes
|
||||
research fan out instead of queueing. This applies to ordinary chats as
|
||||
much as agent ones.
|
||||
</p>
|
||||
<p class="field__hint">
|
||||
<strong>Asking another model a question uses the same switch and the same
|
||||
allowance below</strong>, because it costs the same thing: one reply
|
||||
<strong>{{ t("Asking another model a question uses the same switch and the same allowance below") }}</strong>, because it costs the same thing: one reply
|
||||
setting another reply going. Which people may do it is a separate
|
||||
permission — <strong>Ask another model</strong> — and which models may is a
|
||||
permission — <strong>{{ t("Ask another model") }}</strong> — and which models may is a
|
||||
switch on each model's own page.
|
||||
</p>
|
||||
|
||||
@@ -554,9 +481,9 @@
|
||||
<span>
|
||||
A helper cannot ask anybody anything, so nothing in its chat can stop
|
||||
for approval. It therefore gets only what this chat could already do
|
||||
<em>without</em> asking: it reads, it searches, and on a machine it runs
|
||||
<em>{{ t("without") }}</em> asking: it reads, it searches, and on a machine it runs
|
||||
a short fixed list of read-only commands and nothing else, in every mode
|
||||
including <strong>Auto</strong>. It cannot send helpers of its own.
|
||||
including <strong>{{ t("Auto") }}</strong>. It cannot send helpers of its own.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -564,11 +491,11 @@
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="enabled" value="true"
|
||||
{{ 'checked' if subagents.enabled }}>
|
||||
<span>Let a model delegate</span>
|
||||
<span>{{ t("Let a model delegate") }}</span>
|
||||
</label>
|
||||
<p class="field__hint">
|
||||
People also need the <strong>Delegate to a helper</strong> permission,
|
||||
and the model needs the <strong>Tools</strong> capability. Off by
|
||||
People also need the <strong>{{ t("Delegate to a helper") }}</strong> permission,
|
||||
and the model needs the <strong>{{ t("Tools") }}</strong> capability. Off by
|
||||
default: a reply that spawns helpers spends model time multiplicatively,
|
||||
and on one local endpoint four at once is four times the queue rather
|
||||
than four times the speed.
|
||||
@@ -576,83 +503,60 @@
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="field__label" for="sub_max_per_reply">
|
||||
Most helpers one reply may send
|
||||
</label>
|
||||
<label class="field__label" for="sub_max_per_reply">{{ t("Most helpers one reply may send") }}</label>
|
||||
<input class="input" id="sub_max_per_reply" name="max_per_reply"
|
||||
type="number" min="1" max="20" step="1"
|
||||
value="{{ subagents.max_per_reply }}">
|
||||
<p class="field__hint">
|
||||
Fanning out across a handful of independent questions is what this is
|
||||
for. A reply that wants twenty has misread the tool. Questions put to
|
||||
other models count against this same number, so one reply cannot spend
|
||||
the allowance twice.
|
||||
</p>
|
||||
<p class="field__hint">{{ t("Fanning out across a handful of independent questions is what this is for. A reply that wants twenty has misread the tool. Questions put to other models count against this same number, so one reply cannot spend the allowance twice.") }}</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="field__label" for="sub_max_concurrent">Running at once, instance-wide</label>
|
||||
<label class="field__label" for="sub_max_concurrent">{{ t("Running at once, instance-wide") }}</label>
|
||||
<input class="input" id="sub_max_concurrent" name="max_concurrent"
|
||||
type="number" min="1" max="50" step="1"
|
||||
value="{{ subagents.max_concurrent }}">
|
||||
<p class="field__hint">
|
||||
Each is a whole generation against the same endpoint the reply that
|
||||
asked for it is waiting on. Past this a model is told to do the work
|
||||
itself rather than made to wait.
|
||||
</p>
|
||||
<p class="field__hint">{{ t("Each is a whole generation against the same endpoint the reply that asked for it is waiting on. Past this a model is told to do the work itself rather than made to wait.") }}</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="field__label" for="sub_max_completion_tokens">Most a helper may write</label>
|
||||
<label class="field__label" for="sub_max_completion_tokens">{{ t("Most a helper may write") }}</label>
|
||||
<input class="input" id="sub_max_completion_tokens" name="max_completion_tokens"
|
||||
type="number" min="0" max="5000000" step="1000"
|
||||
value="{{ subagents.max_completion_tokens }}">
|
||||
<p class="field__hint">
|
||||
In tokens, across every round. A helper answers one question, so this
|
||||
should run out well before the reply that asked does. Zero means no
|
||||
ceiling.
|
||||
</p>
|
||||
<p class="field__hint">{{ t("In tokens, across every round. A helper answers one question, so this should run out well before the reply that asked does. Zero means no ceiling.") }}</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="field__label" for="sub_wall_seconds">Longest a helper may take</label>
|
||||
<label class="field__label" for="sub_wall_seconds">{{ t("Longest a helper may take") }}</label>
|
||||
<input class="input" id="sub_wall_seconds" name="wall_seconds"
|
||||
type="number" min="30" max="7200" step="30"
|
||||
value="{{ subagents.wall_seconds }}">
|
||||
<p class="field__hint">
|
||||
Seconds. Past it the helper is <em>stopped</em>, not abandoned: what it
|
||||
Seconds. Past it the helper is <em>{{ t("stopped") }}</em>, not abandoned: what it
|
||||
had written is kept and handed back with a note saying it is partial.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="field__label" for="sub_max_rounds">Most rounds of tool calls</label>
|
||||
<label class="field__label" for="sub_max_rounds">{{ t("Most rounds of tool calls") }}</label>
|
||||
<input class="input" id="sub_max_rounds" name="max_rounds"
|
||||
type="number" min="1" max="200" step="1"
|
||||
value="{{ subagents.max_rounds }}">
|
||||
<p class="field__hint">
|
||||
A backstop, as it is above. The clock and the token ceiling are what
|
||||
normally end one.
|
||||
</p>
|
||||
<p class="field__hint">{{ t("A backstop, as it is above. The clock and the token ceiling are what normally end one.") }}</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="keep_transcript" value="true"
|
||||
{{ 'checked' if subagents.keep_transcript }}>
|
||||
<span>Keep a helper's own chat afterwards</span>
|
||||
<span>{{ t("Keep a helper's own chat afterwards") }}</span>
|
||||
</label>
|
||||
<p class="field__hint">
|
||||
Off means it is deleted once its answer has been handed over, which is
|
||||
what keeps this cheap to use. Turn it on to work out why one came back
|
||||
with something odd. Kept chats are temporary either way and are swept a
|
||||
day later, and neither appears in anybody's sidebar.
|
||||
</p>
|
||||
<p class="field__hint">{{ t("Off means it is deleted once its answer has been handed over, which is what keeps this cheap to use. Turn it on to work out why one came back with something odd. Kept chats are temporary either way and are swept a day later, and neither appears in anybody's sidebar.") }}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="btn-row">
|
||||
<button class="btn btn--primary" type="submit">Save changes</button>
|
||||
<button class="btn btn--primary" type="submit">{{ t("Save changes") }}</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user