> ## Documentation Index
> Fetch the complete documentation index at: https://amd-gaia.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# JiraAgent

<Info>
  **Source Code:** [`hub/agents/python/jira/gaia_agent_jira/agent.py`](https://github.com/amd/gaia/blob/main/hub/agents/python/jira/gaia_agent_jira/agent.py)
</Info>

<Note>
  **Component:** JiraAgent - Natural Language Jira Interface
  **Module:** `gaia_agent_jira.agent`
  **Inherits:** Agent
  **Model:** Qwen3.5-35B-A3B-GGUF (default)
</Note>

***

## Overview

JiraAgent provides a natural language interface to Jira with automatic configuration discovery. It adapts to any Jira instance by discovering projects, issue types, statuses, and priorities dynamically, then translates natural language to JQL queries and API calls.

**Key Features:**

* Automatic Jira instance discovery
* Natural language to JQL translation
* Async API operations (aiohttp)
* Dynamic system prompt generation
* WebUI integration (Electron)
* Portable across Jira instances

***

## Requirements

### Functional Requirements

1. **Jira Discovery**
   * Discover projects (keys, names)
   * Discover issue types (excluding subtasks)
   * Discover statuses
   * Discover priorities
   * Cache discovered configuration

2. **Search Operations**
   * Natural language → JQL translation
   * Execute JQL searches via API
   * Parse and format results
   * Handle pagination

3. **CRUD Operations**
   * Create issues with validation
   * Update issues (summary, description, priority, status)
   * Proper error handling for invalid fields

4. **Configuration Management**
   * Accept pre-discovered config
   * Initialize() method for discovery
   * get\_config() to retrieve cached config

***

## API Specification

### JiraAgent Class

```python theme={null}
class JiraAgent(Agent):
    """Intelligent Jira agent with automatic configuration discovery."""

    def __init__(
        self,
        jira_config: Dict[str, Any] = None,
        **kwargs,
    ):
        """
        Initialize Jira agent.

        Args:
            jira_config: Pre-discovered Jira configuration {
                "projects": [{"key": "PROJ", "name": "Project Name"}],
                "issue_types": ["Bug", "Task", "Story"],
                "statuses": ["To Do", "In Progress", "Done"],
                "priorities": ["Highest", "High", "Medium", "Low"]
            }
            **kwargs: Forwarded to the base Agent — e.g. max_steps
                (default: the global default), model_id (default:
                Qwen3.5-35B-A3B-GGUF), silent_mode, debug, show_prompts.
        """
        pass

    def initialize(self) -> Dict[str, Any]:
        """
        Discover and cache Jira configuration.

        Returns:
            Discovered configuration dict

        Raises:
            Exception: If credentials invalid or API fails
        """
        pass

    def get_config(self) -> Optional[Dict[str, Any]]:
        """Get cached Jira configuration."""
        pass

    # Tools
    @tool
    def jira_search(
        jql: str = "created >= -30d ORDER BY updated DESC",
        max_results: int = None,
        fields: str = None
    ) -> Dict[str, Any]:
        """Search Jira issues using JQL query."""
        pass

    @tool
    def jira_create(
        summary: str,
        description: str = "",
        issue_type: str = "Task",
        priority: str = None,
        project: str = None
    ) -> Dict[str, Any]:
        """Create new Jira issue."""
        pass

    @tool
    def jira_update(
        issue_key: str,
        summary: str = None,
        description: str = None,
        priority: str = None,
        status: str = None
    ) -> Dict[str, Any]:
        """Update existing Jira issue."""
        pass
```

### Environment Variables

```bash theme={null}
# Required
ATLASSIAN_SITE_URL=https://company.atlassian.net
ATLASSIAN_API_KEY=your_api_token_here
ATLASSIAN_USER_EMAIL=you@company.com
```

***

## Implementation Details

### Discovery Process

```python theme={null}
async def _discover_jira_config(self) -> Dict[str, Any]:
    """Discover Jira instance configuration via API."""

    site_url, api_key, user_email = self._get_jira_credentials()
    auth_header = base64.b64encode(f"{user_email}:{api_key}".encode()).decode()

    headers = {
        "Authorization": f"Basic {auth_header}",
        "Content-Type": "application/json"
    }

    config = {
        "projects": [],
        "issue_types": [],
        "statuses": [],
        "priorities": []
    }

    async with aiohttp.ClientSession() as session:
        # Get projects
        async with session.get(f"{site_url}/rest/api/3/project", headers=headers) as resp:
            projects = await resp.json()
            config["projects"] = [{"key": p["key"], "name": p["name"]} for p in projects]

        # Get issue types (exclude subtasks)
        async with session.get(f"{site_url}/rest/api/3/issuetype", headers=headers) as resp:
            types = await resp.json()
            config["issue_types"] = [t["name"] for t in types if not t.get("subtask")]

        # Get statuses
        async with session.get(f"{site_url}/rest/api/3/status", headers=headers) as resp:
            statuses = await resp.json()
            config["statuses"] = [s["name"] for s in statuses]

        # Get priorities
        async with session.get(f"{site_url}/rest/api/3/priority", headers=headers) as resp:
            priorities = await resp.json()
            config["priorities"] = [p["name"] for p in priorities]

    return config
```

### Dynamic System Prompt

```python theme={null}
def _get_system_prompt(self) -> str:
    """Generate system prompt with discovered configuration."""

    prompt = """You are a Jira assistant that responds ONLY in JSON format.

**JQL Basics:**
- Current user: assignee = currentUser()
- By key: key = "PROJ-123"
- Operators: AND, OR, NOT"""

    # Add discovered configuration
    if self._jira_config:
        if self._jira_config.get("projects"):
            keys = [p["key"] for p in self._jira_config["projects"]]
            prompt += f"\n- Projects: {', '.join(keys)}"

        if self._jira_config.get("issue_types"):
            prompt += f"\n- Types: {', '.join(self._jira_config['issue_types'])}"

        if self._jira_config.get("priorities"):
            prompt += f"\n- Priorities: {', '.join(self._jira_config['priorities'])}"

        if self._jira_config.get("statuses"):
            prompt += f"\n- Statuses: {', '.join(self._jira_config['statuses'])}"

    prompt += """

**JQL QUOTING RULES:**
- Single words: NO quotes → status = Done
- Multiple words: DOUBLE quotes → status = "In Progress"
- NEVER use single quotes
- Functions: NO quotes → assignee = currentUser()"""

    return prompt
```

### JQL Search Execution

```python theme={null}
async def _execute_jira_search_async(
    self,
    jql: str,
    max_results: int = None,
    fields: str = None
) -> Dict[str, Any]:
    """Execute Jira search API call."""

    site_url, api_key, user_email = self._get_jira_credentials()
    auth_header = base64.b64encode(f"{user_email}:{api_key}".encode()).decode()

    headers = {
        "Authorization": f"Basic {auth_header}",
        "Content-Type": "application/json"
    }

    params = {"jql": jql}
    if max_results:
        params["maxResults"] = max_results
    if fields:
        params["fields"] = fields
    else:
        params["fields"] = "key,summary,status,priority,issuetype,assignee"

    async with aiohttp.ClientSession() as session:
        url = f"{site_url}/rest/api/3/search/jql"
        async with session.get(url, headers=headers, params=params) as response:
            response.raise_for_status()
            data = await response.json()

            issues = []
            for issue in data.get("issues", []):
                fields = issue.get("fields", {})
                issues.append({
                    "key": issue.get("key"),
                    "summary": fields.get("summary"),
                    "status": fields.get("status", {}).get("name"),
                    "priority": fields.get("priority", {}).get("name"),
                    "type": fields.get("issuetype", {}).get("name"),
                    "assignee": fields.get("assignee", {}).get("displayName", "Unassigned")
                })

            return {
                "issues": issues,
                "total": data.get("total", len(issues)),
                "jql": jql
            }
```

***

## Testing Requirements

### Unit Tests

```python theme={null}
# tests/agents/test_jira_agent.py

def test_jira_agent_initialization():
    """Test JiraAgent initializes."""
    config = {
        "projects": [{"key": "TEST", "name": "Test Project"}],
        "issue_types": ["Bug", "Task"],
        "statuses": ["To Do", "Done"],
        "priorities": ["High", "Low"]
    }
    agent = JiraAgent(jira_config=config, silent_mode=True)
    assert agent.get_config() == config

@pytest.mark.asyncio
async def test_jira_search():
    """Test JQL search execution."""
    agent = JiraAgent(silent_mode=True)
    # Mock aiohttp session
    result = await agent._execute_jira_search_async("assignee = currentUser()")
    assert "issues" in result
    assert "total" in result
    assert "jql" in result

def test_dynamic_system_prompt():
    """Test system prompt includes discovered config."""
    config = {
        "projects": [{"key": "PROJ", "name": "Project"}],
        "issue_types": ["Bug"],
        "priorities": ["High"]
    }
    agent = JiraAgent(jira_config=config, silent_mode=True)
    prompt = agent._get_system_prompt()

    assert "PROJ" in prompt
    assert "Bug" in prompt
    assert "High" in prompt
```

***

## Usage Examples

### Example 1: CLI Usage

```bash theme={null}
# Set credentials
export ATLASSIAN_SITE_URL=https://company.atlassian.net
export ATLASSIAN_API_KEY=your_token
export ATLASSIAN_USER_EMAIL=you@company.com

# Use agent
gaia jira "Show me high priority bugs assigned to me"
```

### Example 2: Python API

```python theme={null}
from gaia_agent_jira.agent import JiraAgent

# Initialize and discover
agent = JiraAgent()
config = agent.initialize()

print(f"Found {len(config['projects'])} projects")

# Natural language query
result = agent.process_query("Show my open issues")
print(result["result"])
```

### Example 3: Pre-configured Setup

```python theme={null}
# Use pre-discovered config (faster initialization)
config = {
    "projects": [{"key": "GAIA", "name": "GAIA Project"}],
    "issue_types": ["Bug", "Task", "Story"],
    "statuses": ["To Do", "In Progress", "Done"],
    "priorities": ["Highest", "High", "Medium", "Low"]
}

agent = JiraAgent(jira_config=config)
result = agent.process_query("Create a bug: Login fails")
```

***

## Related Specifications

* [agent-base](/docs/spec/agent-base) - Agent architecture
* [electron-integration](/docs/spec/electron-integration) - WebUI integration
* [docker-agent](/docs/spec/docker-agent) - Similar agent pattern

***

*JiraAgent Technical Specification*

***

<small style="color: #666;">
  **License**

  Copyright(C) 2024-2026 Advanced Micro Devices, Inc. All rights reserved.

  SPDX-License-Identifier: MIT
</small>
