Files
ss-tools/specs/035-agent-chat-context/tasks.md
busya ff60865183 feat(agent-chat): 035-agent-chat-context — контекст, guardrails, tools, database discovery
== User stories ==
US1: Контекст с дашборда/датасета → /agent с URL params
US2: Guardrails card — env badge, 7 risk tones, countdown, permission_denied
US3: Tools optimization — retry, timeout, summarise, RBAC + context affinity

== Backend ==
- _context.py (NEW): UIContext validation (7 checks)
- _tool_filter.py (NEW): RBAC + context affinity pipeline
- _confirmation.py: build_confirmation_contract_v2, permission_denied_payload
- tools.py: superset_list_databases, retry/summarise/timeout wrappers
- app.py: _inject_uicontext, _inject_env_id_into_tools, database
  prefetch в runtime context
- _persistence.py: prefetch_databases()
- agent_superset_explore.py: GET /databases endpoint
- _llm_async_http.py, _persistence.py: fix double /v1 в LLM URL
  (LM Studio Unexpected endpoint)

== Frontend ==
- AgentChatModel.svelte.ts: 5 atoms, 3 actions, countdown, context
- AgentChat.svelte: production banner, process steps, debug panel
- ConfirmationCard.svelte: 7 risk tones, permission_denied, countdown
- ToolCallCard.svelte: retrying/timeout/cancelled states
- StreamProcessor.svelte.ts: tool_retry, timeout, permission_denied
- TopNavbar: sparkles icon + Ассистент
- sidebarNavigation: AI section
- DashboardHeader, datasets/+page: contextual AI buttons
- Icon: sparkles, brain, cpu icons
- tailwind: assistant category colors
- i18n: en/ru nav keys

== Tests ==
- 159 backend agent tests (+16 US3: retry, timeout, summarise, contracts)
- 2544 frontend tests (+11 model + component tests)
- 15 JSON fixtures (10 API + 5 model)

== Specs ==
- specs/035-agent-chat-context/: spec, UX, plan, tasks, research,
  data-model, contracts, quickstart, traceability, fixtures, checklists

Closes #035
2026-07-04 22:47:17 +03:00

14 KiB

#region Tasks [C:3] [TYPE ADR] [SEMANTICS tasks,implementation,agent-chat] @defgroup Tasks Implementation tasks for 035-agent-chat-context v2 — Path B model with /agent page, two-layer RBAC, permission_denied flow, and structured truncation.

Input: Design documents from /specs/035-agent-chat-context/ Prerequisites: plan.md , spec.md , research.md , data-model.md , contracts/modules.md

Format: [ID] [P?] [Story] Description


Phase 1: Setup

  • T001 [P] Materialize API fixtures from specs/035-agent-chat-context/fixtures/api/ into backend/tests/fixtures/agent/
  • T002 [P] Materialize model fixtures from specs/035-agent-chat-context/fixtures/model/ into frontend/src/lib/models/__fixtures__/agent/
  • T003 Verify toolchain: cd backend && source .venv/bin/activate && python -m pytest --co -q, cd frontend && npm run test -- --run

Phase 2: Foundational

  • T004 [P] Define UIContext, ConfirmMetadataV2, PermissionDeniedMetadata, extended ToolCall in frontend/src/lib/models/AgentChatTypes.ts @POST: UIContext with objectType, objectId, objectName(string|null max 256), envId, route, contextVersion(1) @POST: PermissionDeniedMetadata {type, tool_name, required_role, user_role, alternatives} @POST: ToolCallStatus extended with "retrying"|"timeout"|"cancelled"; ToolCall +isWriteTool?

  • T005 [P] Create backend/src/agent/_context.py — UIContext validation @POST: validate_uicontext(payload) → validated dict or ValidationError @TEST_EDGE: valid→returns, invalid_objectType→422, oversized>4KB→413, objectName>256→422

  • T006 Create backend/src/agent/_tool_filter.py — tool selection pipeline + invocation guard @POST: build_tool_pipeline(tools, user_role, object_type) → filtered list. Order: RBAC→context. @POST: enforce_tool_permission(tool_name, user_role) → allowed or yield permission_denied SSE @POST: _CONTEXT_TOOL_AFFINITY dict (dashboard 9, dataset 8, migration 5 tools) @POST: _TOOL_PERMISSIONS dict (7 admin-only tools) @RATIONALE: Ordered pipeline. Two-layer enforcement per AGTL-FR-005. @REJECTED: Embedding-first routing — postponed to post-MVP (AGTL-FR-006).


