A time in no particular zone, and a preview missing what it previews
The first audit pass: everything from 0.8.1 to 0.9.8 read as a whole rather than one feature at a time, starting with what a model is actually told. Four of these had shipped as correct. The date line carried a timezone variable that resolves to nothing until somebody chooses one -- so every default account was told times were "in unless they say otherwise", while two comments asserted the line disappeared instead. The prompt preview built its variables without a chat, which is what eleven fragments are gated on, so the whole agent surface was absent from it whatever was ticked. Plan mode was instructed to keep its plan current with a tool that mode withdraws. And knowledge_get returned a document whole where every sibling reader caps and says so, its description promising exactly that. The subagent guidance was wrong in both directions at once: it denied a documented parameter and named seven of twenty-three allowed commands. Both halves are pinned by tests against the real list and the real schema now, because prose and a constant drift the moment one is edited alone. docs/notes/audit-0.9.md carries the findings that are not fixed here, with why -- the ones whose fix would change what a feature does are the user's call, not this pass's. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -34,6 +34,21 @@ def test_the_page_lists_every_fragment(client: TestClient, registered):
|
||||
assert f'name="prompt.{fragment.key}"' in page, fragment.key
|
||||
|
||||
|
||||
def test_the_preview_controls_are_inside_what_the_preview_includes(
|
||||
client: TestClient, registered
|
||||
):
|
||||
"""`hx-include="#prompt-form, #preview-controls"` is the whole wiring, so a
|
||||
control placed outside that container is submitted by nothing and changes
|
||||
nothing -- with no error, which is this codebase's recurring failure. Assert
|
||||
the containment rather than the markup of any one field.
|
||||
"""
|
||||
page = client.get("/admin/prompts").text
|
||||
controls = page.split('id="preview-controls"', 1)[1].split("</div>\n </div>", 1)[0]
|
||||
for name in ("preview_model", "preview_bases", "preview_documents",
|
||||
"preview_situation", "preview_mode", "preview_family"):
|
||||
assert f'name="{name}"' in controls, name
|
||||
|
||||
|
||||
def test_the_page_is_refused_to_a_plain_user(client: TestClient, plain_user):
|
||||
assert client.get("/admin/prompts").status_code == 403
|
||||
assert client.post("/admin/prompts", data={}).status_code == 403
|
||||
@@ -135,6 +150,51 @@ def test_the_preview_escapes_what_a_model_wrote(client: TestClient, db, register
|
||||
assert "<img src=x" in body
|
||||
|
||||
|
||||
def test_the_preview_can_reach_the_fragments_that_need_a_chat(client: TestClient, registered):
|
||||
"""Eleven fragments are gated on variables `context_variables` fills only
|
||||
when it is handed a real `Chat`, and the preview hands it `None`. So the
|
||||
entire agent surface, both scheduling fragments and the helper warning were
|
||||
absent from every preview whatever was ticked -- an administrator editing
|
||||
`tool.agent` previewed a system message with `tool.agent` missing from it,
|
||||
and nothing said so. The samples are what close that.
|
||||
"""
|
||||
agent = client.post(
|
||||
"/admin/prompts/preview",
|
||||
data={"preview_family": ["agent"], "preview_mode": "plan"},
|
||||
).text
|
||||
# tool.agent (agent_target), and the mode picked rather than a fixed one.
|
||||
assert "buildbox" in agent
|
||||
assert "Plan** mode" in agent
|
||||
# tool.project_files, context.agent_instructions, context.plan, tool.background
|
||||
assert "pyproject.toml" in agent
|
||||
assert "AGENTS.md" in agent
|
||||
assert "Fix the parser" in agent
|
||||
|
||||
task = client.post(
|
||||
"/admin/prompts/preview", data={"preview_situation": "task", "preview_family": []}
|
||||
).text
|
||||
assert "every weekday at 08:00" in task
|
||||
|
||||
helper = client.post(
|
||||
"/admin/prompts/preview", data={"preview_situation": "helper", "preview_family": []}
|
||||
).text
|
||||
assert "buildbox" not in helper
|
||||
assert helper != task
|
||||
|
||||
|
||||
def test_the_preview_gates_its_samples_exactly_as_a_real_request_would(
|
||||
client: TestClient, registered
|
||||
):
|
||||
"""A preview that admitted a fragment the real request would not is worse
|
||||
than one that omitted it, so the samples follow the same gates: the agent
|
||||
block on the family, the other two on the situation and on no family at all.
|
||||
"""
|
||||
plain = client.post("/admin/prompts/preview", data={"preview_family": ["notes"]}).text
|
||||
assert "buildbox" not in plain
|
||||
assert "AGENTS.md" not in plain
|
||||
assert "every weekday at 08:00" not in plain
|
||||
|
||||
|
||||
def test_the_preview_warns_when_the_cap_would_cut_it_off(client: TestClient, db, registered):
|
||||
settings_store.update(db, {"max_harness_chars": 60}, key=settings_store.PROMPTS)
|
||||
body = client.post("/admin/prompts/preview", data={"preview_family": ["web_search"]}).text
|
||||
|
||||
@@ -986,6 +986,67 @@ async def test_an_ordinary_chat_is_not_asked_to_narrate(db, user_id, machine):
|
||||
assert "Settle what you are setting out to achieve" not in text
|
||||
|
||||
|
||||
def _give_a_plan(db, chat):
|
||||
"""A plan on the chat, the way `_plan_of` finds one: a message carrying it
|
||||
and the chat pointing at that message."""
|
||||
from lembas.services import plans
|
||||
|
||||
message = Message(
|
||||
chat_id=chat.id,
|
||||
role=ROLE_ASSISTANT,
|
||||
content="",
|
||||
plan_json=plans.build(title="Fix the parser", steps="Read it\nChange it"),
|
||||
)
|
||||
db.add(message)
|
||||
db.commit()
|
||||
chat.plan_message_id = message.id
|
||||
db.commit()
|
||||
|
||||
|
||||
async def test_plan_mode_is_not_told_to_use_a_tool_it_does_not_have(db, user_id, machine):
|
||||
"""`agent/tools.py` withdraws `plan_update` in Plan mode -- that mode ends
|
||||
with `plan_submit` instead. Its guidance was gated on {{plan}}, which is set
|
||||
whenever a plan exists in any mode, so a model in Plan mode was told to
|
||||
"keep it current with plan_update as you go" about a tool that was not in
|
||||
its list, directly under `core.tool_list` saying anything unnamed does not
|
||||
exist. The fragment's own hint asserted the two coincided.
|
||||
"""
|
||||
from lembas.services import harness
|
||||
|
||||
chat, _profile = _setup(db, user_id, machine, mode=policy.MODE_PLAN)
|
||||
_give_a_plan(db, chat)
|
||||
user = db.get(User, user_id)
|
||||
|
||||
offered = tools_service.resolve_tools(db, chat, user)
|
||||
assert "plan_update" not in offered.by_name
|
||||
|
||||
values = harness.context_variables(db, user, offered.schemas, chat)
|
||||
# The plan is still shown -- a model that cannot see it cannot submit a
|
||||
# better one. Only the instruction to *edit* it goes.
|
||||
assert values["plan"]
|
||||
assert values["plan_editable"] == ""
|
||||
|
||||
text = harness.compose(db, user, offered.schemas, chat)
|
||||
assert "Fix the parser" in text
|
||||
assert "plan_update" not in text
|
||||
|
||||
|
||||
async def test_a_mode_that_has_plan_update_is_still_told_about_it(db, user_id, machine):
|
||||
"""The other half, or the gate is just a deletion."""
|
||||
from lembas.services import harness
|
||||
|
||||
chat, _profile = _setup(db, user_id, machine, mode=policy.MODE_EDIT)
|
||||
_give_a_plan(db, chat)
|
||||
user = db.get(User, user_id)
|
||||
|
||||
offered = tools_service.resolve_tools(db, chat, user)
|
||||
assert "plan_update" in offered.by_name
|
||||
|
||||
values = harness.context_variables(db, user, offered.schemas, chat)
|
||||
assert values["plan_editable"] == values["plan"] != ""
|
||||
assert "plan_update" in harness.compose(db, user, offered.schemas, chat)
|
||||
|
||||
|
||||
async def test_an_agent_chat_is_told_its_real_round_budget(db, user_id, machine):
|
||||
"""MAX_ROUNDS is one. An agent chat gets hundreds, and telling it one would
|
||||
be a false fact about its own budget on every turn."""
|
||||
|
||||
@@ -37,6 +37,37 @@ def test_no_tools_means_no_tool_guidance(db, owner):
|
||||
assert harness.compose(db, owner, None) == text
|
||||
|
||||
|
||||
def test_the_date_line_names_a_zone_when_nobody_has_chosen_one(db, owner):
|
||||
"""The shipped default, on the shipped configuration.
|
||||
|
||||
`clock.name_for` returns "" for an account that has never chosen a zone,
|
||||
which is every account until somebody visits the settings page. The variable
|
||||
sits *inside* a sentence, and `substitute` drops a line only when the whole
|
||||
line is blank after expansion -- so an unresolved zone was never a dropped
|
||||
line, it was "in unless", shipped on every request. Both the comment in
|
||||
`harness.py` and the fragment's own hint claimed otherwise, and no test
|
||||
asked.
|
||||
"""
|
||||
assert not (owner.settings_json or {}).get("timezone")
|
||||
text = harness.compose(db, owner, [])
|
||||
|
||||
line = next(ln for ln in text.splitlines() if "Times the person gives you" in ln)
|
||||
assert "in unless" not in line
|
||||
assert " " not in line
|
||||
# Whatever the host is set to, it has to be *something*.
|
||||
zone = line.split(" are in ", 1)[1].split(" unless", 1)[0]
|
||||
assert zone.strip()
|
||||
|
||||
|
||||
def test_the_date_line_uses_the_readers_own_zone_when_they_have_one(db, owner):
|
||||
"""And a chosen zone is preferred to the server's, which is the whole point
|
||||
of the variable -- the fallback must not have flattened it."""
|
||||
owner.settings_json = {**(owner.settings_json or {}), "timezone": "Pacific/Auckland"}
|
||||
db.commit()
|
||||
text = harness.compose(db, owner, [])
|
||||
assert "are in Pacific/Auckland unless" in text
|
||||
|
||||
|
||||
def test_clearing_the_core_fragments_restores_an_empty_harness(db, owner):
|
||||
"""The behaviour change is a default, not a rule: an administrator who wants
|
||||
nothing sent to a tool-less model can still have exactly that."""
|
||||
|
||||
@@ -672,3 +672,41 @@ def test_a_helpers_chat_is_sized_by_its_own_numbers(db):
|
||||
assert limits.steps == 7
|
||||
assert limits.wall_seconds == 111.0
|
||||
assert agent_session.resolve(db, parent, _user(db)).limits.steps == 200
|
||||
|
||||
|
||||
# --- What the model is told it may do -----------------------------------------
|
||||
def test_the_guidance_names_every_command_a_helper_may_run():
|
||||
"""`SAFE_COMMANDS` holds twenty-three entries; the fragment named seven of
|
||||
them and said "and nothing else", so a model avoided commands it had --
|
||||
which costs nothing visible and is therefore never reported. Pinned rather
|
||||
than proof-read, because the two drift the moment one is edited alone.
|
||||
"""
|
||||
from lembas.services import prompts
|
||||
|
||||
text = next(f for f in prompts.BUILTIN if f.key == "tool.subagent_agent").default
|
||||
for entry in subagent_service.SAFE_COMMANDS:
|
||||
if entry.startswith("file_"):
|
||||
continue # a tool name, not a command -- see SAFE_COMMANDS' comment
|
||||
# The word a model would look for: the command itself, or for git the
|
||||
# subcommand, since the prose shares one "git" across all six.
|
||||
parts = entry.removesuffix("*").strip().split()
|
||||
word = parts[1] if parts[0] == "git" else parts[0]
|
||||
assert word in text, f"{entry!r} is allowed and unmentioned"
|
||||
|
||||
|
||||
def test_the_guidance_does_not_deny_a_parameter_the_tool_offers():
|
||||
"""It said a helper "reads and reports ... and nothing else, in every mode",
|
||||
beside a `write` parameter that makes one write files. A model reads the
|
||||
prose, not the schema, so the parameter was effectively unreachable.
|
||||
"""
|
||||
from lembas.services import prompts
|
||||
|
||||
text = next(f for f in prompts.BUILTIN if f.key == "tool.subagent_agent").default
|
||||
assert "write" in text
|
||||
assert "nothing else, in every mode" not in text
|
||||
|
||||
schema = subagent_service.tool_defs()[0]
|
||||
assert "write" in schema.parameters["properties"]
|
||||
# And the command list really is fixed whatever `write` says, which is the
|
||||
# distinction the wording now has to carry.
|
||||
assert "in every mode" in text
|
||||
|
||||
@@ -415,6 +415,56 @@ async def test_a_failed_fetch_does_not_kill_the_reply(monkeypatch):
|
||||
assert "not reachable" in outcome.content
|
||||
|
||||
|
||||
async def test_a_long_knowledge_document_is_cut_and_the_model_told(db, user_id):
|
||||
"""It was the one reader with no bound at all. `fetch` caps and says so,
|
||||
`file_read` caps and says so, the memories block, the skill index and the
|
||||
project listing all carry budgets -- and `knowledge_get` returned
|
||||
`extracted_text` whole. Its description said "in full", which is why this
|
||||
read as correct: the tool did exactly what it claimed, and what it claimed
|
||||
was one call away from filling the window with nothing reporting it.
|
||||
"""
|
||||
from lembas.db.models import User
|
||||
from lembas.services.library import documents as documents_service
|
||||
|
||||
owner = db.get(User, user_id)
|
||||
base = documents_service.create_base(db, owner=owner, name="Long")
|
||||
document = documents_service.store_upload(
|
||||
db, owner=owner, payload=b"x" * 90_000, filename="big.txt",
|
||||
title="Big", base=base,
|
||||
)
|
||||
|
||||
outcome = await tools_service.run_tool(
|
||||
tools_service.ToolContext(owner_id=user_id),
|
||||
"knowledge_get",
|
||||
json.dumps({"id": document.id}),
|
||||
)
|
||||
assert len(outcome.content) < tools_service.MAX_DOCUMENT_CHARS + 500
|
||||
assert "Cut off here" in outcome.content
|
||||
assert outcome.event["truncated"] is True
|
||||
|
||||
|
||||
async def test_a_short_knowledge_document_is_returned_whole(db, user_id):
|
||||
"""The cap must not become a truncation notice on ordinary documents."""
|
||||
from lembas.db.models import User
|
||||
from lembas.services.library import documents as documents_service
|
||||
|
||||
owner = db.get(User, user_id)
|
||||
base = documents_service.create_base(db, owner=owner, name="Short")
|
||||
document = documents_service.store_upload(
|
||||
db, owner=owner, payload=b"The mallorn is golden.", filename="a.txt",
|
||||
title="Mallorn", base=base,
|
||||
)
|
||||
|
||||
outcome = await tools_service.run_tool(
|
||||
tools_service.ToolContext(owner_id=user_id),
|
||||
"knowledge_get",
|
||||
json.dumps({"id": document.id}),
|
||||
)
|
||||
assert "The mallorn is golden." in outcome.content
|
||||
assert "Cut off here" not in outcome.content
|
||||
assert "truncated" not in outcome.event
|
||||
|
||||
|
||||
async def test_a_long_page_is_cut_and_the_model_told(monkeypatch):
|
||||
"""120_000 characters is roughly thirty thousand tokens. One call would fill
|
||||
an ordinary window and spend an agent chat's whole output budget."""
|
||||
|
||||
Reference in New Issue
Block a user