The `agent_browser` skill is always loaded, so its safety paragraph shipped to
`off`-mode agents. Its prohibitions do not hold there — Strix only assigns a
browser session in a safety mode, while multi-session browsing is a normal
documented workflow — so the paragraph misdescribed the tools those agents
have. Move it into the already mode-gated block in the system prompt, and pin
the gating in both directions.
Test changes:
- `test_observe_mode_blocks_browser_click` asserted nothing about observe mode.
The same call blocks in guarded for a different reason (no prior snapshot),
so the observe rule was never reached. Give it a snapshot and assert the
block's source and category, plus the passive-read inverse.
- Neither workspace-epoch bump was pinned; removing either left the suite
green. Both are now covered, along with the read-only case that must not
bump, and an end-to-end pairing where a patch during review invalidates a
script decision.
- Cover `invoke_mutating_tool`'s observe-block and off-mode paths, the
reviewer's low-confidence, block, missing-model and failed-inspection rules,
the inline `bash -c` source path, and the two dependency-budget guards.
- Assert browser sessions are disjoint across agents rather than freezing one
agent's command string.
- Fold the compound-separator and safety-config tests into the parametrized
cases that already covered them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Introduce a pre-execution safety layer that reviews effectful agent actions
against compiled, frozen evidence before they run.
`--safety-mode guarded` allows non-destructive interaction after review;
`--safety-mode observe` permits passive target interaction only. `off` stays
the default, so existing runs are unchanged.
Deterministic rules decide what they can on their own: destructive commands,
code-loading environment overrides, blocked browser actions, and mutating
requests in observe mode are refused without a model call, and a small set of
read-only commands is allowed outright. Everything else compiles an evidence
packet — command, scope, script source and its local import closure, prior
tool-call evidence, and browser snapshot context — for a bounded reviewer that
may make one isolated inspection call. Incomplete evidence fails closed.
In safety modes, user-owned local directories are copied into the run
directory so the originals are never mounted writable, while `.git`,
`.agents`, and `.codex` inside the copy stay read-only.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(agents): collapse repeated waits queued inside one model turn
* fix(agents): state that one wait is enough in every prompt variant
---------
Co-authored-by: Ahmed Allam <ahmed39652003@gmail.com>
* fix(llm): cap the tool calls one assistant response may queue
* fix(llm): cap the subscription backend's responses too
---------
Co-authored-by: Ahmed Allam <ahmed39652003@gmail.com>
An exception escaping a non-interactive cycle re-raised before the status
handling, so a dying child stayed 'running' and its parent waited out the
timeout on a completion report the child could no longer send. Set the
terminal status and wake the parent on the way out too.
A tool call for a name Strix does not register raised ModelBehaviorError
from the SDK turn resolver, which nothing retries: the root agent's raise
tore down the whole scan and a sub-agent died before its status was set.
Opt into the SDK's tool_not_found_behavior="return_error_to_model" so the
unknown call comes back as a tool result and the agent self-corrects.
The setting landed in openai-agents 0.19.0, which requires openai>=2.45,
so both pins move.
* perf(cli): fast startup — lazy heavy imports + onedir standalone build
* perf(cli): drop legacy single-file compat from install/self-update
* perf(cli): simplify — drop constants module and extra lazy-import refactors
* refactor(update): strix --update just re-runs the install script
* perf(cli): drop packaging/install/update changes; deepen lazy imports instead
Reverts the onedir build, install.sh, and self-update changes so release
mechanics stay untouched. Startup cost is addressed purely by deferring
heavy imports (agents/openai, config.models, report state/writer, docker)
until a scan actually runs; DEFAULT_MAX_TURNS moves to strix.config.settings
so argparse no longer pulls the agents SDK.
---------
Co-authored-by: Ahmed Allam <ahmed39652003@gmail.com>
One tool was doing three jobs (wait on the user, wait on other agents, and
- wrongly - wait for a long-running command), so the driver had to guess which
one an agent meant and used parent_id as the proxy: the root waits for a human,
everyone else waits for agents. That proxy is wrong, since the user can message
any agent from the TUI's agent tree.
Tool identity now carries the intent, and the coordinator records it as a
wait_kind that survives snapshot/restore:
respond_to_user -> wait_kind="user", never auto-resumed (root or not)
wait_for_agents -> wait_kind="agents", auto-resumed on a 300s timer
recovery exhaust -> wait_kind="stalled"
respond_to_user fuses the message and the yield into one call, so there is no
way to answer and then forget to stop - the two-step that gpt-4o-mini skipped
2/2 in live testing. Plain text still renders as before.
Auto-resume is also bounded now: an agent that re-parks after every timeout
burned a model turn every 300s for the rest of the scan (and, since parked
children notify their parent, spammed the parent's inbox on the same cycle).
After _MAX_IDLE_AUTO_RESUMES it stays parked until a real message arrives.
The user can message any agent from the TUI, not only the root, so the
justification is that the parent is an agent with no other way to learn
the child parked - not that the child has no human resumer.
Parking is self-service only for the root, which the user is watching.
A parked child owes its parent a report it can no longer send, so the
parent would wait out its full timeout for nothing.
A mutual wait between two agents resolves only when both hit their cap,
so the ceiling is the worst-case idle burn. Name the constants instead of
repeating the literal, and align the interactive auto-resume timeout.
An exhausted agent parked in 'waiting' got a fresh nudge budget on every
600s auto-resume, so a wedged agent could nudge-park-nudge indefinitely.
Track the count on the coordinator, snapshot it, and reset it only on
real input or an explicit lifecycle tool.
Interactive turns ended by plain text left the agent parked in 'waiting'
forever. Require an explicit lifecycle tool in both modes and nudge a
text-only turn back into a tool call, bounded by a recovery limit.