Phase 3: User Story 1 — Context-Aware Agent (P1)

Goal: Agent on /agent page receives context from URL params, filters tools by objectType.

Tests for US1

  • T007 [P] [US1] L1 model test for setUIContextFromParams + contextPillLabel in frontend/src/lib/models/__tests__/AgentChatModel.context.test.ts @TEST_FIXTURE: full_params → uiContext populated, pill="📋 dashboard #42 · ss-dev" @TEST_FIXTURE: no_params → uiContext null, pill=" Контекст не выбран" @TEST_FIXTURE: malformed_objectType → treated as null

  • T008 [P] [US1] Contract test for agent_handler uicontext in backend/tests/test_agent_context.py @TEST_FIXTURE: null→22 tools, dashboard→9 tools, invalid_json→no error+audit log, malformed_objectType→422

  • T009 [P] [US1] Contract test for build_tool_pipeline in backend/tests/test_agent_tool_filter.py @TEST_FIXTURE: null→all RBAC-allowed, dashboard+analyst→dashboard tools minus admin, unknown→graceful fallback

Implementation for US1

  • T010 [US1] Implement setUIContextFromParams + contextPillLabel in frontend/src/lib/models/AgentChatModel.svelte.ts @ACTION: parse URLSearchParams, validate objectType enum, set uiContext with contextVersion=1 @POST: contextPillLabel derived: icon+type+id+env or " Контекст не выбран"

  • T011 [US1] Implement onMount context init in frontend/src/routes/agent/+page.svelte @RELATION BINDS_TO -> [AgentChat.Model] import { page } from "$app/stores"; onMount: URLSearchParams($page.url.search) → setUIContextFromParams

  • T012 [US1] Implement process steps update in frontend/src/lib/components/agent/AgentChat.svelte First step label uses model.contextPillLabel. Remaining steps unchanged. @RELATION BINDS_TO -> [AgentChat.Model]

  • T013 [US1] Implement _inject_uicontext in backend/src/agent/app.py @POST: Appends [USER CONTEXT — informational] block. Explicitly marked "not instructions" for prompt-injection protection.

  • T014 [US1] Implement agent_handler uicontext param in backend/src/agent/app.py @PRE: uicontext_str at position 6 (appended, default None). Validated via validate_uicontext. @POST: injected into runtime context. Tools filtered via build_tool_pipeline. @SIDE_EFFECT: UIContext logged via logger.reason. Validation failures logged via logger.explore. RATIONALE: Appended at end — backward-compat for existing 6-arg Gradio clients.

  • T015 [P] [US1] Implement isProdContext + showProdWarning + prod banner in frontend/src/lib/models/AgentChatModel.svelte.ts + AgentChat.svelte isProdContext = $derived((uiContext?.envId?.toLowerCase().includes("prod")) || _activeEnvId?.toLowerCase().includes("prod")) Prod banner using <Icon name="warning"> from $lib/ui (existing). bg-warning-light background. @UX_STATE: prod_env→bg-warning-light+banner; non_prod→standard

  • T016 [US1] Implement updateActiveEnv in frontend/src/lib/models/AgentChatModel.svelte.ts @ACTION: syncs _activeEnvId AND uiContext.envId to prevent silent desync @POST: both atoms updated. isProdContext $derived recomputes.

Verification for US1

  • T017 [US1] Run backend tests: cd backend && source .venv/bin/activate && python -m pytest tests/test_agent_context.py tests/test_agent_tool_filter.py -v
  • T018 [US1] Run frontend tests: cd frontend && npm run test -- --run frontend/src/lib/models/__tests__/AgentChatModel.context.test.ts
  • T019 [US1] E2E: navigate /dashboards/10?env_id=ss-dev → click Agent → /agent?objectType=dashboard&objectId=10&envId=ss-dev → pill shows dashboard context

Phase 4: User Story 2 — Guardrails Refinement (P2)

Goal: Confirmation card with env badge, risk toning, 10s model-owned countdown, permission_denied as separate SSE event.

