Skip to content

System Prompts

The system prompt tells Iris who it is and what it knows about you. It's assembled dynamically for each request, combining a static persona with contextual information like memories, summaries, and calendar events.

How It Works

Every time you send a message, Iris builds a system prompt by rendering a series of prompt classes in order. Each class is responsible for one section of the prompt:

┌─────────────────────────────┐
│  IrisStaticPrompt           │  ← Core identity and personality (cached)
├─────────────────────────────┤
│  ShellToolPrompt            │  ← Shell tool instructions (cached)
├─────────────────────────────┤
│  SkillsPrompt               │  ← Available agent skills (cached)
├─────────────────────────────┤
│  MemoryPrompt               │  ← Truths + contextual memories
├─────────────────────────────┤
│  SummaryPrompt              │  ← Recent conversation summaries
├─────────────────────────────┤
│  CalendarPrompt             │  ← Upcoming calendar events
├─────────────────────────────┤
│  CurrentTimePrompt          │  ← Current date and time
└─────────────────────────────┘

The result is a personalized, context-aware prompt that includes everything Iris needs to respond appropriately.

Architecture

Prompts are self-contained: each prompt injects a RequestContext and any services it needs, then fetches its own context when content() is called. This provides:

  • Uniform pattern: Core and custom prompts work identically
  • Testability: Prompts can be tested in isolation
  • Flexibility: Each prompt injects only what it needs
  • Conditional rendering: Prompts can return empty content if they have nothing to contribute

Prompt Pipeline

Each prompt is a separate class rendered in order. The default pipeline:

Static Prompt (Cached)

Core identity and behavior that rarely changes:

  • Identity and personality
  • Communication style
  • Tool usage guidelines

Cached for 1 hour for performance.

Shell Tool Prompt (Cached)

Instructions for shell command execution:

  • Security guidelines and restrictions
  • Only renders when the shell tool is enabled

Cached for 1 hour — content is loaded from config, not generated per-request.

Skills Prompt (Cached)

Lists available agent skills for Iris to activate:

  • Skill names and descriptions from .agents/skills/
  • Only renders when skills are enabled and available

Cached for 1 hour — content is loaded from disk files, not generated per-request.

Memory Prompt

Recalls relevant context for the current message:

  • Truths: Stable, core facts ranked by relevance (pinned Truths always included)
  • Memories: Semantically similar memories found via search
  • Only renders when Truths or memories exist

Summary Prompt

Injects recent conversation summaries for continuity:

  • Narrative arc from previous conversations
  • Emotional context and relationship dynamics
  • Only renders when summaries exist

Calendar Prompt

Retrieves upcoming calendar events for the current user:

  • Upcoming events for the next 7 days
  • Calendar names and default calendar info
  • Only renders when events exist

Current Time Prompt

Provides the current date and time in the user's timezone (falling back to the configured default). Always renders.

Prompt Templates

Templates are Blade files in resources/views/prompts/:

prompts/
├── personas/
│   └── iris-static.blade.php       # Core identity
├── recalled-context.blade.php      # Memory context
├── calendar-context.blade.php      # Calendar events
└── summary-context.blade.php       # Conversation summaries

Customizing Prompts

Create your own prompt classes to customize Iris's behavior. Each prompt is self-contained and injects the dependencies it needs.

The content() method can return either a string or a Blade view:

php
<?php

declare(strict_types=1);

namespace App\Extensions\Prompts;

use App\Prompts\Prompt;
use App\ValueObjects\RequestContext;
use Illuminate\View\View;

class WeatherPrompt extends Prompt
{
    public function __construct(
        protected RequestContext $requestContext,
        protected WeatherService $weather,
    ) {}

    // Return a Blade view for complex templates
    public function content(): View
    {
        return view('prompts.extensions.weather', [
            'forecast' => $this->weather->getForecast(
                $this->requestContext->user()?->id
            ),
        ]);
    }
}

For simpler prompts, return a string directly:

php
<?php

declare(strict_types=1);

namespace App\Extensions\Prompts;

use App\Prompts\Prompt;

class TimezonePrompt extends Prompt
{
    public function content(): string
    {
        return "## User Timezone\n\nThe user is in the Pacific timezone.";
    }
}

The RequestContext API

The RequestContext value object provides access to everything about the current request:

MethodReturnsDescription
user()User|nullThe authenticated user model, or null if unauthenticated
message()stringThe user's message text for this request
images()arrayArray of attached image data (for multi-modal requests)

