---
title: "Check-ins and verdicts"
url: https://upskill.coach/docs/check-ins-and-verdicts
last_updated: 2026-10-25
tokens_approx: 4190
publisher: Avatar 8 LLC (ops@avatar33.com)
---

> For AI agents: the docs index is at https://upskill.coach/llms.txt. Every page has a markdown version: add .md to its URL.

# Check-ins and verdicts

> Once a day the agent sends a check-in: its north-star, KPIs, work counts and a report for each adopted item. The coach answers with health signals and a verdict per item. In short: permanent means keep it, temporary means keep it for the current task only, and pending_removal means remove it after your owner confirms. The rules below are the exact ones the code runs, also served as JSON at https://upskill.coach/api/v1/rules.

## Send a check-in
```bash
curl -s -X POST 'https://upskill.coach/api/v1/agents/me/checkins' \
  -H 'authorization: Bearer <api_key>' \
  -H 'content-type: application/json' \
  -d '{"north_star":{"value":15,"evidence":"verified","source":"prod DB query"},"kpis":{"paying_accounts":1},"releases":{"shipped":1,"verified_live":1},"work":{"build_actions":3,"distribution_actions":2},"items":[{"item_id":"<id>","used":true,"uses":4,"successes":4,"failures":0,"minutes_saved":20,"helped":"yes","scope":"ongoing"}],"notes":"Shipped pricing page; 3 directory listings."}'
```

MCP: the `check_in` tool takes the same fields. Re-sending is safe: a second check-in on the same UTC day merges into that day's check-in. Blocks you send replace the earlier ones, blocks you don't resend are kept, nothing is counted twice, and the response says `"merged": true`. Send `"mode":"new"` only for a genuinely separate check-in.

Report every adopted item, including unused ones (`"used": false`). Use `"scope":"task"` for an item used for one task only. Set an item's `owner_feedback` to up or down only if your owner actually said so. Leave out any block you can't measure.

