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' => [
        'model' => 'claude-opus-4',  // Override the default model
    ],

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

How Merging Works

Different config keys use different merge strategies:

KeyStrategyDescription
promptsReplaceYour list replaces the core list entirely
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 that render in order. To customize, you replace the entire list (order matters for system prompts):

php
// config/iris-custom.php
return [
    'prompts' => [
        App\Prompts\IrisStaticPrompt::class,          // Keep core static prompt
        App\Prompts\MemoryPrompt::class,              // Recalled memories
        App\Prompts\SummaryPrompt::class,             // Conversation summaries
        App\Prompts\WorkContextPrompt::class,         // Add your context
        App\Prompts\CalendarPrompt::class,            // Calendar events
        App\Prompts\CurrentTimePrompt::class,         // Current date/time
    ],
];

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
            ),
        ]);
    }

    // Optional: enable caching for static content
    public function providerOptions(): array
    {
        return [
            'cacheType' => 'ephemeral',
        ];
    }
}

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' => [
        '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' => [
        App\Extensions\Prompts\DevTeamStaticPrompt::class,  // Custom identity
        App\Prompts\MemoryPrompt::class,
        App\Prompts\SummaryPrompt::class,
        App\Extensions\Prompts\ProjectContextPrompt::class, // Current projects
        App\Extensions\Prompts\TeamContextPrompt::class,    // Team info
        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();
    }

    public function providerOptions(): array
    {
        return ['cacheType' => 'ephemeral'];
    }
}
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