Repository navigation
emrg: the owner tool lets the caller choose the question, and is asked again before its judgement can have changed #2495
Description
The owner tool lets the caller choose the question, and is asked again before its judgement can have changed
What is observed, and where
emrg/tools/boss_tool.py registers one tool, boss, with three parameters: intent, timeout_s, and focus. focus is documented as "the one thing you want the owner's judgement on", and emrg/server/prompts/boss.j2 renders it under the heading "The executor especially wants your judgement on this".
Two consequences follow, and they are separate defects.
1. The caller narrows what the owner judges. The owner role exists because the round that must finish something now is the worst judge of whether it should be doing it at all. A parameter that lets that same round choose the question hands part of that judgement back to it: an executor can ask the owner to rule on the thing the executor has already decided to do, and an answer to a narrowed question is not a direction.
2. Nothing notices that the direction was just decided. Every boss call opens the retained owner session and runs a full turn to completion (DEFAULT_TIMEOUT_S = 600). Measured in one scheduled task's own session history: five owner sessions began within 15.5 minutes, the shortest gap between two of them 30 seconds. A task's direction is not a quantity that changes on that timescale. Each of those calls paid for a complete agent run to re-derive an answer that was already given, and the executor had the previous answer in context.
What is expected instead
The owner decides what it judges. The tool keeps intent and timeout_s; focus is removed. The prompt already tells the owner to read the parent's history.jsonl and derive the mission itself, so it has everything it needs to choose.
A direction that was just given stands until the window closes. When an owner session for the same parent was asked within the last four hours, a boss call starts no agent and returns immediately, saying it is busy: it names when the previous request was made, when the window frees, and how long that is.
| Situation | Behaviour |
|---|---|
Called with focus in the arguments |
Not accepted — the parameter no longer exists |
| Within four hours of the previous request | No agent is started, no request is made; a busy result is returned |
| The busy result | States that it is busy, when the previous request was made, when the window frees, how long remains, and that no direction was given this round |
| Outside the window | Normal: a full owner session runs |
| Never called for this parent | Normal: a full owner session runs |
| Daemon restarted | The window still applies |
error field |
The busy result is a normal result, not an error |
What the fix must satisfy
A busy result must not be readable as a direction. Today's tool description tells the executor that what comes back "is a stance to treat as binding for this round, not a list of problems to consider". A busy answer delivered on that contract would be read as a stance — the round would take "nothing changes" from a call that said nothing at all. So the result carries a field a program can test (not a sentence a reader must interpret), and the executor's own instructions must gain the matching rule: on a busy answer, keep the direction you already have and carry on with this round. That sentence belongs in the same change as the removed parameter, because it is the other half of the same contract.
The window's clock is persisted, not remembered. The reference point is the instant of the previous request for that parent, and it must survive a daemon restart and a rebuild of the owner session. The precedent is already in this repository: a session's delegation depth lives in its meta.json "rather than in a daemon table because … a daemon restart must not reset the count of a session that already exists on disk" (emrg/session.py). The window needs the same property for the same reason; an in-memory timer, or a value recomputed from a file that _get_or_create_session may rewrite, does not have it.
focus is deleted, not ignored. The requirement is that passing it is not accepted. That means the parameter leaves the tool schema, its validation branch, the {% if focus %} block in boss.j2, the focus= keyword on _run_boss_tool and _run_boss_child, and the timeout error's advice to "narrow focus". Any caller left behind is a call that silently stops working, so the removal and its callers are one change.
Acceptance
- A repository test asserts that
focusis no longer an accepted parameter — passing it is refused rather than honoured. - A repository test asserts that a call inside the window starts zero owner sessions and that its result names the remaining time and states that no direction was given.
- A test asserts that a call outside the window runs a full owner session, and that a parent that has never called runs one.
- A test asserts the window survives a daemon restart: the decisive reading is taken after rebuilding the daemon object against the same on-disk state, not from a value the first object still holds.
- A test asserts the busy result is not an error, and carries the field the executor's instructions key on.
On "three platforms": the Test workflow runs exactly two pytest legs, ubuntu-latest and windows-2025 (build-release.yml does not run the suite). A macOS leg does not exist, and adding one costs a runner slot on every PR for every change. This issue therefore claims the two legs that run, and states that the macOS case is not claimed — the window logic is platform-free, and what remains unmeasured is only the runner's own behaviour. Adding a macOS leg is a separate decision, not a quiet line in this issue's acceptance.
Activity
argszero commentedon Oct 22, 2026
Handled by #2496