### Check-in fields
| field | type | required | notes |
|---|---|---|---|
| `north_star` | object | no | Your north-star value right now. Report it every day |
| `north_star.value` | number | yes |  |
| `north_star.evidence` | one of `self_reported`, `verified` | no | self_reported (default): your own count. verified: read from a system of record (prod DB, Stripe, analytics, live E2E check) and you can link it in source |
| `north_star.source` | string | no | Where the number comes from: a URL, query name, or system. max 500 chars |
| `kpis` | map of number or object | no | Secondary KPI values: number, or {value, evidence, source}. default `{}` |
| `releases` | object | no |  |
| `releases.shipped` | integer | yes | min 0, max 1000000 |
| `releases.verified_live` | integer | yes | How many of those you verified live end to end (real URL, real request) before calling them done. min 0, max 1000000 |
| `releases.evidence_urls` | array of string | no | at most 20 |
| `releases.evidence` | one of `self_reported`, `verified` | no | self_reported (default): your own count. verified: read from a system of record (prod DB, Stripe, analytics, live E2E check) and you can link it in source |
| `releases.source` | string | no | Where the number comes from: a URL, query name, or system. max 500 chars |
| `work` | object | no |  |
| `work.build_actions` | integer | yes | Features/fixes shipped or built. min 0, max 1000000 |
| `work.distribution_actions` | integer | yes | Listings, posts, launches, outreach sent, directory submissions. min 0, max 1000000 |
| `work.evidence` | one of `self_reported`, `verified` | no | self_reported (default): your own count. verified: read from a system of record (prod DB, Stripe, analytics, live E2E check) and you can link it in source |
| `work.source` | string | no | Where the number comes from: a URL, query name, or system. max 500 chars |
| `owner_blockers` | object | no |  |
| `owner_blockers.asked` | integer | yes | New asks to your owner since last check-in. min 0, max 1000000 |
| `owner_blockers.cleared` | integer | yes | min 0, max 1000000 |
| `owner_blockers.open_now` | integer | yes | min 0, max 1000000 |
| `owner_blockers.total_wait_hours` | number | no | Sum of hours the cleared asks waited. min 0, max 100000 |
| `owner_blockers.open_asks` | array of object | no | Each ask still open, with its age. at most 20 |
| `owner_blockers.open_asks[].age_hours` | number | yes | How long this ask has been open, in hours. min 0, max 100000 |
| `owner_blockers.open_asks[].summary` | string | no | max 200 chars |
| `owner_blockers.evidence` | one of `self_reported`, `verified` | no | self_reported (default): your own count. verified: read from a system of record (prod DB, Stripe, analytics, live E2E check) and you can link it in source |
| `owner_blockers.source` | string | no | Where the number comes from: a URL, query name, or system. max 500 chars |
| `owner_corrections` | object | no |  |
| `owner_corrections.total` | integer | yes | min 0, max 1000000 |
| `owner_corrections.repeated` | integer | yes | Corrections your owner had already given you before. min 0, max 1000000 |
| `owner_corrections.examples` | array of string | no | at most 5 |
| `owner_corrections.evidence` | one of `self_reported`, `verified` | no | self_reported (default): your own count. verified: read from a system of record (prod DB, Stripe, analytics, live E2E check) and you can link it in source |
| `owner_corrections.source` | string | no | Where the number comes from: a URL, query name, or system. max 500 chars |
| `interruptions` | object | no |  |
| `interruptions.timeouts` | integer | yes | min 0, max 1000000 |
| `interruptions.killed_tasks` | integer | yes | Tasks stopped for running too long. min 0, max 1000000 |
| `interruptions.evidence` | one of `self_reported`, `verified` | no | self_reported (default): your own count. verified: read from a system of record (prod DB, Stripe, analytics, live E2E check) and you can link it in source |
| `interruptions.source` | string | no | Where the number comes from: a URL, query name, or system. max 500 chars |
| `claims` | object | no |  |
| `claims.made` | integer | yes | Claims about users, metrics or results you made to your owner or in public. min 0, max 1000000 |
| `claims.unsupported` | integer | yes | Of those, ones without a linked source. min 0, max 1000000 |
| `claims.evidence` | one of `self_reported`, `verified` | no | self_reported (default): your own count. verified: read from a system of record (prod DB, Stripe, analytics, live E2E check) and you can link it in source |
| `claims.source` | string | no | Where the number comes from: a URL, query name, or system. max 500 chars |
| `incidents` | object | no |  |
| `incidents.destructive` | integer | yes | Deletes/sends/charges outside policy. min 0, max 1000000 |
| `incidents.wrong_entity` | integer | yes | Wrong account, card, email or identity used. min 0, max 1000000 |
| `incidents.notes` | string | no | max 500 chars |
| `incidents.evidence` | one of `self_reported`, `verified` | no | self_reported (default): your own count. verified: read from a system of record (prod DB, Stripe, analytics, live E2E check) and you can link it in source |
| `incidents.source` | string | no | Where the number comes from: a URL, query name, or system. max 500 chars |
| `deliverable_progress_pct` | number | no | 0-100: how far along the named deliverable is. min 0, max 100 |
| `items` | array of object | no | at most 50, default `[]` |
| `items[].item_id` | string | yes | max 120 chars |
| `items[].used` | boolean | yes | Did you use this item since the last check-in? |
| `items[].uses` | integer | no | min 0, max 1000000 |
| `items[].successes` | integer | no | Uses where it did what you needed. min 0, max 1000000 |
| `items[].failures` | integer | no | min 0, max 1000000 |
| `items[].minutes_saved` | number | no | min 0, max 100000 |
| `items[].cost_usd` | number | no | min 0, max 1000000 |
| `items[].helped` | one of `yes`, `no`, `unsure` | no |  |
| `items[].scope` | one of `ongoing`, `task` | no | 'task' if you used it only for one specific task |
| `items[].owner_feedback` | one of `up`, `down`, `none` | no | Only if your owner actually said so |
| `items[].evidence` | one of `self_reported`, `verified` | no | self_reported (default): your own count. verified: read from a system of record (prod DB, Stripe, analytics, live E2E check) and you can link it in source |
| `items[].notes` | string | no | max 500 chars |
| `mode` | one of `auto`, `new`, `merge` | no | auto (default): a second check-in on the same UTC day merges into that day's check-in (new values replace old ones, nothing is double counted). merge: always merge into your latest check-in. new: force a separate check-in. default `"auto"` |
| `tasks` | object | no |  |
| `tasks.attempted` | integer | no | min 0, max 1000000 |
| `tasks.succeeded` | integer | no | min 0, max 1000000 |
| `tasks.minutes` | number | no | min 0 |
| `tasks.cost_usd` | number | no | min 0 |
| `owner_feedback` | object | no |  |
| `owner_feedback.rating` | integer | no | min 1, max 5 |
| `owner_feedback.comment` | string | no | max 500 chars |
| `notes` | string | no | max 2000 chars |

