Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 860e0d56bb | |||
| 36aad58ce9 | |||
| 21625d3c89 | |||
| 87857eb44f | |||
| daf6dbfc46 | |||
| c06fea412f | |||
| 4ce2e0e8b8 | |||
| bc2ce176d4 | |||
| 911f78bcb3 | |||
| 669316c254 |
+110
-4
@@ -76,6 +76,112 @@ auth via infra-ops `ENGINE_TOKEN`. Deploy: `irv-ml1:8201`, WireGuard-only. Canon
|
||||
`vh/arbo:docs/rest-api.md` v1.4.27 + auto-served `/openapi.json`. comfy-dev offered a concrete t2i
|
||||
walkthrough + will coordinate the token when we smoke.
|
||||
|
||||
**⚠️ Cross-agent Tier-3 memory BLEED — WORLDTREE defect (found via debug surface 2026-07-17; reported to
|
||||
worldtree-dev, reply pending).** Loaded Lofn (Tier-1) in the memory explorer under end_user `ratatoskr-tui`
|
||||
→ saw **Sindra's** chunks, and a live Lofn turn **recalled Vuong's name** (a Sindra fact). ROOT: WT writes
|
||||
Tier-3 memory scoped `{"end_user": "ratatoskr-tui"}`, `worldtree_scope: "end_user"`, **NO `agent_self`** —
|
||||
`agent_id: "ratatoskr:sindra"` rides as record metadata only, not isolation scope. So every agent under one
|
||||
end_user shares the pool. **NOT a ratatoskr defect:** our v0.6 lattice {end_user,group,tenant,agent_self} +
|
||||
`_matches_scope` support agent_self; `upsert_many` stores scope verbatim (no strip/inject); read-path proof —
|
||||
Lofn getting Sindra's chunks means WT's retrieval `scope_all` ALSO omits agent_self (else `_scope_subset`
|
||||
excludes agent_self-less records → Lofn would get nothing). FIX is WT's: stamp `agent_self` on Tier-3 writes
|
||||
+ include it in turn-retrieval scope_all; ratatoskr enforces the moment it arrives, zero change. Likely the
|
||||
known per-(user,character) isolation gap (#172). Thread `01KXRY8V1C30R3HGFCTKHPA4PR`. **FORK RELEVANCE:** the
|
||||
fork's memory isolation inherits this same WT axis — don't design around it, it's Worldtree's to close.
|
||||
Explorer nuance: its agent filter is LENIENT (excludes a chunk only if it CARRIES a conflicting agent_self),
|
||||
so agent_self-less chunks show under any agent — a faithful display of user-shared data, not a separate bug.
|
||||
|
||||
**RESOLVED-DIRECTION 2026-07-17 (WT #368 "Cross-agent Tier-3 memory bleed — end_user scope is agent-agnostic").**
|
||||
Operator ruled **Option 1: silo per (user, character) + backfill**. NO amnesia cliff — backfill stamps `agent_self`
|
||||
from the `record.agent_id` every chunk already carries. Contract arc starting WT-side (blast-radius→contract→heid
|
||||
review→TDD→code-review+bug-hunt panels); **ratatoskr gets the contract for a consumer-side read BEFORE preflight —
|
||||
we are the enforcement half.** Confirmed empirically (our `_scope_subset`/`_matches_scope`): a multi-axis scope_any
|
||||
element is matched CONJUNCTIVELY → **fix rides `scope_any=[{end_user:X, agent_self:Y}]` ALONE, no `scope_all`
|
||||
threading** (INV-005 / #297 "each AND-matched as a whole"). Cutover ordering: **backfill THEN flip retrieval**
|
||||
(a conjunctive element excludes 100% of un-backfilled chunks). **READ-PATH WRINKLE (flagged to WT):** labeled Lofn
|
||||
capture (agent=lofn, session 15fde90d, turn 2253) recited *"Vuong is the name on file"* — behavioral bleed exhibit
|
||||
— BUT driven via CLI `--bifrost-url` (plane=direct) the turn hit our provider with a HANDSHAKE ONLY, zero
|
||||
memory-calls, yet still knew the name → the name came from **Worldtree-internal context, not our Tier-3 store** on a
|
||||
direct bind. CONFIRMED via a SECOND fresh combined-plane turn (session c153d8ec): it TOO recited the name with
|
||||
handshake-but-ZERO-memory-calls. So the turn-context bleed is Worldtree-INTERNAL on BOTH bind shapes — it never
|
||||
traverses our Tier-3 retrieval. **TWO channels:** (1) Tier-3 semantic recall to us (the morning memory.search lines;
|
||||
closed by the conjunctive scope_any fix) and (2) WT-internal assembly (the ACTIVE fresh-session turn-context channel;
|
||||
needs the same (user,character) conjunction WT-side or the silo leaks exactly where the operator saw it). Both fresh
|
||||
sessions verified via message logs (name only in Lofn's response, no prior history → not same-session continuity).
|
||||
WT is tracing channel 2 code-side (Tier-1 per-agent store audited immune). CLI GAP: `--bifrost-plane` offers only
|
||||
memory/affect (→:8391/:8390), not `combined` (→:8392). Exhibit sessions: 15fde90d (direct), c153d8ec (combined).
|
||||
**CHANNEL 2 = REAL BUG (discriminator run via GET /search, our key carries search.read, 2026-07-17):** name
|
||||
introduced ONLY to Sindra (8 user msgs, e.g. session 81952f2e "my name is Vuong"), ZERO to Lofn (0 user, 4 assistant
|
||||
incl. PRE-EXISTING session cc8d7ee6 @20:47Z which PREDATES my captures — the operator's own observation). So Lofn's
|
||||
person-prime (#349, turn-1 query-less "what I know about this person") surfaces a Sindra-only fact un-introduced →
|
||||
cross-agent leak in WT's server-side per-agent store, one layer below the Bifrost plane. WT's channel-2 source: on
|
||||
foundational agents the Bifrost store is NEVER attached (kind-guard) — context comes from WT's own per-agent store via
|
||||
#349 person-prime + #221 auto-recall, whose query filter carries NO agent axis (end_user only; isolation rests solely
|
||||
on physical per-collection boundary). That missing agent-axis looks LOAD-BEARING (channel-2's real fix), not just
|
||||
defense-in-depth. WT tracing write/query path; **rev 1.2 contract (ch1 + ch2 + agent-axis stamp) comes to ratatoskr
|
||||
for the enforcement read** (INV-368-9 blocks WT preflight until then).
|
||||
**SCRUB + REPRO (2026-07-18):** operator ruled a FULL memory scrub both sides (contamination noise, nothing
|
||||
worth keeping). Server-side (personal/b125): infra-ops wiping all agent chroma collections + shared promotion
|
||||
state. Ours: **Tier-3 memory.db scrubbed to 0 chunks, :8392 restarted born-empty, no backup** (operator direct
|
||||
go; new pid 3548269; affect plane untouched). Personal is ALREADY b125 (redeployed 20:35Z; /version 404 =
|
||||
b125 dropped the route, misled us). Current-code status: the fresh Lofn 'Vuong' chunks are the FEEDBACK LOOP
|
||||
(Lofn's own recitations re-extracted), NOT proven fresh mis-write; the original cross-agent mis-write ("I'm
|
||||
Saga, and you are Vuong" → lofn.chroma) is Jun-3 code, UNPROVEN on b125. **QUEUED (after BOTH scrubs confirmed):
|
||||
the marker repro** — drive ONE fresh Sindra session (combined :8392 bound), user introduces an absurd greppable
|
||||
marker ("my favorite mineral is rhodochrosite", NOT the name), idle a few min for extraction, report timestamps
|
||||
→ infra-ops greps lofn.chroma + our store. Marker in lofn.chroma = mis-write ALIVE on b125 (code fix); marker
|
||||
only in our store = channel 2 is just the feedback loop + agent-axis hardening. Post-scrub our #368 backfill is
|
||||
a NO-OP (born-conjunctive once the fix ships); contract keeps migration machinery for other consumers. DO NOT
|
||||
re-ask Lofn the name meanwhile (re-seeds the loop). Awaiting WT infra-ops server-side-scrub confirm to run it.
|
||||
**RAN 2026-07-18 (both scrubs confirmed clean):** marker repro executed — fresh Sindra session 822c3af7
|
||||
(combined :8392), user introduced "my favorite mineral is rhodochrosite", 5 more turns drove the idle-scan
|
||||
promotion at ~6 turns. RESULT: marker promoted to OUR store scoped **{end_user}-only** (chunk 647aeac6;
|
||||
record.agent_id "ratatoskr:sindra" metadata; worldtree_scope "end_user"; verbatim "Favorite mineral is
|
||||
rhodochrosite") = **channel-1 baseline exhibit** (pre-fix shape; agent_self absent, to be backfilled from the
|
||||
agent_id metadata). **KEY WT CORRECTION:** consumer-defined agents (ratatoskr:sindra) have NO server-side
|
||||
chroma — they write ONLY to our store via Bifrost; only FOUNDATIONAL agents (lofn/forseti/mimir) get
|
||||
server-side collections. Confirmed on the wire: Sindra IS Bifrost-attached (queried us scan+search) UNLIKE
|
||||
foundational Lofn (handshake-only, no memory-call). DECISIVE channel-2 grep (marker must be in ZERO
|
||||
server-side stores; ANY server-side hit = cross-plane bug, no benign reading) still PENDING infra-ops.
|
||||
**ETA (WT): rev 1.2 amended contract reaches us TODAY 2026-07-18 for the consumer-side enforcement read;
|
||||
ship-ready LIKELY 2026-07-19; our integration risk LOW** (born-fresh store won't exercise the backfill
|
||||
machinery, which is the bulk of the impl). ratatoskr is the enforcement half; INV-368-9 blocks WT preflight.
|
||||
**CHANNEL 2 CLOSED CLEAN + ENFORCEMENT READ DONE (2026-07-18):** marker routed ONLY to our store, zero in every
|
||||
foundational store → current-b125 write path VERIFIED correct; the contamination was old-code (saga-v1, pre-#211)
|
||||
seeding + a b125 recited-content re-promotion feedback loop (→ WT #369). Contract FINAL: `Worldtree/docs/contracts/
|
||||
issues/368.contract.md` rev 1.2 (cda308c). **Consumer-side enforcement read complete — read-path CONFORMANT
|
||||
(conjunctive-per-element re-verified); 3 findings sent to WT (thread 01KXSBY3A8):** F1[HIGH] INV-368-10 can fail-OPEN
|
||||
at our store — our empty-scope = MATCH-ALL, so the guard's "drop end_user element" must NOT reach us as an empty
|
||||
filter (else total bleed); fix = don't-call/never-match. F2[HIGH] backfill scan NOT snapshot-stable — our scan is
|
||||
offset-over-updated_at (chunk_id NOT sortable), and re-upserts advance updated_at, so multi-page paging under
|
||||
concurrent mutation can SKIP chunks → un-stamped → post-flip amnesia (INV-368-3 failure); fix = whole-corpus
|
||||
single-page scan (no consumer change) OR add chunk_id sort (consumer change). F3[MED] sentinel needs a valid 1024-dim
|
||||
embedding or our vec0 upsert raises → backfill never completes. All 3 are WT-side backfill/guard adjustments, NOT
|
||||
consumer reworks — the "zero consumer changes" claim holds for reads, needs the qualifier for backfill/guard.
|
||||
**FINAL 2026-07-18: all 3 findings FOLDED into contract rev 1.3 (3e3f629).** F1 → INV-368-10 now mandates
|
||||
drop-to-empty = SKIP the wire call (test `drop_to_empty_skips_wire` asserts our store gets NO query). F2 →
|
||||
option (a) pinned: backfill scans whole corpus in ONE page (test `single_page_snapshot`; immutable-key paging
|
||||
= recorded scale follow-up that WOULD be a consumer change). F3 → sentinel writes a zero-vector at embedding_dim
|
||||
(test `sentinel_carries_vector`). Our read-path conformance + conjunctive-per-element check cited in the revision
|
||||
record. WT implementing against rev 1.3 (TDD after their context-reset; cross-frontier panels follow); ship ~7/19.
|
||||
**QUEUED — backfill live-verify:** when WT's backfill lands, drive a SYNTHETIC legacy corpus into our store, run
|
||||
their backfill against it, confirm 100% stamped (no un-attributable skips) + sentinel writes + reads flip clean,
|
||||
all BEFORE any real flip. **#369** = the recited-content re-promotion feedback-loop policy class (WT owns).
|
||||
**#368 arc DONE from ratatoskr's side** — diagnosed → reported → operator ruled → exhibits + both scrubs →
|
||||
channel-2 closed clean → enforcement read (3 findings, all folded).
|
||||
**LIVE SILO TEST IN PROGRESS (2026-07-18; operator moving the fix forward — "the code will complete, you'll test
|
||||
it").** Store RE-SCRUBBED born-fresh (0 chunks). Created **THROWAWAY consumer agent `ratatoskr:betty`** (role
|
||||
`thoughtful-character`, minimal prompt) for the Alice/Betty silo demo. **⚠️ MUST DELETE after the test:**
|
||||
`DELETE /agents/ratatoskr:betty` (or fix+use `tier3 delete ratatoskr:betty`) + `remove_local_agent('ratatoskr:betty')`.
|
||||
**⚠️ DON'T HURT SINDRA (operator directive):** she's test agent A; delete ONLY betty; her definition stays intact.
|
||||
TEST (post-deploy): Sindra says "coffee>tea", Betty says "tea>coffee" → verify post-fix conjunctive write
|
||||
`{end_user, agent_self}` + conjunctive read ISOLATES (Sindra must NOT recite Betty's tea, & vice-versa) = silo holds.
|
||||
**TIER3 CLI DRIFT (consumer-side discovery):** `python -m ratatoskr.tier3 define` is BROKEN vs live b125 —
|
||||
`POST /agents/define` now requires `role` (replaced `model`); `role` is a model-role validated server-side
|
||||
(`thoughtful-character` valid = Sindra's; old model IDs + `char-rp` rejected). Also drifted: `/models/available-
|
||||
for-characters` exists, `GET/DELETE/PATCH /agents/{agent_id}` exist. Created Betty via direct `POST /agents/define`.
|
||||
tier3.py needs a schema update (follow-up). Awaiting WT fix deploy signal to run the test (monitor armed).
|
||||
|
||||
**✅ Cross-session name-recall arc COMPLETE + SHIPPED (v0.20.11→v0.20.15, pushed).** person-prime `scan`
|
||||
verb + `mark_superseded` verb both shipped, deployed to :8392, and live-verified; **WT #364 closed
|
||||
end-to-end both sides; Sindra durably recalls Vuong's name** (the "name unknown" negatives are RETIRED
|
||||
@@ -86,10 +192,10 @@ via mark_superseded, not just recency-evicted). Full arcs:
|
||||
buttons (hover-revealed; one delegated `#tw` handler + LAN-http clipboard fallback; Playwright-verified).
|
||||
Served from disk → browser refresh picks it up, no :8765 restart for UI edits.
|
||||
|
||||
**Sindra READY for a test drive — PRISTINE BASELINE.** Tidied the live memory.db to **5 clean genuine
|
||||
facts** (name leads person-prime → she recalls the name turn-1); rollback checkpoint at
|
||||
`sindra-baseline/pristine-baseline.db` (+ `pre-tidy-*.db`), both gitignored. Restore: stop :8392, copy
|
||||
the baseline over `memory.db`, restart.
|
||||
**OBSOLETE — Sindra pristine baseline WIPED 2026-07-18.** The whole Tier-3 memory.db was scrubbed to 0
|
||||
chunks (operator full-scrub ruling for #368 — contamination noise, nothing worth keeping; no backup taken
|
||||
per direct order). The old `sindra-baseline/*.db` checkpoints predate the scrub and no longer reflect a
|
||||
"clean 5-fact" target — the store is now born-empty. Any future test drive starts from the born-fresh store.
|
||||
|
||||
**Deferred/queued tasks (all NON-URGENT — tracked in Recent decisions):** (1) P06-driver **referential-
|
||||
frame A/B/C probe** (operator-ratified; waits on WT b122; read-shape + brokkr's scoring rubric captured →
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "ratatoskr"
|
||||
version = "0.21.0"
|
||||
version = "0.21.1"
|
||||
description = "Worldtree Conversation API debug console (web + headless CLI) — multi-pane observability"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
|
||||
+17
-15
@@ -151,7 +151,7 @@ async def define_agent(
|
||||
*,
|
||||
agent_name: str,
|
||||
system_prompt: str,
|
||||
model: str,
|
||||
role: str,
|
||||
) -> Tier3AgentInfo:
|
||||
"""POST /agents/define — create a Tier 3 agent.
|
||||
|
||||
@@ -165,12 +165,14 @@ async def define_agent(
|
||||
f"agent_name must match [a-z][a-z0-9-]{{2,63}}: {agent_name!r}"
|
||||
)
|
||||
assert system_prompt, "system_prompt must be non-empty"
|
||||
assert model, "model must be non-empty"
|
||||
assert role, "role must be non-empty"
|
||||
|
||||
# b125 drift: /agents/define takes `role` (a model-role, e.g. "thoughtful-character")
|
||||
# in the request; the response echoes it back as `model`. See #15 follow-up.
|
||||
body = {
|
||||
"agent_name": agent_name,
|
||||
"system_prompt": system_prompt,
|
||||
"model": model,
|
||||
"role": role,
|
||||
}
|
||||
resp = await client.post("/agents/define", json=body)
|
||||
|
||||
@@ -198,7 +200,7 @@ async def patch_agent(
|
||||
agent_id: str,
|
||||
*,
|
||||
system_prompt: str | None = None,
|
||||
model: str | None = None,
|
||||
role: str | None = None,
|
||||
) -> Tier3AgentInfo:
|
||||
"""PATCH /agents/<id> — mutate system_prompt and/or model.
|
||||
|
||||
@@ -207,15 +209,15 @@ async def patch_agent(
|
||||
"""
|
||||
assert client is not None
|
||||
assert ":" in agent_id, f"tier 3 agent_id must contain ':': {agent_id!r}"
|
||||
assert system_prompt is not None or model is not None, (
|
||||
"patch requires at least one of system_prompt or model"
|
||||
assert system_prompt is not None or role is not None, (
|
||||
"patch requires at least one of system_prompt or role"
|
||||
)
|
||||
|
||||
body: dict[str, str] = {}
|
||||
if system_prompt is not None:
|
||||
body["system_prompt"] = system_prompt
|
||||
if model is not None:
|
||||
body["model"] = model
|
||||
if role is not None:
|
||||
body["role"] = role
|
||||
resp = await client.patch(f"/agents/{agent_id}", json=body)
|
||||
|
||||
if resp.status_code == 200:
|
||||
@@ -278,14 +280,14 @@ def _build_parser() -> argparse.ArgumentParser:
|
||||
help="System prompt the agent ships with.",
|
||||
)
|
||||
p_define.add_argument(
|
||||
"--model", required=True,
|
||||
help="Provider model ID (NOT a profile alias; e.g., qwen3.6-35-a3b).",
|
||||
"--role", required=True,
|
||||
help="Model role, e.g. thoughtful-character (see GET /models/available-for-characters).",
|
||||
)
|
||||
|
||||
p_patch = sub.add_parser("patch", help="Mutate system_prompt and/or model.")
|
||||
p_patch.add_argument("agent_id", help='Full "<user_id>:<agent_name>" form.')
|
||||
p_patch.add_argument("--system-prompt", dest="system_prompt", default=None)
|
||||
p_patch.add_argument("--model", default=None)
|
||||
p_patch.add_argument("--role", default=None)
|
||||
|
||||
p_delete = sub.add_parser("delete", help="Hard-delete a Tier 3 agent.")
|
||||
p_delete.add_argument("agent_id", help='Full "<user_id>:<agent_name>" form.')
|
||||
@@ -327,7 +329,7 @@ async def _run_define(ns: argparse.Namespace) -> int:
|
||||
client,
|
||||
agent_name=ns.name,
|
||||
system_prompt=ns.system_prompt,
|
||||
model=ns.model,
|
||||
role=ns.role,
|
||||
)
|
||||
# v0.8.0: persist to local index so the picker can show it.
|
||||
add_local_agent(
|
||||
@@ -352,9 +354,9 @@ async def _run_patch(ns: argparse.Namespace) -> int:
|
||||
update_local_agent,
|
||||
)
|
||||
|
||||
if ns.system_prompt is None and ns.model is None:
|
||||
if ns.system_prompt is None and ns.role is None:
|
||||
raise _Tier3UsageError(
|
||||
"patch requires at least one of --system-prompt or --model"
|
||||
"patch requires at least one of --system-prompt or --role"
|
||||
)
|
||||
async with httpx.AsyncClient(
|
||||
base_url=server_url,
|
||||
@@ -368,7 +370,7 @@ async def _run_patch(ns: argparse.Namespace) -> int:
|
||||
client,
|
||||
ns.agent_id,
|
||||
system_prompt=ns.system_prompt,
|
||||
model=ns.model,
|
||||
role=ns.role,
|
||||
)
|
||||
# v0.8.0: refresh local index with the post-patch state.
|
||||
update_local_agent(
|
||||
|
||||
+17
-17
@@ -43,7 +43,7 @@ class TestDefineAgent:
|
||||
client,
|
||||
agent_name="wizard",
|
||||
system_prompt="You are a wizard.",
|
||||
model="qwen3.6-35-a3b",
|
||||
role="qwen3.6-35-a3b",
|
||||
)
|
||||
assert isinstance(info, Tier3AgentInfo)
|
||||
assert info.agent_id == "ratatoskr:wizard"
|
||||
@@ -64,14 +64,14 @@ class TestDefineAgent:
|
||||
client,
|
||||
agent_name="wizard",
|
||||
system_prompt="You are a wizard.",
|
||||
model="qwen3.6-35-a3b",
|
||||
role="qwen3.6-35-a3b",
|
||||
)
|
||||
body = _json.loads(route.calls[0].request.content)
|
||||
# INV-001: exactly these three keys — no layer fields, no metadata.
|
||||
assert body == {
|
||||
"agent_name": "wizard",
|
||||
"system_prompt": "You are a wizard.",
|
||||
"model": "qwen3.6-35-a3b",
|
||||
"role": "qwen3.6-35-a3b",
|
||||
}
|
||||
|
||||
@respx.mock
|
||||
@@ -90,7 +90,7 @@ class TestDefineAgent:
|
||||
client,
|
||||
agent_name="overflow",
|
||||
system_prompt="x",
|
||||
model="m",
|
||||
role="m",
|
||||
)
|
||||
assert exc.value.retry_after == 0
|
||||
|
||||
@@ -105,7 +105,7 @@ class TestDefineAgent:
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
with pytest.raises(Tier3UserIdUnsupported):
|
||||
await define_agent(
|
||||
client, agent_name="wizard", system_prompt="x", model="m"
|
||||
client, agent_name="wizard", system_prompt="x", role="m"
|
||||
)
|
||||
|
||||
@respx.mock
|
||||
@@ -120,7 +120,7 @@ class TestDefineAgent:
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
with pytest.raises(Tier3LayerDeferred) as exc:
|
||||
await define_agent(
|
||||
client, agent_name="wizard", system_prompt="x", model="m"
|
||||
client, agent_name="wizard", system_prompt="x", role="m"
|
||||
)
|
||||
assert exc.value.field == "persona"
|
||||
|
||||
@@ -133,7 +133,7 @@ class TestDefineAgent:
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
with pytest.raises(AssertionError):
|
||||
await define_agent(
|
||||
client, agent_name="Wizard", system_prompt="x", model="m"
|
||||
client, agent_name="Wizard", system_prompt="x", role="m"
|
||||
)
|
||||
assert route.call_count == 0
|
||||
|
||||
@@ -146,7 +146,7 @@ class TestDefineAgent:
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
with pytest.raises(AssertionError):
|
||||
await define_agent(
|
||||
client, agent_name="ab", system_prompt="x", model="m"
|
||||
client, agent_name="ab", system_prompt="x", role="m"
|
||||
)
|
||||
assert route.call_count == 0
|
||||
|
||||
@@ -159,7 +159,7 @@ class TestDefineAgent:
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
with pytest.raises(AssertionError):
|
||||
await define_agent(
|
||||
client, agent_name="wizard", system_prompt="", model="m"
|
||||
client, agent_name="wizard", system_prompt="", role="m"
|
||||
)
|
||||
assert route.call_count == 0
|
||||
|
||||
@@ -172,7 +172,7 @@ class TestDefineAgent:
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
with pytest.raises(SessionApiFailed) as exc:
|
||||
await define_agent(
|
||||
client, agent_name="wizard", system_prompt="x", model="m"
|
||||
client, agent_name="wizard", system_prompt="x", role="m"
|
||||
)
|
||||
assert exc.value.status == 503
|
||||
|
||||
@@ -196,16 +196,16 @@ class TestPatchAgent:
|
||||
client,
|
||||
"ratatoskr:wizard",
|
||||
system_prompt="new prompt",
|
||||
model="different-model",
|
||||
role="different-model",
|
||||
)
|
||||
body = _json.loads(route.calls[0].request.content)
|
||||
assert body == {"system_prompt": "new prompt", "model": "different-model"}
|
||||
assert body == {"system_prompt": "new prompt", "role": "different-model"}
|
||||
assert info.system_prompt == "new prompt"
|
||||
assert info.model == "different-model"
|
||||
|
||||
@respx.mock
|
||||
async def test_happy_patch_single_field(self) -> None:
|
||||
"""happy_patch_single_field: omit model → body has system_prompt only."""
|
||||
"""happy_patch_single_field: omit role → body has system_prompt only."""
|
||||
import json as _json
|
||||
|
||||
updated = {**_FULL_AGENT_RESP, "system_prompt": "only this"}
|
||||
@@ -349,7 +349,7 @@ class TestCli:
|
||||
"define",
|
||||
"--name", "wizard",
|
||||
"--system-prompt", "You are a wizard.",
|
||||
"--model", "qwen3.6-35-a3b",
|
||||
"--role", "qwen3.6-35-a3b",
|
||||
])
|
||||
out = capsys.readouterr()
|
||||
assert rc == 0
|
||||
@@ -429,7 +429,7 @@ class TestCli:
|
||||
"define",
|
||||
"--name", "wizard",
|
||||
"--system-prompt", "x",
|
||||
"--model", "m",
|
||||
"--role", "m",
|
||||
])
|
||||
err = capsys.readouterr().err
|
||||
assert rc == 11
|
||||
@@ -449,7 +449,7 @@ class TestCli:
|
||||
"define",
|
||||
"--name", "wizard",
|
||||
"--system-prompt", "x",
|
||||
"--model", "m",
|
||||
"--role", "m",
|
||||
])
|
||||
err = capsys.readouterr().err
|
||||
assert rc == 20
|
||||
@@ -473,7 +473,7 @@ class TestCli:
|
||||
"define",
|
||||
"--name", "wizard",
|
||||
"--system-prompt", "x",
|
||||
"--model", "m",
|
||||
"--role", "m",
|
||||
])
|
||||
err = capsys.readouterr().err
|
||||
assert rc == 20
|
||||
|
||||
Reference in New Issue
Block a user