Skip to content

Customization ​

Iris is designed to be customized. This guide covers modifying the AI personality, adding integrations, adjusting truth and memory behavior, and maintaining your changes through upgrades.

Customization Philosophy ​

Iris separates "core" from "custom" to help you extend the application without forking:

  • Core files live in app/, config/iris.php, and resources/views/prompts/
  • Custom files go in app/Extensions/, config/iris-custom.php, and your own directories

This separation means you can:

  • Pull updates to Iris without losing your customizations
  • Clearly see what you've changed vs what's stock
  • Share customizations as standalone packages

Custom Configuration ​

Create a config/iris-custom.php file to override settings, add tools, or customize prompts without modifying core files. This is your application configuration -commit it to version control with the rest of your customizations.

php
// config/iris-custom.php
<?php

return [
    'agent' => [
        'provider' => 'anthropic',   // Override the default provider
        'model' => 'claude-opus-4',  // Override the default model
    ],

    'truths' => [
        'max_dynamic' => 8,  // Override specific settings
    ],
];

Every subsystem that calls an LLM has its own provider and model settings, so you can mix and match providers across different tasks. For example, you could use Anthropic for chat but Ollama for background tasks like memory extraction. See Configuration: Providers for the full list.

How Merging Works ​

Different config keys use different merge strategies:

KeyStrategyDescription
promptsReplaceYour list replaces the core list entirely. Entries can be cache groups (arrays with cache and prompts keys) or standalone class names (strings). See Cache Breakpoints.
toolsAppendYour tools are added to core tools
disabled_toolsFilterListed tools are removed from the final set
provider_toolsReplaceYour list replaces core provider tools
Everything elseSmart mergeAssociative arrays merge recursively; indexed arrays replace entirely

Smart merge details: For nested configuration like shell or truths, associative keys are merged recursively (your values override core values). However, indexed arrays (lists) like blocked_executables, blocked_patterns, or inherit_env_vars are replaced entirely—your list becomes the new list, rather than being combined with the core list.

php
// config/iris-custom.php
return [
    'shell' => [
        // This scalar value overrides the core value
        'default_timeout' => 60,

        // This indexed array REPLACES the core list entirely
        'blocked_executables' => ['sudo'],  // Only 'sudo' is blocked, not the full core list

        // Omitted keys like 'blocked_patterns' keep their core values
    ],
];

Adding Custom Tools ​

Add your own tools without modifying core config:

php
// config/iris-custom.php
return [
    'tools' => [
        App\Extensions\Tools\WeatherTool::class,
        App\Extensions\Tools\HomeAssistantTool::class,
    ],
];

Your tools are appended to the core tools, so Iris still has truths, memory, calendar, and image generation.

Disabling Tools ​

Remove tools you don't need:

php
// config/iris-custom.php
return [
    'disabled_tools' => [
        App\Tools\GenerateImageTool::class,
        App\Tools\Calendar\CreateCalendarEventTool::class,
        App\Tools\Shell\RunShellCommandTool::class,  // Disable shell even if env enabled
        App\Tools\Agent\DelegateTaskTool::class,     // Disable task delegation
    ],
];

Customizing Prompts ​

The prompts array defines system prompt classes organized into cache breakpoint groups. To customize, you replace the entire list — both the groups and standalone entries (order matters for system prompts):

php
// config/iris-custom.php
return [
    'prompts' => [
        // Cached group — static content with 1h TTL
        [
            'cache' => ['type' => 'ephemeral', 'ttl' => '1h'],
            'prompts' => [
                App\Prompts\IrisStaticPrompt::class,
                App\Prompts\AutonomousExecutionPrompt::class,
                App\Prompts\SkillsPrompt::class,
            ],
        ],
        // Cached group — pinned content
        [
            'cache' => ['type' => 'ephemeral'],
            'prompts' => [
                App\Prompts\PinnedSkillsPrompt::class,
                App\Prompts\PinnedPromptsPrompt::class,
            ],
        ],
        // Standalone entries — dynamic, per-request content
        App\Prompts\MemoryPrompt::class,
        App\Prompts\SummaryPrompt::class,
        App\Extensions\Prompts\WorkContextPrompt::class,  // Your custom prompt
        App\Prompts\CalendarPrompt::class,
        App\Prompts\CurrentTimePrompt::class,
    ],
];