A nested field marked required is only required when its parent is sent.

## Item verdicts
| verdict | label | what to do |
|---|---|---|
| `permanent` | Permanent skill | Keep it installed. |
| `promising` | Promising (not proven yet) | Keep using it and report verified values where you can. |
| `temporary` | Temporary (this task only) | Keep it for the current task, then uninstall. |
| `trial` | Trial (collecting evidence) | Keep using it; not enough evidence yet. |
| `unproven` | Unproven | Evidence is mixed; keep measuring. |
| `pending_removal` | Pending removal | Ask your owner to confirm, uninstall, then send `{"item_ids":["<id>"],"action":"remove"}` to POST /api/v1/agents/me/items. |

### How a verdict is decided
At each check-in, for every adopted item, the coach looks only at check-ins since that item was adopted. Rules run in this order and the first match wins:

| rule | verdict | condition |
|---|---|---|
| `R1_owner_down` | `pending_removal` | Owner gave a thumbs-down on the latest report for this item. |
| `R2_unused` | `pending_removal` | Adopted but not used in any of the last 3+ check-ins. |
| `R3_failing` | `pending_removal` | 3+ attempts and success rate under 50%. |
| `R4_kpi_regressed` | `pending_removal` | Used in 2+ check-ins and its related KPIs got worse by 10%+ vs baseline (and nothing else shows it helping). |
| `R5_task_scoped` | `temporary` | Most reports say it was used for one specific task (scope=task) and it worked (success rate 50%+). |
| `R6_proven` | `permanent` | All of: used in 5+ check-ins on 3+ different days; success rate 70%+ (50%+ with an owner thumbs-up) or no failures reported; KPI trend positive (related KPIs, or all KPIs if none are related, better than baseline in their direction); and either at least one verified value since adoption or an owner thumbs-up. |
| `R7_promising` | `promising` | Working but not proven yet: an owner thumbs-up with usage, or used in 2+ check-ins with success rate 70%+ and a gain (related KPIs up 10%+ vs baseline while in use, a positive KPI trend, 30+ minutes saved, or reported helpful in most check-ins). Becomes permanent only when R6 is fully met. |
| `R8_one_off` | `temporary` | Used in exactly one check-in, then not again for 2+ check-ins, and that use worked. |
| `R9_too_early` | `trial` | Fewer than 2 check-ins with usage: not enough evidence yet. |
| `R10_mixed` | `unproven` | Enough usage, but the evidence is mixed. Keep measuring; it will be marked for removal if it doesn't improve. |

The bar for permanent: used in 5+ check-ins on 3+ different UTC days, success rate 70%+, a positive KPI trend, and at least one verified value or an owner thumbs-up. Each verdict comes with its rule id and a reason that quotes the numbers it used, so the agent can see what is missing.

KPI movement is only linked to an item after 2 or more check-ins where it was used, and it is shown as a correlation, not proof. An item's related KPIs are the north-star and KPIs whose tags overlap the item's tags.

### Updating items
POST /api/v1/agents/me/items (MCP: `update_items`):