Tests for US2

  • T020 [P] [US2] Contract test for build_confirmation_contract_v2 + permission denied in backend/tests/test_agent_confirmation_v2.py @TEST_FIXTURE: deploy_prod→guarded+prod, delete_any→dangerous, read_only→safe @TEST_FIXTURE: analyst_deploy→permission_denied SSE (NOT confirm_required) @TEST_FIXTURE: env_resolution: tool_args.prod > submit.staging > uiContext.dev

  • T021 [P] [US2] L2 UX test for ConfirmationCard 7+1 states in frontend/src/lib/components/assistant/__tests__/ConfirmationCard.ux.test.ts Verify: read→success, write_staging→warning+badge, write_prod→destructive+PROD, write_dev→info Verify: dangerous→destructive+model-owned countdown, permission_denied→muted+lock (separate event)

Implementation for US2

  • T022 [US2] Implement build_confirmation_contract_v2 in backend/src/agent/_confirmation.py @POST: three-axis risk. Env resolution: tool_args.env_id>submit envId>UIContext.envId>null. Env normalization via substring match. @DATA_CONTRACT: (tool_name, tool_args, user_role, target_env)→ConfirmMetadataV2 RATIONALE: Three-axis replaces prefix heuristic; env resolution prevents guardrail misclassification.

  • T023 [US2] Implement yield_permission_denied in backend/src/agent/_confirmation.py @POST: Yields SSE type="permission_denied" (NOT confirm_required). Bypasses HITL checkpoint. RATIONALE: Security — forbidden calls must not enter guarded checkpoint.

  • T024 [US2] Implement enforce_tool_permission invocation guard in backend/src/agent/_tool_filter.py @POST: Called before every tool execution. Checks user_role vs _TOOL_PERMISSIONS. Denied→yield permission_denied. @RATIONALE: Two-layer enforcement — catches hallucinated/replayed/direct calls bypassing prompt filter.

  • T025 [US2] Implement ConfirmationCard env badge + 7 risk tones in frontend/src/lib/components/assistant/ConfirmationCard.svelte Uses <Button> from $lib/ui (existing), <Icon> from $lib/ui (existing) @UX_STATE: 7 states (read, write_dev, write_staging, write_prod, dangerous, loading_confirm, loading_deny)

  • T026 [US2] Implement model-owned dangerous countdown in frontend/src/lib/models/AgentChatModel.svelte.ts startDangerousCountdown(): setInterval 1s decrement on this.dangerousCountdown. Component reads model atom only. @RATIONALE: Single source of truth — eliminates model/component timer race condition.

  • T027 [US2] Implement dangerous countdown UI in frontend/src/lib/components/assistant/ConfirmationCard.svelte Button disabled while model.dangerousCountdown>0. Text: "💀 Удалить (N)". aria-live="assertive" every 1s. Reads model.dangerousCountdown — no component timer.

  • T028 [US2] Implement permission_denied handler in frontend/src/lib/models/AgentChat.StreamProcessor.svelte.ts case "permission_denied": render card with 🔒, required_role, alternatives. "Закрыть" only.

  • T029 [US2] Implement cancelled tool card for deny flow in frontend/src/lib/models/AgentChat.StreamProcessor.svelte.ts + ToolCallCard.svelte confirm_resolved deny → push ToolCall status="cancelled". Also yield agent message "⏹️ Операция отменена".

Verification for US2

  • T030 [US2] Run backend tests: cd backend && source .venv/bin/activate && python -m pytest tests/test_agent_confirmation_v2.py -v
  • T031 [US2] Run frontend tests: cd frontend && npm run test -- --run frontend/src/lib/components/assistant/__tests__/ConfirmationCard.ux.test.ts
  • T032 [US2] Manual: deploy to prod→destructive+PROD badge. delete→10s countdown. analyst→permission_denied (no confirm button)

Phase 5: User Story 3 — Tool Optimisation (P3)

Goal: Fixed-delay retry, structured truncation, configurable timeout (write-safe), invocation RBAC guard.

Tests for US3

  • T033 [P] [US3] Contract test for _retry_read_tool in backend/tests/test_agent_retry.py @TEST_FIXTURE: first_502→retries+success, both_502→exhausted, write_tool→no retry

  • T034 [P] [US3] Contract test for _summarise_response in backend/tests/test_agent_summarise.py array 50→"Found 50...+45 more", object→keys+sample, text→sentence-boundary truncation, short→unchanged

  • T035 [P] [US3] Contract test for _execute_with_timeout in backend/tests/test_agent_timeout.py @TEST_EDGE: complete_under→normal, read_exceed→retryable timeout, write_exceed→retryable=false status_unknown

  • T036 [P] [US3] L2 UX test for ToolCallCard states in frontend/src/lib/components/assistant/__tests__/ToolCallCard.ux.test.ts retrying→spinner+attempt, read_timeout→⏱️+retry button, write_timeout→⏱️+"check status" (no retry), cancelled→🚫