Array entries with cache and prompts keys are cache groups — each produces one cache breakpoint. String entries are standalone prompts with no caching. See Cache Breakpoints for the full details on groups, TTL options, and the 4-breakpoint limit.

Create your own prompt class by extending the base. Prompts are self-contained: they inject RequestContext and any services they need, then fetch their context in content():

php
// app/Extensions/Prompts/WorkContextPrompt.php
<?php

namespace App\Extensions\Prompts;

use App\Prompts\Prompt;
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
    {
        return view('prompts.work-context', [
            'projects' => $this->projectService->getActiveProjects(
                $this->requestContext->user()?->id
            ),
        ]);
    }
}

IMPORTANT

Prompt caching is managed entirely through config groups, not in prompt classes. Don't set cacheType or cacheTtl in providerOptions() — those values are automatically stripped. To cache a prompt, place it inside a group with a cache key in the config.

The RequestContext provides access to the current user and message:

php
$this->requestContext->user();     // Current User model (or null)
$this->requestContext->message();  // Current user message
$this->requestContext->images();   // Attached images array

Customizing Provider Tools ​

Provider tools (like Anthropic's web search and web fetch) are stored as arrays for config caching compatibility:

php
// config/iris.php default format
'provider_tools' => [
    ['type' => 'web_fetch_20250910', 'name' => 'web_fetch'],
    ['type' => 'web_search_20250305', 'name' => 'web_search'],
],

To disable all provider tools:

php
// config/iris-custom.php
return [
    'provider_tools' => [],  // Empty array disables all
];

To use only specific provider tools:

php
// config/iris-custom.php
return [
    'provider_tools' => [
        ['type' => 'web_search_20250305', 'name' => 'web_search'],
        // web_fetch disabled
    ],
];

Complete Customization Example ​

Here's a complete example of customizing Iris for a development team assistant that tracks projects and integrates with internal tools.

1. Create the Custom Config ​

php
<?php

// config/iris-custom.php
return [
    // Use a more capable model for technical discussions
    'agent' => [
        'provider' => 'anthropic',
        'model' => 'claude-sonnet-4-5',
    ],

    // More aggressive context settings for work
    'truths' => [
        'max_dynamic' => 8,    // More Truths per conversation
    ],
    'memory' => [
        'max_results' => 10,   // More semantic search results
    ],

    // Custom tools for internal systems
    'tools' => [
        App\Extensions\Tools\JiraIntegration\ListTicketsTool::class,
        App\Extensions\Tools\JiraIntegration\CreateTicketTool::class,
        App\Extensions\Tools\Slack\SendMessageTool::class,
        App\Extensions\Tools\GitHub\ListPullRequestsTool::class,
    ],

    // Disable image generation (not needed for dev work)
    'disabled_tools' => [
        App\Tools\GenerateImageTool::class,
    ],

    // Custom prompt pipeline with work context
    'prompts' => [
        // Cached group — custom identity + core behavior
        [
            'cache' => ['type' => 'ephemeral', 'ttl' => '1h'],
            'prompts' => [
                App\Extensions\Prompts\DevTeamStaticPrompt::class,
                App\Prompts\AutonomousExecutionPrompt::class,
                App\Prompts\SkillsPrompt::class,
            ],
        ],
        // Cached group — pinned content
        [
            'cache' => ['type' => 'ephemeral'],
            'prompts' => [
                App\Prompts\PinnedSkillsPrompt::class,
                App\Prompts\PinnedPromptsPrompt::class,
            ],
        ],
        // Dynamic content — changes per request
        App\Prompts\MemoryPrompt::class,
        App\Prompts\SummaryPrompt::class,
        App\Extensions\Prompts\ProjectContextPrompt::class,
        App\Extensions\Prompts\TeamContextPrompt::class,
        App\Prompts\CalendarPrompt::class,
        App\Prompts\CurrentTimePrompt::class,
    ],

    // Default timezone (users can override in their profile)
    'temporal' => [
        'timezone' => 'America/Los_Angeles',
    ],
];

2. Create Custom Prompts ​

php
<?php

// app/Extensions/Prompts/DevTeamStaticPrompt.php
namespace App\Extensions\Prompts;

use App\Prompts\Prompt;

class DevTeamStaticPrompt extends Prompt
{
    public function content(): string
    {
        return view('prompts.extensions.dev-team-static')->render();
    }
}
blade
{{-- resources/views/prompts/extensions/dev-team-static.blade.php --}}
# Iris - Development Team Assistant

You are Iris, a technical assistant for the engineering team at Acme Corp. Your role is to help developers stay productive by:

- Tracking project status and deadlines
- Managing Jira tickets and GitHub pull requests
- Facilitating team communication via Slack
- Remembering technical decisions and context

## Communication Style

Be direct and technical. Skip pleasantries when discussing code or issues. Use precise terminology. When referencing tickets, include the ID (e.g., ACME-123).

## Team Standards

- All code changes require PR review
- Tickets move through: To Do → In Progress → Review → Done
- Sprint planning is every Monday at 10am
- Deployments happen on Tuesdays and Thursdays

3. Create Custom Tools ​

See Custom Tools for the full guide. Here's a quick example:

php
<?php

// app/Extensions/Tools/JiraIntegration/ListTicketsTool.php
namespace App\Extensions\Tools\JiraIntegration;

use App\Models\User;
use Prism\Prism\Tool;

class ListTicketsTool extends Tool
{
    public function __construct(
        protected User $user,
        protected JiraClient $jira,
    ) {
        $this
            ->as('list_jira_tickets')
            ->for('List Jira tickets assigned to the user or in current sprint')
            ->withStringParameter('filter', 'Filter: mine, sprint, or project key (default: mine)')
            ->using($this);
    }

    public function __invoke(string $filter = 'mine'): string
    {
        $tickets = match ($filter) {
            'mine' => $this->jira->getAssignedTo($this->user->email),
            'sprint' => $this->jira->getCurrentSprint(),
            default => $this->jira->getByProject($filter),
        };

        if ($tickets->isEmpty()) {
            return 'No tickets found.';
        }

        return $tickets
            ->map(fn ($t) => "- [{$t->key}] {$t->summary} ({$t->status})")
            ->join("\n");
    }
}

4. Register Services ​

If your tools need external services, register them in a service provider:

php
<?php

// app/Providers/ExtensionsServiceProvider.php
namespace App\Providers;

use App\Extensions\Tools\JiraIntegration\JiraClient;
use Illuminate\Support\ServiceProvider;

class ExtensionsServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->singleton(JiraClient::class, function () {
            return new JiraClient(
                baseUrl: config('services.jira.url'),
                apiToken: config('services.jira.token'),
            );
        });
    }
}