| field | type | required | notes |
|---|---|---|---|
| `item_ids` | array of string | yes | at least 1, at most 20 |
| `action` | one of `adopt`, `remove`, `decline`, `not_relevant` | no | adopt; remove (was adopted, now uninstalled); decline (owner said no); not_relevant (doesn't fit your job; not a judgment on the item's quality). default `"adopt"` |
| `reason` | string | no | max 300 chars |

`decline` is for items your owner rejected and `not_relevant` for items that don't fit your job. Neither is recommended again.

## Health
The check-in response has `health`, `health_trend` (improving, flat or worsening vs the previous check-in), `health_confidence` (verified, partly_verified or unverified), `signals`, item `verdicts`, `verdict_changes` and `actions`. Do what `actions` says. Health rules run in order; the first match wins. Weekly signals use check-ins from the last 7 days.

| health | rule |
|---|---|
| `no_signal` | No check-in yet, or the last one is more than 48h old. |
| `at_risk` | A risk signal is active: repeated corrections, boundary incidents, unsupported claims, or 2+ interruptions/day in the last 7 days, AND the latest check-in either repeats it or doesn't report that block. Recent check-ins weigh more: if the latest check-in reports that block clean, the older event is shown as warn ('earlier; latest clean') and no longer forces at_risk. |
| `recovering` | There were risk events earlier in the last 7 days, the latest check-in is clean, and the trend vs the previous check-in is improving (north-star better in its direction, or deliverable progress up). |
| `stalled` | Building with no distribution, or the deliverable hasn't moved in 3 check-ins. |
| `on_track` | North-star fresh, releases verified live (when shipping), distribution not neglected, the north-star better than baseline, the declared deliverable moving, and evidence confidence not bad. With 0% verified evidence it's labeled confidence: unverified. |
| `needs_attention` | Anything else. |

### Signals
| signal | label | rule |
|---|---|---|
| `north_star_fresh` | North-star reported in the last 24h | good if the latest check-in includes a north-star value and is under 24h old; bad if no north-star is declared or it hasn't been reported for 48h+; warn otherwise. |
| `definition_stable` | North-star definition stable | warn if the definition (or key) changed in the last 7 days; good otherwise. The value says how long the current version has actually existed. Both series stay visible in the report. |
| `evidence_confidence` | Evidence confidence | share of reported values tagged verified (with a source), over all check-ins. good at 50%+; warn under 50%; bad if 0% after 2+ check-ins (warn at the first). When bad, health can't be on_track. |
| `live_verified_ship_rate` | Releases verified live end to end | verified_live / shipped over 7 days. good at 90%+, warn 50-89%, bad under 50%. unknown if nothing shipped. |
| `distribution_build_ratio` | Distribution vs build | distribution_actions / build_actions over 7 days. bad if building with zero distribution; warn under 0.5; good at 0.5+. |
| `owner_blocker_dwell` | Owner-blocker wait time | open asks and their current age (age_hours reported + time since that check-in), plus avg wait of cleared asks. bad if any open ask is older than 24h, avg cleared wait > 72h, or 5+ open; warn if anything is open, avg > 24h; good if nothing is open and waits are short. |
| `repeat_corrections` | Repeated owner corrections | bad if the owner had to repeat any correction in the last 7 days; good if zero. |
| `interruptions` | Timeouts and killed tasks | per day, 7-day average. bad at 2+/day, warn above 0, good at 0. |
| `unsupported_claims` | Claims without evidence | bad if any claim about users/metrics/results had no linked source in the last 7 days. |
| `boundary_incidents` | Destructive / wrong-entity mistakes | bad if any destructive action outside policy or wrong account/card/identity in the last 7 days. |
| `deliverable_progress` | Progress toward the named deliverable | from deliverable_progress_pct, or computed from the deliverable's kpi_key (baseline → target). warn if not reported, at 0%, or only reported once; bad if it hasn't moved across the last 3 check-ins (and isn't 100%); good if it moved. A declared deliverable that isn't moving blocks on_track. |
| `north_star_trend` | North-star vs baseline | direction-adjusted change from baseline to latest: good if better, bad if worse, warn if flat. |
