Tier 3 agent lifecycle — ratatoskr.tier3 module + CLI #15

Closed
opened 2026-05-24 20:21:19 -07:00 by vh · 1 comment
Owner

Problem

Worldtree Phase 2.0 ships Tier 3 — consumer-defined agents owned by the caller's user_id, addressed as <user_id>:<agent_name>. The lifecycle endpoints are:

Method Path Purpose
POST /agents/define Create a Tier 3 agent.
DELETE /agents/<user_id>:<agent_name> Owner-initiated hard-delete.
PATCH /agents/<user_id>:<agent_name> Mutate system_prompt and/or model.

Ratatoskr's picker already handles Tier 3 agents generically per issue #8 (they appear in GET /agents if defined; the picker shows them like any other). What's missing: lifecycle management. Without a way to define / patch / delete Tier 3 agents from ratatoskr, the only Tier 3 flow operators can exercise is "use whatever Tier 3 agents already exist on the server" — which doesn't help observe how Tier 3 agents are HANDLED by the API.

Live verification against personal Worldtree (v0.16.2):

POST /agents/define {"agent_name":"smoke-test","system_prompt":"...","model":"qwen3.6-35-a3b"}
→ 201 {"agent_id":"ratatoskr:smoke-test", "user_id":"ratatoskr", ...}

DELETE /agents/ratatoskr:smoke-test
→ 204

Tier 3 is live; ratatoskr needs to expose it.

Solution

New module src/ratatoskr/tier3.py following the same caller-owned-AsyncClient posture as ratatoskr.sessions:

@dataclass(frozen=True)
class Tier3AgentInfo:
    agent_id: str          # "user_id:agent_name"
    user_id: str
    agent_name: str
    system_prompt: str
    model: str
    created_at: str
    updated_at: str

async def define_agent(
    client: httpx.AsyncClient, *,
    agent_name: str,
    system_prompt: str,
    model: str,
) -> Tier3AgentInfo: ...

async def patch_agent(
    client: httpx.AsyncClient, agent_id: str, *,
    system_prompt: str | None = None,
    model: str | None = None,
) -> Tier3AgentInfo: ...

async def delete_agent(client: httpx.AsyncClient, agent_id: str) -> None: ...

Errors mirror ratatoskr.sessions patterns — exceptions with [:1024] body truncation:

  • Tier3QuotaExceeded — 429 agent_quota_exceeded (50-agent cap per Heimdall key).
  • Tier3UserIdUnsupported — 403 tier3_user_id_unsupported (non-slug user_id).
  • Tier3FieldNotMutable — 422 field_not_mutable on PATCH with an immutable key.
  • Tier3LayerDeferred — 422 layer_deferred if persona / motivational / valence / memory carry non-null.
  • Tier3AgentNotFound — 404 (delete on non-existent / patch on non-existent).
  • SessionApiFailed (reused from sessions) — other non-2xx responses.

CLI entry point: python -m ratatoskr.tier3 <subcommand>:

python -m ratatoskr.tier3 define --name wizard --system-prompt "You are a guided-elicitation wizard..." --model qwen3.6-35-a3b
python -m ratatoskr.tier3 list                # filtered to <user_id>:* via GET /agents
python -m ratatoskr.tier3 patch ratatoskr:wizard --system-prompt "New prompt"
python -m ratatoskr.tier3 delete ratatoskr:wizard

Auth via the same env vars as the main ratatoskr CLI (WORLDTREE_API_URL, WORLDTREE_API_KEY). No new flags; share USER_AGENT from ratatoskr.cli.

Out of scope (this issue)

  • Tier 3 management UI inside the TUI — managing agents inline during a session is a different surface (deferred until empirical demand). Operators define agents via the CLI tool, then launch ratatoskr against them.
  • Bifrost binding parameter on Tier 3 session-create — issue #5 / #160 territory; if needed, file a follow-up.
  • Quota tracking / display — surface the 50-cap somewhere if agent_quota_exceeded becomes a frequent pain.
  • Layer-specific fields (persona / motivational / valence / memory) — Phase 2.0 ships baseline only; layer fields stay null. If a future Phase enables them, amend this module.
  • Cross-session-key cascade observability — issue #11 (AdminEvents pane) covers agents.cascade_delete event visibility.
  • The picker's "show user-owned tier-3 agents distinctly" — picker stays generic; agents with : in id show with their full id like any other.

