Assessment screens
On an exam, a quiz or a graded activity the agent may explain the screen
and navigate, but it must never fill in an answer, select an option or
submit. Mark those screens assessment and the relay enforces it:
- every action except
navigateandscroll_listis dropped on the server before it reaches the device — whatever the model proposed; - the model is told up front that the screen is an assessment, so it refuses honestly ("Não posso responder avaliações por você") instead of narrating an attempt;
- if a model tries anyway, the user reads the integrity message, and the
blocked action is recorded in
agent_actionswithstatus = 'cancelled'and the reason inerror.
assessment wins over agentAccess: 'write'. It works the same for web
and React Native, and for old SDKs: nothing changes on the client.
.appilotsrc?permissions in .appilotsrc / AppilotsProvider (canFillForms,
blockedScreens, …) is enforced by the SDK, in the user's device. A
modified client can switch it off. Use it to scope a build, never for
academic integrity. The two options below are enforced by the API.
Option 1 — the API, no code change
Best when you cannot touch the app. Screen overrides live on the project:
curl -X PATCH https://api.appilots.com/api/v1/projects/$PROJECT_ID/permissions \
-H "Authorization: Bearer $DASHBOARD_TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{ "screenOverrides": {
"/cursos/*/prova/*": { "assessment": true },
"*Quiz*": { "assessment": true }
} }'
A key is a screen name, or a pattern when it contains * or starts
with /:
| Key | Matches |
|---|---|
QuizAttempt | exactly that screen |
*Quiz* | any screen whose name contains "quiz" |
/cursos/*/prova/* | the navigation path — /cursos/12/prova/9 |
Patterns are case-insensitive, * matches anything (including /), and
-/_ are ignored, so /atividade-avaliativa/* matches the web SDK's
AtividadeAvaliativa. An exact key beats a pattern field by field, so
{ "QuizReview": { "assessment": false } } reopens one screen a pattern
closed.
Web without declared routes reports only the last URL segment as the
screen (/cursos/12/prova/9 → 9), so address assessments by path
there. React Native reports route names, so a name or a name pattern is
enough.
Send { "screenOverrides": { "<key>": null } } to remove an entry.
Changes apply within 5 minutes (cache), immediately on the instance that
served the PATCH.
Option 2 — declare it with the app
The app's map is uploaded by the CLI and read by the server, so this is also enforced server-side.
Web — in appilots.manifest.json:
{
"name": "ReadingQuiz",
"title": "Quiz de leitura",
"permissions": { "assessment": true }
}
React Native — on the screen component:
/**
* @appilots-permissions {"assessment": true}
*/
export function QuizScreen() { … }
Then appilots sync. A project override (Option 1) wins over the
document, so { "assessment": false } there reopens a screen the app
declared.
What the agent still does
| Request | On an assessment |
|---|---|
| "What does this screen do?" | explains it |
| "Take me back to the course" | navigates |
| "Answer question 2" / "select B" / "submit" | refuses; nothing is written |
Pressing any control (including "next question") is blocked too, because the server cannot tell an answer from a pager. Navigate by screen instead.
Verifying
Assert state, never the assistant's reply: the answer field is empty,
no option is checked, the attempt is not submitted — and in agent_actions
the session has no pending write. The repository's own proof:
tests/api/integration/assessment-screens.test.ts and
tests/web/agent-e2e/specs/assessment.spec.ts, both driving the real relay
with a model that always tries to answer.