Upgrade Strategy ​

When Iris releases updates, follow this process to incorporate them while preserving your customizations.

Before Upgrading ​

  1. Review the changelog - Check for breaking changes or new features
  2. Backup your database - Especially if migrations are included
  3. Note your customizations - List what you've changed

Upgrade Process ​

bash
# Fetch the latest changes
git fetch origin

# Review what's changed
git diff main..origin/main

# Merge or rebase
git merge origin/main
# or
git rebase origin/main

# If conflicts arise in customized files, resolve them
# Your custom config (iris-custom.php) shouldn't conflict
# Custom classes in app/Extensions/ shouldn't conflict

# Run migrations
php artisan migrate

# Rebuild frontend if needed
npm run build

# Clear caches
php artisan config:clear
php artisan view:clear

Handling Conflicts ​

Core prompts changed: If you've replaced the prompts array and core prompts changed, review the changes and update your custom config if needed.

New config options: New options in iris.php are automatically available. Override in iris-custom.php if you need different values.

Database migrations: Run migrations after every upgrade. Review migration files if you've modified the schema.

Keep your customizations on a separate branch:

bash
# Create a customizations branch
git checkout -b customizations main

# Make your customizations
# Commit them to the customizations branch

# When upgrading:
git checkout main
git pull origin main
git checkout customizations
git rebase main