Implementation for US3

  • T037 [US3] Implement _retry_read_tool in backend/src/agent/tools.py @PRE: tool risk_level="safe". Error 5xx/ConnectError. Fixed 1s asyncio.sleep. @POST: retry once. Yields tool_retry SSE. Exhaust→raises retry_exhausted. RATIONALE: Single fixed retry catches ~80% transient failures. Read-only only.

  • T038 [US3] Implement _summarise_response in backend/src/agent/tools.py @POST: >4000 chars + JSON array→"Found N: -item1...+ (N-5) more". Object→keys+sample. Text→sentence-boundary cut. RATIONALE: Structured truncation preserves context better than hard cut.

  • T039 [US3] Implement _execute_with_timeout in backend/src/agent/tools.py @PRE: asyncio.wait_for(tool_fn, 30s). is_write param. @POST: timeout→tool_timeout SSE. Write: is_write_tool=true, retryable=false. RATIONALE: Write tool timeout ≠ mutation didn't happen. No retry — check status instead.

  • T040 [US3] Implement tool_retry + tool_timeout handlers in frontend/src/lib/models/AgentChat.StreamProcessor.svelte.ts case "tool_retry": ToolCall status="retrying". case "tool_timeout": status="timeout", isWriteTool→no retry button.

  • T041 [US3] Implement retry+timeout states in frontend/src/lib/components/assistant/ToolCallCard.svelte retrying: "Повторная попытка... ({N}/{M})". Read timeout: ⏱️+retry btn. Write timeout: ⏱️+"Operation status unknown" (no btn). Uses <Button variant="secondary" size="xs"> from $lib/ui (existing).

  • T042 [US3] Implement debug panel context sections in frontend/src/lib/components/agent/AgentChat.svelte Add "Контекст страницы" (JSON.stringify uiContext) + "Pipeline" (effective tool list after filtering) sections to debug panel. Tool list data comes from backend SSE debug event (pipeline_result) — NOT frontend reconstruction.

Verification for US3

  • T043 [US3] Run backend tests: cd backend && source .venv/bin/activate && python -m pytest tests/test_agent_retry.py tests/test_agent_summarise.py tests/test_agent_timeout.py -v
  • T044 [US3] Run frontend tests: cd frontend && npm run test -- --run frontend/src/lib/components/assistant/__tests__/ToolCallCard.ux.test.ts
  • T045 [US3] Manual: 502→auto-retry→success. Large→top-5. Analyst→write tools excluded+invocation guard active.

Phase 6: Polish & Cross-Cutting

  • T046 [P] Full backend: cd backend && source .venv/bin/activate && python -m pytest -v
  • T047 [P] Full frontend: cd frontend && npm run test
  • T048 [P] Backend lint: cd backend && python -m ruff check .
  • T049 [P] Frontend lint: cd frontend && npm run lint
  • T050 [P] Frontend build: cd frontend && npm run build
  • T051 Attention compliance audit — verify ATTN_1-4 on all new/changed contracts
  • T052 E2E verification per quickstart.md
  • T053 Run quickstart validation

Dependencies

Phase 1 → Phase 2 → US1(P1) → US2(P2) ∥ US3(P3) → Phase 6

US2 and US3 can proceed in parallel after US1 base.

Parallel Opportunities

Phase 1: T001∥T002 | Phase 2: T004∥T005∥T006 | US1: T007∥T008∥T009, T015∥(T010→T011) | US2: T020∥T021 | US3: T033∥T034∥T035∥T036 | Phase 6: T046∥T047∥T048∥T049∥T050

Notes

  • Gradio uicontext at position END (6), not middle — backward-compat
  • Permission denied: SSE type="permission_denied", bypasses HITL
  • Countdown: model-owned, component reads only
  • Write timeout: no retry
  • No initialContext in store — context from URL params only
  • RBAC: two-layer (prompt filter + invocation guard)

#endregion Tasks