Feedback Loop & Skill Suggestions
Skill suggestions are part of the Conversational Agents Beta.
Ratings on agent answers (Answer ratings & feedback) feed two review surfaces:
| What was wrong | Where it is fixed | Where you decide |
|---|---|---|
| A fact: price, opening hours, product detail | Knowledge base: a new or updated FAQ entry | Knowledge Base → Corrections |
| A policy: tone, a promise the agent must not make, a rule it ignored | The agent's skill instructions: a behaviour rule | The agent's Skill suggestions |
Nothing in this loop changes your agent without a human approving it.
Fact vs policy corrections​
Every Wrong rating, and every 👎 with a reason, opens a correction. The correction analyser reads the report, the answer and its sources. In the same step (no extra charge) it classifies the correction as:
fact: proposed as FAQ changes (proposed_changes), exactly as before.policy: the analyser writes one short behaviour rule (policy_rule) instead of FAQ changes.
The reasons wrong_tone ("Wrong tone / behaviour") and policy_violation are a strong hint for policy. The analyser can still decide fact when the report clearly names a wrong fact.
Where a policy rule goes:
| Answer came from | Result | Decided in |
|---|---|---|
| A Conversational Agent | The rule is added to an open policy_rule skill suggestion on the skill that caused it. If no single skill fits, it goes to the agent's foundation skill. | The agent's Skill suggestions. Approving the correction in the Corrections queue returns 409 routed_to_skill_suggestion. |
| A FAQ Bot | One proposed change, append_bot_rule, which adds the rule to the bot's custom instructions under "Rules from reviewed feedback:" | Corrections queue (approve or reject) |
| Neither can be resolved | No change. policy_note explains that the correction needs manual handling. | Corrections queue (reject when handled) |
Approving a correction (POST /api/v1/kb-corrections/{id}/approve) needs knowledge_base:manage. If any change you are applying is an append_bot_rule, it changes the bot's instructions, so the caller also needs automation:update; otherwise the request returns 403 with detail.code missing_scope and nothing is applied.
Rejecting a correction (POST /api/v1/kb-corrections/{id}/reject, body {"reason": "optional"}) never changes the knowledge base. If it is a policy correction that was routed to a skill suggestion that is still proposed, its rule is removed from that suggestion. The suggestion's rule_text, proposed_instructions, diff, source_correction_ids, source_rating_ids and rating_count are recalculated. A rule that another open correction on the same suggestion also proposed is kept. If no rule is left, the suggestion becomes dismissed with decision_note "All source corrections rejected". The answer rating itself is kept and still counts in the Feedback stats.
Errors from approve and reject always have a detail.code:
| Status | detail.code | When |
|---|---|---|
404 | correction_not_found | No such correction in this workspace |
409 | already_applied | Approve or reject: the correction was already applied (revert its revisions instead) |
409 | already_rejected | Approve: the correction was already rejected |
409 | analysis_pending | Approve: the analysis has not produced proposed changes yet |
409 | routed_to_skill_suggestion | Approve: the policy rule is decided on the agent's skill suggestion (skill_suggestion_id is included) |
403 | missing_scope | Approve: an append_bot_rule change needs automation:update |
500 / 503 | apply_failed / apply_unavailable | Approve: the changes could not be applied |
Rating the same answer again while its correction is still open, analyzing or proposed updates that correction's report text. It does not open a second correction or start a second analysis.
Policy corrections are never applied automatically, even with automatic correction mode. A FAQ Bot rule is not applied if the instructions would go over 15,000 characters. In that case the change stays in the queue with a blocked_reason.
The foundation skill is the agent's first always on skill (by skill order). This is the skill the agent builder creates first. An agent with no always-on skill and exactly one skill uses that skill.
Correction fields​
GET /api/v1/kb-corrections and GET /api/v1/kb-corrections/{id} (scope knowledge_base:read) include:
| Field | Notes |
|---|---|
correction_kind | fact, policy, or null for corrections made before classification (treat as fact) |
policy_rule | The proposed behaviour rule (policy only) |
skill_suggestion_id | Set when the rule was routed to a skill suggestion |
policy_target | Policy only: {"type": "skill" | "bot" | null, "id", "name", "agent_id"}. agent_id is set for skills, so you can link to the agent editor. |
policy_note | Set when a policy correction has nowhere to apply |
proposed_changes[].action can now also be append_bot_rule (target_type: "bot").
Skill suggestions​
A skill suggestion is a proposed new text for one skill, shown as a diff against the version it is based on. There are two kinds:
kind | Created | Cost |
|---|---|---|
improvement | On demand, when you ask for one (Suggest improvements on a skill). The AI reads the recent Wrong and 👎-with-reason ratings on answers that used this skill's current version. It returns the full revised text and a rationale. | 5 AI credits per generated suggestion (charge key skill_suggestion), only when a suggestion is created |
policy_rule | Automatically, from policy corrections (see above). Several rules for the same skill collect into one open suggestion. | Free |
There is no background job: improvements are only generated when you ask.
Approving a suggestion creates a new skill version; skill versions are never edited in place. Each skill keeps at most one open suggestion of each kind per base version: generating again supersedes the previous open improvement. If the skill changed after the suggestion was made, approving without your own edited text returns 409 stale_base.
Endpoints​
All paths are under /api/v1/automation. The plan must include Conversational Agents and AI features.
| Method | Path | Scope | Success |
|---|---|---|---|
GET | /agents/{agent_id}/skill-suggestions | automation:read | 200 paginated list |
POST | /agents/{agent_id}/skill-suggestions/generate | automation:update | 201 suggestion |
POST | /agents/{agent_id}/skill-suggestions/{suggestion_id}/approve | automation:update | 200 {suggestion, skill_version} |
POST | /agents/{agent_id}/skill-suggestions/{suggestion_id}/dismiss | automation:update | 200 suggestion |
List​
GET /agents/{agent_id}/skill-suggestions?status=proposed&skill_id=…&page=1&page_size=25
| Parameter | Notes |
|---|---|
status | Optional: proposed, approved, dismissed, superseded, failed |
skill_id | Optional |
page / page_size | Default 1 / 25, max 100 |
Returns {"items": [...], "pagination": {...}}, newest first.
An unknown agent or suggestion returns 404 not_found on every suggestion route, and an unknown status returns 422 invalid_status. Without the AI Agents feature on the plan, every route returns 403 feature_disabled.
Generate​
POST /agents/{agent_id}/skill-suggestions/generate
{ "skill_id": "…", "days": 30 }
days is 1–90 (default 30). The suggestion is built from Wrong ratings and 👎 ratings with a reason. A 👎 without a reason is not used. Only ratings on answers that used the skill's current version count; ratings on older versions are passed along as context. The skill must be linked to the agent. The request is also refused when AI is switched off for the workspace (403 ai_disabled).
| Status | detail.code | Charged |
|---|---|---|
201 | Yes, 5 AI credits | |
422 | no_feedback: no qualifying ratings for the current version | No (no AI call) |
422 | no_change: the AI found nothing in the feedback that the skill could fix | No |
502 / 503 | ai_error / ai_unavailable | No |
429 | rate_limit_exceeded (10 per hour per workspace, Retry-After header) | No |
Approve​
POST /agents/{agent_id}/skill-suggestions/{suggestion_id}/approve
{ "instructions": "optional edited full text" }
Without a body, the proposed text is applied. With instructions, your text is applied instead, which also works when the skill changed after the suggestion was made.
{
"suggestion": { "…": "…", "status": "approved", "resulting_version_id": "…" },
"skill_version": { "id": "…", "skill_id": "…", "version": 4, "title": "Refunds", "created_at": "…" }
}
| Status | detail.code | When |
|---|---|---|
409 | stale_base | The skill has a newer version than base_version and no instructions were sent |
409 | not_open | The suggestion is not proposed |
409 | skill_unavailable | The skill is no longer linked to this agent, or it was archived. Dismiss the suggestion, or link or restore the skill first |
422 | no_change | The text equals the current version |
422 | missing_instructions | A policy_rule suggestion whose text would exceed the 20,000-character skill limit: send instructions |
422 | too_long | The text to apply is over the 20,000-character skill limit |
If an agent pins an older version of the skill, the pin stays: approving creates the new current version, and those agents keep using their pinned version. pinned_consumers in the response lists them. To move an agent to the new version, send its full skill list to PUT /api/v1/automation/agents/{agent_id}/skills (automation:update) with that skill's pinned_version_id set to null (always follow the current version) or to the new version id. The list replaces all of the agent's skill links, so include the others unchanged (skill_id, mode, pinned_version_id, sort_order from GET /agents/{agent_id}). Approving a policy_rule suggestion marks its corrections applied.
Dismiss​
POST /agents/{agent_id}/skill-suggestions/{suggestion_id}/dismiss
{ "note": "optional, max 2,000 characters" }
Dismissing a policy_rule suggestion marks its corrections rejected. Rejecting a single correction in the corrections queue removes only its rule from the open suggestion (see Fact vs policy corrections).
Suggestion object​
| Field | Notes |
|---|---|
id, agent_id, skill_id, skill_title, skill_slug | |
kind | improvement or policy_rule |
status | proposed, approved, dismissed, superseded or failed |
base_version_id, base_version | The skill version the suggestion was made against |
is_stale | true when the skill has a newer version than the base (approve needs instructions) |
current_instructions | The base version's text |
proposed_instructions | The full proposed text (null when a policy rule would exceed the skill limit) |
diff | Unified diff, base to proposal |
rationale | Why the change was proposed |
rule_text | policy_rule only: the collected rules, one per line |
source_rating_ids, source_correction_ids, rating_count | The feedback the suggestion is based on |
source_ratings | Up to 20, newest first: {rating_id, verdict, reason_category, comment, better_answer, question, answer, created_at} |
credits_charged | AI credits charged for this suggestion (0 for policy rules) |
model | The model that wrote an improvement |
created_by_user_id, decided_by_user_id, decided_at, decision_note, resulting_version_id | Review trail |
created_at, updated_at | |
pinned_consumers | Agents in this workspace that pin this skill to a version other than the current one: [{type: "agent", id, name, pinned_version_id, pinned_version}]. They do not get an approved change until the pin is switched. Flows never pin skill versions themselves: a flow's AI agent node uses the agent's link, so only agents appear here. |
Rating text is customer and reviewer content. It is passed to the AI as untrusted data and is never followed as instructions. Treat proposed_instructions as a draft and read the diff before approving.