Perceptions
Perceptions help the AI understand what a user might want to do based on their current state. When certain conditions match, the perception's suggested tasks are surfaced to the AI, improving task discovery and relevance.
What's a Perception?
A perception is a conditional hint to the AI. You define:
- What conditions must be true (tokens present/absent, data state)
- What the user likely wants
- What tasks to suggest
When the conditions match, your perception is included in the AI's context.
Why Use Perceptions?
Without perceptions, the AI only knows what tools exist. It doesn't know:
- Whether a user has completed setup steps
- If required configuration is missing
- What the logical next step is
Perceptions fill this gap. They let you say "when the user has a project but hasn't set an objective, suggest setting one."
Defining Perceptions
Create perception files in perception/*.json:
my-app/
├── perception/
│ ├── needs_setup.json
│ ├── ready_to_work.json
│ └── missing_config.json
└── ...
Basic Structure
{
"perception": "What the user likely wants",
"tasks": ["Suggested task 1", "Suggested task 2"]
}
The perception field describes the user's intent. The tasks array lists what the AI should suggest.
Token Conditions
The simplest conditions check whether tokens exist.
Token Must Exist
{
"tokens": {
"exists": {
"@malv/research": "project"
}
},
"perception": "Working on a research project",
"tasks": ["View project data", "Edit project settings"]
}
This perception only applies when the user has a project token from @malv/research.
Token Must NOT Exist
{
"tokens": {
"absent": {
"@malv/research": "project"
}
},
"perception": "Hasn't selected a project yet",
"tasks": ["Create a new project", "Select an existing project"]
}
This perception only applies when the user does NOT have a project token.
Combining Conditions
Check multiple tokens at once:
{
"tokens": {
"exists": {
"@malv/auth": "account"
},
"absent": {
"@malv/research": "project"
}
},
"perception": "Signed in but no project selected",
"tasks": ["Help user select or create a project"]
}
Storage Conditions
Token conditions are binary - you have a token or you don't. Storage conditions let you check actual data values, enabling state-aware perceptions.
Why Storage Conditions?
Knowing a user "has a project" doesn't tell you if that project is configured. Storage conditions let you check:
- "User has a project with no objective set"
- "User has a project with an empty questions list"
- "User has completed all setup steps"
Structure
{
"tokens": {
"exists": { "@malv/research": "project" }
},
"storage": {
"@malv/research": {
"/teams/<project.teamId>/projects/<project.projectId>/config.json": {
"objective": {
"operator": "equals",
"value": null
}
}
}
},
"perception": "Project has no objective set",
"tasks": ["Set the project objective"]
}
Path Templates
Storage paths support <tokenType.field> templates that resolve from token payloads:
/teams/<project.teamId>/projects/<project.projectId>/config.json
If the project token has { "teamId": "team-123", "projectId": "proj-456" }, this becomes:
/teams/team-123/projects/proj-456/config.json
Operators
| Operator | Description | Needs value |
|---|---|---|
equals |
Deep equality | Yes |
notEquals |
Not equal | Yes |
exists |
Value is not undefined |
No |
notExists |
Value is undefined |
No |
isEmpty |
Value is null, undefined, empty string, empty array, or empty object |
No |
isNotEmpty |
Value has content | No |
Examples
Check if a field is null:
{
"storage": {
"@malv/research": {
"/path/to/config.json": {
"objective": {
"operator": "equals",
"value": null
}
}
}
}
}
Check if an array is empty:
{
"storage": {
"@malv/research": {
"/path/to/config.json": {
"questions": {
"operator": "isEmpty"
}
}
}
}
}
Check nested fields:
{
"storage": {
"@malv/research": {
"/path/to/config.json": {
"settings.notifications.enabled": {
"operator": "equals",
"value": false
}
}
}
}
}
Progressive Perceptions
Guide users through setup with a series of perceptions:
// 1. No project yet
{
"tokens": { "absent": { "@malv/research": "project" } },
"perception": "Wants to create a research project",
"tasks": ["Create project"]
}
// 2. Has project, no objective
{
"tokens": { "exists": { "@malv/research": "project" } },
"storage": {
"@malv/research": {
"/path/config.json": { "objective": { "operator": "isEmpty" } }
}
},
"perception": "Wants to set project objective",
"tasks": ["Set objective"]
}
// 3. Has project and objective, no questions
{
"tokens": { "exists": { "@malv/research": "project" } },
"storage": {
"@malv/research": {
"/path/config.json": {
"objective": { "operator": "isNotEmpty" },
"questions": { "operator": "isEmpty" }
}
}
},
"perception": "Wants to add research questions",
"tasks": ["Add questions"]
}
How It Works
When the orchestrator prepares context for the AI:
- Fetch perceptions from all apps
- Evaluate token conditions (fast, synchronous)
- If tokens don't match, skip immediately
- Evaluate storage conditions (async, fetches data)
- Only runs if token conditions passed
- Fetches data from storage
- Checks each field condition
- Collect matches and include in AI prompt
The AI sees something like:
**Info about user:**
- Wants to set project objective
Suggested: Set objective
Best Practices
Use token conditions as guards - Always include token conditions when using storage conditions. This ensures fast rejection and prevents unnecessary storage queries.
{
"tokens": {
"exists": { "@malv/research": "project" }
},
"storage": { ... }
}
Keep perceptions focused - Each perception should represent a single, clear intent:
// Good - specific
{
"perception": "Wants to set project objective",
"tasks": ["Set objective"]
}
// Avoid - too broad
{
"perception": "Working on project",
"tasks": ["Set objective", "Add questions", "Configure settings", "Export data"]
}
Be specific about state - Use storage conditions to check exactly what's missing:
// Good - checks actual state
{
"storage": {
"@malv/research": {
"/path/config.json": { "objective": { "operator": "isEmpty" } }
}
},
"perception": "Wants to set objective"
}
// Vague - doesn't check state
{
"perception": "Might need to configure project"
}
Order matters - More specific perceptions should have more specific conditions. The system evaluates all matching perceptions, but focused suggestions are more helpful than broad ones.