# How to Load Agent Skills from URLs or Community Contributions in AI Edge Gallery

> Learn to load Agent Skills from URLs or community contributions in the AI Edge Gallery. Easily add new skills using SkillManagerViewModel and SkillAllowlist.

- Repository: [google-ai-edge/gallery](https://github.com/google-ai-edge/gallery)
- Tags: how-to-guide
- Published: 2026-04-06

---

**The AI Edge Gallery app supports three distinct methods to load Agent Skills: fetching directly from approved URLs via `SkillManagerViewModel.validateAndAddSkillFromUrl()`, importing from a curated community allowlist defined in `SkillAllowlist`, or importing from local device storage, with all paths converging on parsing [`SKILL.md`](https://github.com/google-ai-edge/gallery/blob/main/SKILL.md) metadata into protocol buffer objects persisted through DataStore.**

The `google-ai-edge/gallery` repository implements a modular skill system that allows users to extend on-device LLM capabilities without rebuilding the app. To load Agent Skills from URLs or community contributions, the codebase provides dedicated `ViewModel` classes and UI components that handle validation, security checks, and persistence.

## Agent Skill Architecture and Data Model

Every Agent Skill is defined by a [`SKILL.md`](https://github.com/google-ai-edge/gallery/blob/main/SKILL.md) file that follows a strict front-matter schema containing `name`, `description`, and optional `metadata`. According to the source code in `Android/src/app/src/main/proto/skill.proto`, this markdown is parsed into a `Skill` protobuf object (`com.google.ai.edge.gallery.proto.Skill`).

The application persists skills using a `DataStore<Skills>` implementation configured in `AppModule.provideSkillsDataStore`. The `SkillsSerializer` implements `kotlinx.serialization.Serializer<Skills>` to handle protobuf read/write operations to the app's internal storage, ensuring skills survive process restarts.

## Loading Skills from a URL

The primary entry point for remote skill loading is `SkillManagerViewModel.validateAndAddSkillFromUrl()`. This method coordinates URL normalization, markdown fetching, and proto conversion.

### URL Normalization and Security Checks

Before fetching content, the app validates the host against an allowlist defined in [`AddSkillFromUrlDialog.kt`](https://github.com/google-ai-edge/gallery/blob/main/AddSkillFromUrlDialog.kt). The `APPROVED_SKILL_HOSTS` set currently includes `google-ai-edge.github.io`, and the `isHostApproved()` function normalizes URLs using `java.net.URI` to prevent malicious redirects.

### The Fetching and Parsing Flow

When `validateAndAddSkillFromUrl()` executes, it performs the following operations:

1. Normalizes the user-provided URL and appends [`/SKILL.md`](https://github.com/google-ai-edge/gallery/blob/main//SKILL.md)
2. Fetches the markdown content using `java.net.URL.openConnection()`
3. Parses the markdown via `convertSkillMdToProto()` with `builtIn = false` and `selected = true` flags
4. Persists the resulting proto through `DataStoreRepository.setSkills()`

```kotlin
// Inside SkillManagerViewModel.validateAndAddSkillFromUrl()
val skillMdUrl = "$normalizedUrl/SKILL.md"
val mdContent = URL(skillMdUrl).openConnection()
                  .getInputStream().reader().readText()
val (skillProto, errors) = convertSkillMdToProto(
    mdContent, 
    builtIn = false,
    selected = true,
    skillUrl = normalizedUrl
)
addSkill(skill = skillProto, addToDataStore = true)

```

The method updates the in-memory UI state (`_uiState`) and triggers persistence, making the skill immediately available for agent chat sessions.

## Importing Featured Community Skills

For curated community contributions, the app implements an allowlist system centered on `SkillAllowlist` data classes found in [`Android/src/app/src/main/java/com/google/ai/edge/gallery/data/SkillAllowlist.kt`](https://github.com/google-ai-edge/gallery/blob/main/Android/src/app/src/main/java/com/google/ai/edge/gallery/data/SkillAllowlist.kt).

### The Allowlist System

On application startup, `SkillManagerViewModel.loadSkillAllowlist()` downloads a JSON file from `SKILL_ALLOWLIST_URL` and deserializes it into a list of `AllowedSkill` objects. The JSON schema mirrors the `SkillAllowlist` data class structure, enabling type-safe parsing of community-vetted contributions.

The UI layer in [`AddSkillFromFeaturedListBottomSheet.kt`](https://github.com/google-ai-edge/gallery/blob/main/AddSkillFromFeaturedListBottomSheet.kt) exposes these entries through `uiState.featuredSkills`, presenting users with a curated selection of third-party capabilities.

### Adding Featured Skills

When a user selects a featured skill, `SkillManagerViewModel.addSkillFromFeatured()` executes:

- Downloads the skill's remote assets (HTML/JS scripts)
- Copies files into the private `files/skills/` directory
- Generates and writes a local [`SKILL.md`](https://github.com/google-ai-edge/gallery/blob/main/SKILL.md) file
- Registers the skill via the same proto conversion path as URL-based loading

```kotlin
@Composable
fun FeaturedSkillPicker(viewModel: SkillManagerViewModel) {
    val uiState by viewModel.uiState.collectAsState()
    LazyColumn {
        items(uiState.featuredSkills) { allowedSkill ->
            ListItem(
                headlineText = { Text(allowedSkill.name) },
                supportingText = { Text(allowedSkill.description) },
                trailingContent = {
                    IconButton(
                        onClick = { viewModel.addSkillFromFeatured(allowedSkill) }
                    ) {
                        Icon(Icons.Default.Add, contentDescription = null)
                    }
                }
            )
        }
    }
}

```

## Loading Skills from Local Folders

Users can also import skills from device storage using `SkillManagerViewModel.checkLocalSkillExisted()`. This flow leverages Android's file picker to select a folder containing [`SKILL.md`](https://github.com/google-ai-edge/gallery/blob/main/SKILL.md) and optional `scripts/` subdirectories. The selected folder is copied into `context.filesDir/skills/` and registered through the standard proto parsing pipeline without requiring network access.

## Security Model for External Skills

The implementation includes security measures to mitigate malicious content. Only hosts listed in `APPROVED_SKILL_HOSTS` are allowed without displaying a disclaimer dialog. By default, this list contains `google-ai-edge.github.io` as defined in [`AddSkillFromUrlDialog.kt`](https://github.com/google-ai-edge/gallery/blob/main/AddSkillFromUrlDialog.kt) (lines 60-66). Unknown hosts trigger additional validation warnings before permitting skill installation, ensuring users explicitly approve code from unverified sources.

## Summary

- **URL-based loading** utilizes `SkillManagerViewModel.validateAndAddSkillFromUrl()` to fetch [`SKILL.md`](https://github.com/google-ai-edge/gallery/blob/main/SKILL.md) from approved hosts, parse it via `convertSkillMdToProto()`, and persist through `DataStoreRepository`.
- **Community contributions** are managed through `SkillAllowlist` JSON deserialization in `loadSkillAllowlist()` and installed via `addSkillFromFeatured()` into the private `files/skills/` directory.
- **Local imports** copy user-selected folders into internal storage using `checkLocalSkillExisted()` and follow identical proto registration flows.
- **Security validation** enforces host allowlists through `APPROVED_SKILL_HOSTS` checks in [`AddSkillFromUrlDialog.kt`](https://github.com/google-ai-edge/gallery/blob/main/AddSkillFromUrlDialog.kt) before permitting external skill execution.

## Frequently Asked Questions

### What file format defines an Agent Skill?

Agent Skills are defined by a [`SKILL.md`](https://github.com/google-ai-edge/gallery/blob/main/SKILL.md) file located at the root of the skill directory. This markdown file contains YAML front-matter specifying the skill `name`, `description`, and optional metadata. The file is parsed into a protocol buffer object defined in `Android/src/app/src/main/proto/skill.proto` using the `convertSkillMdToProto()` utility function.

### How does the app verify that a URL is safe for loading skills?

The app validates URLs against an `APPROVED_SKILL_HOSTS` list implemented in [`AddSkillFromUrlDialog.kt`](https://github.com/google-ai-edge/gallery/blob/main/AddSkillFromUrlDialog.kt). The validation uses `java.net.URI` normalization to extract and compare hosts, with `google-ai-edge.github.io` included as a default approved domain. URLs from unapproved hosts require explicit user confirmation before fetching content.

### Where are downloaded skill files stored on the device?

Downloaded skill assets are written to the application's private `files/skills/` directory within `context.filesDir`. This location is accessible via the `SkillManagerViewModel` methods that handle both featured skill downloads and local folder imports, ensuring that skill scripts and metadata remain sandboxed from other applications.

### Can I programmatically add a skill without using the UI dialogs?

Yes. Direct programmatic access is available through `SkillManagerViewModel.validateAndAddSkillFromUrl()`, which accepts a URL string and callback handlers for success and validation error states. This method performs the complete workflow including markdown fetching, proto conversion, and DataStore persistence without requiring interaction with [`AddSkillFromUrlDialog.kt`](https://github.com/google-ai-edge/gallery/blob/main/AddSkillFromUrlDialog.kt) or [`AddSkillFromFeaturedListBottomSheet.kt`](https://github.com/google-ai-edge/gallery/blob/main/AddSkillFromFeaturedListBottomSheet.kt).