Two controls that did nothing, and instructions worth reading
**Switching mode mid-reply did nothing.** The mode was snapshotted when the reply began, so changing to Auto during a long agent reply went on asking about every call until the next turn. The same snapshot held the chat's allow list, which means "Always allow this" was accepted, written to the row, and then ignored for the rest of the reply that had just asked about it -- the same bug, in the quieter place nobody reported. `agent/session.py:refresh` re-reads exactly those two, between rounds and never within one. A round's calls are authorised together, so a switch must not retroactively approve what is already queued -- which is the property the reply-long snapshot was protecting by accident, and the reason this is not simply moved into `_authorise`. It mutates in place, because `as_approved` copies field references and a replacement would leave the round's approved copy pointing at the old context. **The composer's highlighting stayed behind after sending.** htmx fires afterSwap and afterSettle *before* afterRequest, and the composer empties itself from `hx-on::after-request` -- so every repaint ran while the box still held the message. It repaints on afterRequest and on `reset` as well now, deferred a frame: a form's reset event fires before its fields are actually cleared, so reading the value in the same turn paints the text that is about to vanish. Driven under a DOM stub reproducing htmx's real ordering, and confirmed to fail without the fix. **plan_update, audited.** It never said to mark a task `doing`, so the plan only ever showed work already finished, which is the opposite of "what somebody reads to see where you are". It never said several changes fit in one call, so a model spends a round per task. And `done` now means checked rather than written. **New: core.engineering**, an agent-chat fragment about conduct rather than about any language -- run what you write, find the project's own build and test commands rather than guessing, read before editing, change one thing at a time, read the error instead of guessing at a fix, do not broaden an except to make output clean, and say what you did not check. Every line is about the gap between having written something and knowing it works, which is the gap a model closes by asserting. That pushed the shipped harness to within 1,300 characters of its ceiling, where crossing it silently severs the project's own AGENTS.md. The ceiling is 20,000 and the test pins a margin as well as a fit -- the headroom is also where an administrator's own wording goes, and an override is usually longer than the default it replaces. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -160,6 +160,37 @@ def _allow_for(chat: Chat) -> tuple[str, ...]:
|
||||
return tools_service.scoped_allow(chat)
|
||||
|
||||
|
||||
def refresh(db: DBSession, agent: AgentContext) -> AgentContext:
|
||||
"""Re-read the two things a person can change while a reply is running.
|
||||
|
||||
The mode and the chat's own allow list, and nothing else. Everything else on
|
||||
the context is fixed for the life of a chat (the connection, the directory)
|
||||
or is an instance setting nobody is editing mid-reply.
|
||||
|
||||
Called once per round rather than once per reply. The reply-long snapshot it
|
||||
replaces made both controls do nothing until the next turn: switching to
|
||||
Auto during a long agent reply went on asking about every call, and
|
||||
"Always allow this" was accepted, written to the row, and then ignored for
|
||||
the rest of the reply that had just asked. Both look exactly like a control
|
||||
that does not work, because for that reply they were.
|
||||
|
||||
Once per *round* and not more often, because a round's calls are authorised
|
||||
together: what is already queued was decided under the mode that was in
|
||||
force when it was queued, and switching to Auto must not retroactively
|
||||
approve it. Mutated in place -- `as_approved` copies field references, so a
|
||||
replacement here would leave the approved copy of this round pointing at the
|
||||
old one.
|
||||
"""
|
||||
chat = db.get(Chat, agent.chat_id)
|
||||
if chat is None:
|
||||
return agent
|
||||
|
||||
agent.mode = chat.agent_mode if chat.agent_mode in policy.MODES else policy.MODE_MANUAL
|
||||
instance = settings_store.agents(db)
|
||||
agent.allow = (*(instance.get("allow_default") or ()), *_allow_for(chat))
|
||||
return agent
|
||||
|
||||
|
||||
def resolve(db: DBSession, chat: Chat, user: User | None) -> AgentContext | None:
|
||||
"""This chat's agent setup, or None if it has none it can use.
|
||||
|
||||
|
||||
@@ -1015,14 +1015,23 @@ def tool_defs(context: AgentContext | None = None) -> list[ToolDef]:
|
||||
name="plan_update",
|
||||
family=FAMILY_AGENT,
|
||||
description=(
|
||||
"Keep the plan current while you carry it out. Call it when a "
|
||||
"task finishes, when something you find changes what needs doing, "
|
||||
"and when a task turns out to be unnecessary — as you go, not at "
|
||||
"the end. The plan is what somebody reads to see where you are.\n"
|
||||
"Keep the plan current while you carry it out. The plan is what "
|
||||
"somebody reads to see where you are, so it has to be updated as "
|
||||
"you go and not written up at the end.\n"
|
||||
"\n"
|
||||
"Quote the ids from the plan in your prompt: tasks are t1, t2 and "
|
||||
"so on, objectives are o1. This does not end your turn; carry on "
|
||||
"with the work afterwards."
|
||||
"Mark a task 'doing' when you start it and 'done' when you have "
|
||||
"checked it actually works — not when you have written the code "
|
||||
"for it. Use 'dropped' for a task that turned out to be "
|
||||
"unnecessary, and say why in its note. Add tasks the plan did "
|
||||
"not anticipate as you discover them.\n"
|
||||
"\n"
|
||||
"One call carries as many changes as you like: finishing one "
|
||||
"task and starting the next is a single call, not two. Use the "
|
||||
"ids exactly as they appear in the plan above — tasks are t1, "
|
||||
"t2 and so on, objectives o1, phases p1.\n"
|
||||
"\n"
|
||||
"This does not end your turn and is not a progress report to "
|
||||
"stop after. Carry straight on with the work."
|
||||
),
|
||||
parameters={
|
||||
"type": "object",
|
||||
|
||||
Reference in New Issue
Block a user