Using RequestContext

Every prompt receives a RequestContext via constructor injection. Use it to customize prompt content based on the current request:

php
public function __construct(
    protected RequestContext $requestContext,
) {}

public function content(): string
{
    $user = $this->requestContext->user();

    if (! $user) {
        return ''; // No content for unauthenticated requests
    }

    // Customize based on user or message
    $message = $this->requestContext->message();

    if (str_contains(strtolower($message), 'urgent')) {
        return "## Priority Mode\n\nThe user has indicated urgency.";
    }

    return '';
}

Registering Custom Prompts

Add your prompt to config/iris-custom.php. The prompts array replaces the default list, so include the core prompts you want to keep:

php
// config/iris-custom.php
return [
    'prompts' => [
        App\Prompts\IrisStaticPrompt::class,
        App\Prompts\ShellToolPrompt::class,
        App\Prompts\SkillsPrompt::class,
        App\Prompts\MemoryPrompt::class,
        App\Prompts\SummaryPrompt::class,
        App\Extensions\Prompts\WeatherPrompt::class,  // Your custom prompt
        App\Prompts\CalendarPrompt::class,
        App\Prompts\CurrentTimePrompt::class,
    ],
];

Prompt Ordering Considerations

Prompts are rendered in order, and that order matters. The system prompt flows from static/cacheable content to dynamic, per-request context:

  1. Static identity first: Establishes who Iris is and how it should behave (cached)
  2. Semi-static tools/skills: Instructions and capabilities that rarely change (cached)
  3. Memories: Facts about the user that inform the response (dynamic)
  4. Summaries: Historical context from previous conversations (dynamic)
  5. Integrations: Calendar, weather, or other external data (dynamic)
  6. Temporal context last: Current date/time anchors everything (dynamic)

This ordering maximizes prompt cache hit rates — static content at the top gets cached, while dynamic content at the bottom changes per-request without invalidating the cache.

When adding custom prompts, consider where the information fits logically. Static content should go near the top (after skills), and user-specific context (like project information) typically goes after memories but before temporal context.

Complete Custom Prompt Example

Here's a full example of creating and registering a custom prompt that injects work project context:

1. Create the Prompt Class

php
<?php

declare(strict_types=1);

namespace App\Extensions\Prompts;

use App\Models\User;
use App\Prompts\Prompt;
use App\Services\ProjectService;
use App\ValueObjects\RequestContext;
use Illuminate\View\View;

class WorkContextPrompt extends Prompt
{
    public function __construct(
        protected RequestContext $requestContext,
        protected ProjectService $projectService,
    ) {}

    public function content(): View|string
    {
        $user = $this->requestContext->user();

        if (! $user) {
            return '';
        }

        $projects = $this->projectService->getActiveProjects($user->id);

        if ($projects->isEmpty()) {
            return '';
        }

        return view('prompts.extensions.work-context', [
            'projects' => $projects,
        ]);
    }
}

2. Create the Blade Template

blade
{{-- resources/views/prompts/extensions/work-context.blade.php --}}
## Current Work Projects

The user is currently working on these projects:

@foreach($projects as $project)
- **{{ $project->name }}**: {{ $project->description }}
  - Status: {{ $project->status }}
  - Deadline: {{ $project->deadline?->format('F j, Y') ?? 'No deadline' }}
@endforeach

Use this context when the user asks about work or projects.

3. Register the Prompt

php
// config/iris-custom.php
return [
    'prompts' => [
        App\Prompts\IrisStaticPrompt::class,
        App\Prompts\ShellToolPrompt::class,
        App\Prompts\SkillsPrompt::class,
        App\Prompts\MemoryPrompt::class,
        App\Prompts\SummaryPrompt::class,
        App\Extensions\Prompts\WorkContextPrompt::class,  // After summaries
        App\Prompts\CalendarPrompt::class,
        App\Prompts\CurrentTimePrompt::class,
    ],
];

Caching Static Prompts

For prompts with content that rarely changes, you can enable caching to reduce token costs:

php
public function providerOptions(): array
{
    return [
        'cacheType' => 'ephemeral',
    ];
}

The IrisStaticPrompt, ShellToolPrompt, and SkillsPrompt use this to cache their content for 1 hour. Dynamic prompts (memories, summaries, calendar) shouldn't be cached since their content changes with each request.