Acceptance

  • Contract docs/contracts/issues/15.contract.md (new module) drift-checks clean.
  • New tests cover: happy-path define, patch (both fields, single field), delete, all error responses (quota, slug, layer_deferred, field_not_mutable, 404).
  • uv run ruff check src/ tests/ clean.
  • Live smoke against personal Worldtree:
    1. python -m ratatoskr.tier3 define --name smoke-1 --system-prompt "..." --model qwen3.6-35-a3b → 201 with agent_id ratatoskr:smoke-1
    2. ratatoskr --new --agent ratatoskr:smoke-1 → picker fetches GET /agents (the new tier-3 agent should appear in the list), or --new --agent ratatoskr:smoke-1 directly creates a session
    3. Send a message, verify the streaming flow handles the colon-containing agent_id transparently
    4. python -m ratatoskr.tier3 delete ratatoskr:smoke-1 → 204

Dependencies

  • Issue #4 (ratatoskr.tui) — landed; picker already accepts colon-containing agent_ids.
  • Issue #5 (--end-user-id) — landed; Tier 3 session-create requires end_user_id (same as lofn).
  • Spec pin: tier-3 fully spec'd in current docs/conversation-api-spec.md (v0.19.0 pin). No spec-pin refresh needed for this issue.
## Problem Worldtree Phase 2.0 ships Tier 3 — consumer-defined agents owned by the caller's `user_id`, addressed as `<user_id>:<agent_name>`. The lifecycle endpoints are: | Method | Path | Purpose | | -------- | --------------------------------- | ------------------------------------------ | | `POST` | `/agents/define` | Create a Tier 3 agent. | | `DELETE` | `/agents/<user_id>:<agent_name>` | Owner-initiated hard-delete. | | `PATCH` | `/agents/<user_id>:<agent_name>` | Mutate `system_prompt` and/or `model`. | Ratatoskr's picker already handles Tier 3 agents generically per issue #8 (they appear in `GET /agents` if defined; the picker shows them like any other). What's missing: **lifecycle management**. Without a way to define / patch / delete Tier 3 agents from ratatoskr, the only Tier 3 flow operators can exercise is "use whatever Tier 3 agents already exist on the server" — which doesn't help observe how Tier 3 agents are HANDLED by the API. Live verification against personal Worldtree (v0.16.2): ``` POST /agents/define {"agent_name":"smoke-test","system_prompt":"...","model":"qwen3.6-35-a3b"} → 201 {"agent_id":"ratatoskr:smoke-test", "user_id":"ratatoskr", ...} DELETE /agents/ratatoskr:smoke-test → 204 ``` Tier 3 is live; ratatoskr needs to expose it. ## Solution New module `src/ratatoskr/tier3.py` following the same caller-owned-AsyncClient posture as `ratatoskr.sessions`: ```python @dataclass(frozen=True) class Tier3AgentInfo: agent_id: str # "user_id:agent_name" user_id: str agent_name: str system_prompt: str model: str created_at: str updated_at: str async def define_agent( client: httpx.AsyncClient, *, agent_name: str, system_prompt: str, model: str, ) -> Tier3AgentInfo: ... async def patch_agent( client: httpx.AsyncClient, agent_id: str, *, system_prompt: str | None = None, model: str | None = None, ) -> Tier3AgentInfo: ... async def delete_agent(client: httpx.AsyncClient, agent_id: str) -> None: ... ``` Errors mirror `ratatoskr.sessions` patterns — exceptions with `[:1024]` body truncation: - `Tier3QuotaExceeded` — 429 `agent_quota_exceeded` (50-agent cap per Heimdall key). - `Tier3UserIdUnsupported` — 403 `tier3_user_id_unsupported` (non-slug user_id). - `Tier3FieldNotMutable` — 422 `field_not_mutable` on PATCH with an immutable key. - `Tier3LayerDeferred` — 422 `layer_deferred` if `persona` / `motivational` / `valence` / `memory` carry non-null. - `Tier3AgentNotFound` — 404 (delete on non-existent / patch on non-existent). - `SessionApiFailed` (reused from sessions) — other non-2xx responses. CLI entry point: `python -m ratatoskr.tier3 <subcommand>`: ``` python -m ratatoskr.tier3 define --name wizard --system-prompt "You are a guided-elicitation wizard..." --model qwen3.6-35-a3b python -m ratatoskr.tier3 list # filtered to <user_id>:* via GET /agents python -m ratatoskr.tier3 patch ratatoskr:wizard --system-prompt "New prompt" python -m ratatoskr.tier3 delete ratatoskr:wizard ``` Auth via the same env vars as the main ratatoskr CLI (`WORLDTREE_API_URL`, `WORLDTREE_API_KEY`). No new flags; share `USER_AGENT` from `ratatoskr.cli`. ## Out of scope (this issue) - **Tier 3 management UI inside the TUI** — managing agents inline during a session is a different surface (deferred until empirical demand). Operators define agents via the CLI tool, then launch ratatoskr against them. - **Bifrost binding parameter on Tier 3 session-create** — issue #5 / #160 territory; if needed, file a follow-up. - **Quota tracking / display** — surface the 50-cap somewhere if `agent_quota_exceeded` becomes a frequent pain. - **Layer-specific fields** (persona / motivational / valence / memory) — Phase 2.0 ships baseline only; layer fields stay `null`. If a future Phase enables them, amend this module. - **Cross-session-key cascade observability** — issue #11 (AdminEvents pane) covers `agents.cascade_delete` event visibility. - **The picker's "show user-owned tier-3 agents distinctly"** — picker stays generic; agents with `:` in id show with their full id like any other. ## Acceptance - Contract `docs/contracts/issues/15.contract.md` (new module) drift-checks clean. - New tests cover: happy-path define, patch (both fields, single field), delete, all error responses (quota, slug, layer_deferred, field_not_mutable, 404). - `uv run ruff check src/ tests/` clean. - Live smoke against personal Worldtree: 1. `python -m ratatoskr.tier3 define --name smoke-1 --system-prompt "..." --model qwen3.6-35-a3b` → 201 with agent_id `ratatoskr:smoke-1` 2. `ratatoskr --new --agent ratatoskr:smoke-1` → picker fetches GET /agents (the new tier-3 agent should appear in the list), or `--new --agent ratatoskr:smoke-1` directly creates a session 3. Send a message, verify the streaming flow handles the colon-containing agent_id transparently 4. `python -m ratatoskr.tier3 delete ratatoskr:smoke-1` → 204 ## Dependencies - Issue #4 (`ratatoskr.tui`) — landed; picker already accepts colon-containing agent_ids. - Issue #5 (`--end-user-id`) — landed; Tier 3 session-create requires `end_user_id` (same as lofn). - Spec pin: tier-3 fully spec'd in current `docs/conversation-api-spec.md` (v0.19.0 pin). No spec-pin refresh needed for this issue.
vh added the enhancementtask labels 2026-05-24 20:21:19 -07:00
Author
Owner

Closed — shipped. ratatoskr.tier3 module + CLI implemented; tier3.py exposes define/patch/delete subcommands following the caller-owned-AsyncClient posture. Actively used to manage the Sindra Tier 3 agent (model migration + FORM ASSUMPTION patches). Tests in test_tier3.py + test_local_agents.py.

Closed — shipped. ratatoskr.tier3 module + CLI implemented; tier3.py exposes define/patch/delete subcommands following the caller-owned-AsyncClient posture. Actively used to manage the Sindra Tier 3 agent (model migration + FORM ASSUMPTION patches). Tests in test_tier3.py + test_local_agents.py.
vh closed this issue 2026-05-29 23:27:33 -07:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vh/ratatoskr#15