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:

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:

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:

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:

  1. Fetch perceptions from all apps
  2. Evaluate token conditions (fast, synchronous)
    • If tokens don't match, skip immediately
  3. Evaluate storage conditions (async, fetches data)
    • Only runs if token conditions passed
    • Fetches data from storage
    • Checks each field condition
  4. 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.