Skip to main content

threadlight-hitl-patterns

Generate Teams Adaptive Card flows + bot UX components for the seven canonical action gates (approve, edit-and-approve, reject, escalate, signoff, audit-view, request-info) declared in spec § 8 Human Interaction Points. Pairs with foundry-teams-bot for delivery. USE FOR: human-in-the-loop, approval cards, Teams Adaptive Cards for agent decisions, action gate UX, edit-and-approve flow, escalation card, signoff flow, threadlight HITL, add gate to Kratos export. DO NOT USE FOR: bot infrastructure (use foundry-teams-bot), workspace UI (use threadlight-workspace-ui), agent runtime logic (use threadlight-deploy).

Zur Installation springen

Quellinformationen

Repository
aiappsgbb/threadlight-skills
Letzte Quellaktivität
16. September 2026 um 08:20
Erkannte Sprache von SKILL.md
Englisch
Sterne
1
Forks
5

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
11 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
threadlight-hitl-patterns
description
Generate Teams Adaptive Card flows + bot UX components for the seven canonical action gates (approve, edit-and-approve, reject, escalate, signoff, audit-view, request-info) declared in spec § 8 Human Interaction Points. Pairs with foundry-teams-bot for delivery. USE FOR: human-in-the-loop, approval cards, Teams Adaptive Cards for agent decisions, action gate UX, edit-and-approve flow, escalation card, signoff flow, threadlight HITL, add gate to Kratos export. DO NOT USE FOR: bot infrastructure (use foundry-teams-bot), workspace UI (use threadlight-workspace-ui), agent runtime logic (use threadlight-deploy).
metadata
{"version":"1.2.0"}
# Threadlight HITL Patterns Generate **Teams Adaptive Cards** + bot integration for the seven canonical action gates declared in `specs/SPEC.md` § 8 Human Interaction Points. > **Why a separate skill from `foundry-teams-bot`?** `foundry-teams-bot` > handles the bot **infrastructure** (manifest, ACA, UAMI, MsalConnectionManager, > messaging extension routing). This skill handles the **gate UX** — the > Adaptive Card content, the Action.Submit handlers, the audit-trail wiring. > One bot, many gates; the bot doesn't know what the gates mean. Before generation, select framework, enforcement path, channel and review window against the [runtime support matrix](../../docs/runtime-support.md). A Teams UI does not supply native resume or delegated authority. Do not choose a deferred GitHub Copilot SDK flow and discover its unsupported resume seam only at deploy. ## When to Use - Process spec § 8 declares one or more action gates - Process needs human approval/escalation/signoff in Teams - Edit-and-approve flow (operator can amend the agent's proposal before approving) ## When NOT to Use - Process is fully autonomous (no § 8) - Operator works in a workspace UI only (use `threadlight-workspace-ui`); but note that a workspace can still embed action gates locally --- ## Input contract / Output artifacts **Input contract**: - `specs/SPEC.md` § 8 — for each interaction: - `Action gate`: one of the seven canonical gates (see below) - `Linked business rules` (BR-XXX list) - `Data Presented`: which fields the human sees - `Options`: what actions the human can take - `Timeout/SLA`: how long before escalation - `specs/SPEC.md` § 4 — entity field schemas (for the card data binding) - `AGENTS.md` — for the agent identity that calls the gate **Output**: ``` <skills-root>/{skill-using-gate}/cards/ ├── {gate-name}.json # Adaptive Card template └── {gate-name}-handler.py # Action.Submit response handler src/bot/cards/ ├── card_router.py # Routes incoming Action.Submit to the right handler ├── audit_trail.py # Writes gate outcomes to Cosmos (or AppInsights) └── card_registry.json # Map of card name → handler module ``` > **Skills root + Kratos-export mode.** `<skills-root>` resolves per the > [`docs/KRATOS-BRIDGE.md`](../../docs/KRATOS-BRIDGE.md) convention: > `use-cases/<x>/skills/` for a **Kratos-exported project** (`src/hosted-agent/` > + `use-cases/<x>/`), otherwise `src/agent/skills/` (design mode). Override with > `--skills-root <path>`. In Kratos-export mode the gate scaffold is written > **next to the existing use-case skills** so it travels with the same bundle. > If the export has no `specs/SPEC.md` § 8, take the gate type + linked fields > from the operator (or infer from `use-cases/<x>/SYSTEM_PROMPT.md`) instead of > failing on the missing SPEC. --- ## The seven canonical gates Aligned with the action-gate taxonomy in `threadlight-design` SPEC § 8. ### 1. `approve` — yes/no **When**: agent proposes a low-risk action; human confirms. **Card shape**: Title + summary card + two buttons (`Approve` / `Decline`). ```jsonc { "type": "AdaptiveCard", "version": "1.5", "body": [ {"type": "TextBlock", "text": "${title}", "size": "large", "weight": "bolder"}, {"type": "FactSet", "facts": "${summaryFacts}"}, {"type": "TextBlock", "text": "Linked rules: ${linkedRules}", "isSubtle": true, "size": "small"} ], "actions": [ {"type": "Action.Submit", "title": "Approve", "data": {"gate": "approve", "decision": "approved", "case_id": "${caseId}", "review_id": "${reviewId}"}, "style": "positive"}, {"type": "Action.Submit", "title": "Decline", "data": {"gate": "approve", "decision": "declined", "case_id": "${caseId}", "review_id": "${reviewId}"}, "style": "destructive"} ] } ``` **Audit fields written**: `gate=approve`, `decision`, `case_id`, `actor`, `timestamp`, `linked_rules`, `agent_proposal_summary`. ### 2. `edit-and-approve` — amend before commit **When**: agent's proposal is mostly right but the human may want to tweak fields before committing. **Card shape**: editable Input fields prefilled with the proposal + `Review amended proposal` and `Cancel`. Changed arguments require a new intent and a fresh decision; the original grant must never authorize the edit. ```jsonc { "type": "AdaptiveCard", "version": "1.5", "body": [ {"type": "TextBlock", "text": "${title}", "size": "large", "weight": "bolder"}, {"type": "Input.Text", "id": "field1", "label": "Field 1", "value": "${proposed.field1}"}, {"type": "Input.ChoiceSet", "id": "field2", "label": "Field 2", "value": "${proposed.field2}", "choices": "${field2Choices}"}, {"type": "Input.Text", "id": "rationale", "label": "Why edit?", "isMultiline": true} ], "actions": [ {"type": "Action.Submit", "title": "Review amended proposal", "data": {"gate": "edit-and-approve", "decision": "review_edits", "case_id": "${caseId}", "review_id": "${reviewId}"}, "style": "positive"}, {"type": "Action.Submit", "title": "Cancel", "associatedInputs": "none", "data": {"gate": "edit-and-approve", "decision": "cancelled", "case_id": "${caseId}", "review_id": "${reviewId}"}} ] } ``` **Audit fields**: includes `proposed_diff` (the delta between agent proposal and human-edited values) for traceability. ### 3. `reject` — refuse with reason **When**: human declines and must record why (regulatory or quality reasons). **Card shape**: reason picker (ChoiceSet from the linked rules) + free-text + single `Reject` button. ### 4. `escalate` — route to higher authority **When**: case exceeds the human's authority; routes to a queue or named role. **Card shape**: role/queue picker + reason + `Escalate` button. After submit, post a NEW card to the escalation target. ### 5. `signoff` — attest review (no veto) **When**: regulator requires human attestation but human has no veto power (read-and-acknowledge). **Card shape**: full case detail + `I have reviewed and acknowledge` single button. Records signature trail (actor + timestamp + content hash). ### 6. `audit-view` — read-only inspection **When**: human (auditor, compliance) inspects a case without taking action. **Card shape**: full case detail with NO action buttons. Generates an audit event ("viewed by X at T") for compliance. ### 7. `request-info` — ask for more data **When**: agent can't proceed without more input from the customer or an external party. **Card shape**: templated message composer (subject + body, optionally with attachment slots). Submit posts the templated message via the configured channel (Teams chat, email via Logic App, etc.). --- ## Generation procedure ### Step 1: Walk spec § 8 For each interaction: ```python gate = interaction["action_gate"] linked_rules = interaction["linked_business_rules"] fields = interaction["data_presented"] sla = interaction["timeout_sla"] ``` ### Step 2: Generate the card template - Pick the canonical card shape from this skill's `references/cards/{gate}.json` - Substitute `${title}`, `${summary}`, `${linkedRules}`, etc. with server-owned review data - Bind `reviewId` to the exact pending intent nonce and `caseId` to its authoritative case - For `edit-and-approve`: generate Input fields from the entity schema in spec § 4 - For `escalate`: derive the role list from the AGENTS.md skill actor table ### Step 3: Bind the handler to actual authority For governed approve/reject actions, copy the executable [`control_plane_review.py`](references/handlers/control_plane_review.py) bridge and use the packaged `govern_control_plane.review` client. The bridge reloads a server-owned pending review, checks case/nonce/freshness, and calls the actual delegated decision protocol. It **never writes the business case** or starts a new agent session. A successful result says `execution: not-started`. The bot integration supplies `load_review(review_id) -> (case_id, pending)` from its protected review store and a real delegated `AsyncTokenCredential`. The configured control-plane URL, scope and approving role are server-owned inputs. Do not derive authority from `activity.from`, Easy Auth headers, card fields or the bot's app-only credential. Verified Teams SSO/OBO, bot delivery and consent remain application/platform integration requirements; missing integration is a blocker, not a reason to invent a token or grant. Generate the bot's `handle(turn_context, activity, value)` wrapper to call `decide_submission(value, ...)` with those trusted dependencies, then render the recorded decision. Resume only the original operation through the supported native host adapter; the authority is consumed there, once, before its effect. Surface a rejected/expired/replayed decision or unavailable service explicitly. Other gates need different handlers. Edits create a **new intent** and fresh review; escalation validates the destination; signoff records acknowledgement; audit-view requires audit access; request-info needs a separately authorized message action. The bridge rejects these gates rather than mapping them all to `approved=true`. The common bot routing signature does not make their business semantics interchangeable. ### Step 4: Wire into the bot Update `src/bot/cards/card_router.py` to map gate names to handlers: ```python import importlib import logging log = logging.getLogger(__name__) HANDLERS = { "approve": "skills.kyc_decision.cards.approve_handler", "edit-and-approve": "skills.kyc_decision.cards.edit_and_approve_handler", "reject": "skills.kyc_decision.cards.reject_handler", "escalate": "skills.kyc_decision.cards.escalate_handler", "signoff": "skills.kyc_decision.cards.signoff_handler", "audit-view": "skills.kyc_decision.cards.audit_view_handler", "request-info": "skills.kyc_decision.cards.request_info_handler", } async def route(turn_context, activity): value = activity.value or {} gate = value.get("gate") if not gate or gate not in HANDLERS: return error_card("unknown-gate", details=f"Got {gate!r}") try: handler = importlib.import_module(HANDLERS[gate]).handle except (ImportError, AttributeError) as e: log.exception("Handler import failed for gate=%s", gate) return error_card("handler-unavailable", details=str(e)) return await handler(turn_context, activity, value) ``` `error_card(code, details=None)` is a small helper that builds an Adaptive Card with a short red-banner error message and a "Try again" button — define it once in `src/bot/cards/_error.py` and import. ### Step 5: Generate the audit-trail writer `src/bot/cards/audit_trail.py` records UI interactions under the application's data-retention policy. This is not the immutable governance receipt or the business decision audit, and must not be used as proof that an effect occurred. Record the authority's decision reference and authenticated subject rather than treating display names as identity. The UI record has this application-specific shape: ```json { "id": "audit-{case_id}-{gate}-{activity_id}", "case_id": "...", "gate": "approve | edit-and-approve | reject | escalate | signoff | audit-view | request-info", "decision": "approved | declined | cancelled | escalated_to | acknowledged | viewed | requested", "actor": {"upn": "...", "displayName": "..."}, "timestamp": "ISO 8601", "linked_rules": ["BR-001", "BR-007"], "agent_proposal": {...}, "human_edits": {...}, // only for edit-and-approve "rationale": "..." // only for reject / edit-and-approve } ``` > **Deterministic id, not `uuid4`.** The `id` is composed from the case > id, the gate name, and the source `activity.id` from Bot Framework. > This makes the write **idempotent** under Bot Framework retries (Teams > is at-least-once for outgoing card submissions when the bot times out) > — a duplicate Action.Submit collapses into a single audit row instead > of double-booking the case. Pair with `upsert_item` not `create_item`. **Keyless Cosmos pattern (mandatory for threadlight pilots):** ```python # src/bot/cards/audit_trail.py import os from azure.cosmos.aio import CosmosClient from azure.identity.aio import DefaultAzureCredential # Module-level singletons — created once at bot startup, reused for the # bot's lifetime. Re-creating CosmosClient per request is a known cause # of socket exhaustion under load (Cosmos SDK opens up to 100 sockets
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen