Forums

Articles
Create
cancel
Showing results for 
Search instead for 
Did you mean: 

Does anyone else feel like Rovo is just A, not AI?

daften
April 1, 2026

I have tried to create several agents. Lookup information works okay, not great, but okay.

But taking actions, holy moly ...

I wanted a personal agent to help me with task management. I use a jira project to keep track of my tasks. The agent in this phasse would be simple: look up information in my tasks and create tasks. To gradually make this more complex later on.

But even this step, Rovo fails at spectacularly. If I ask it to create a task in my personal project and the scenario instructions contain items such as "Always check which category the task belongs to. Suggest an existing category if possible, or propose a new one if none fit." it still doesn't follow this instruction and doesn't ask for a category. When trying to get it to update the category later, it always said it did it, but never does it. It conflates Category on work item level with Project category and confidently says that Category on issue level just "shows" the project category.

It also doesn't fill out any other structured fields like due date properly.

It's like Atlassian Rovo can't work with Atlassian Jira, it's amazing.

In the meantime, if I create a local agent with Claude code using MCP, I can do more things, and better than with the built-in Atlassian AI Tool.

Am I alone in this? Am I missing crucial information or steps I need to take?

2 answers

1 accepted

0 votes
Answer accepted
Arkadiusz Wroblewski
Community Champion
April 1, 2026

Hello @daften 

You're not alone. this is a pretty common experience right now. Rovo agents are genuinely useful for read operations and simple lookups, but when it comes to write actions involving structured fields, multi-step logic, or anything that requires understanding Jira's field model deeply, it still falls short of what you'd expect.

The category/project category confusion you described is a good example of a known limitation, Rovo doesn't always distinguish cleanly between field types and their scope, and it tends to hallucinate success on write actions rather than flagging that it couldn't complete them properly.

A few things that can help in agent instructions are being very explicit about field names exactly as they appear in Jira, breaking instructions into smaller discrete steps rather than compound conditions, and adding confirmation checks like "after creating the work item, retrieve it and confirm the field values".

But to be honest, Rovo agents are still maturing, and your comparison with a Claude MCP setup is fair, the flexibility isn't quite there yet natively. Worth submitting feedback directly to Atlassian via the thumbs down on Rovo responses, as that data does feed into their improvement roadmap.

Dr Valeri Colon _Connect Centric_
Community Champion
April 2, 2026

Hi @daften! I second much of what @Arkadiusz Wroblewski has said.  What you’re seeing lines up with how Rovo works today.

One key thing: Rovo isn’t the AI itself. It’s an interface that connects to underlying LLMs + Atlassian data/tools, so behavior depends on how well those models, tools, and permissions line up. Rovo = AI Interface.

Where it struggles today:

  • Structured field updates (category, due date) can be inconsistent
  • It can misinterpret fields (project vs issue-level)
  • “Taking action” is still more limited than “answering questions”

Also, when it says something worked but didn’t, that’s often due to tooling/permission gaps or partial execution, not just the model making things up or hallucination. Your MCP + Claude setup feels better because it’s more explicit and controlled. For now, Rovo works best as assistive (suggest, draft), with automation/API handling precise updates.

Like # people like this
0 votes
Birendra Pratap Singh
Banned
May 3, 2026

Alright—now we turn this into something you can actually run.
I’ll give you a clean controller skeleton that enforces:
schema validation
tool contracts
deterministic flow
verification loop
I’ll do this in Node.js (TypeScript-style JS) first (best fit for MCP-style agents), and then a Python version.
🧠 High-Level Architecture

User → Controller → LLM (structured output only)

Tools Layer → Jira API

Verification Layer
The controller is the brain, not the LLM.
🟦 Node.js Controller Skeleton
1. Install deps
Bash
npm install axios zod
2. jiraClient.js (low-level API wrapper)
JavaScript
import axios from "axios";

const JIRA_BASE = process.env.JIRA_BASE_URL;
const AUTH = {
username: process.env.JIRA_EMAIL,
password: process.env.JIRA_API_TOKEN
};

export async function createIssue(payload) {
const res = await axios.post(
`${JIRA_BASE}/rest/api/3/issue`,
payload,
{ auth: AUTH }
);
return res.data;
}

export async function updateIssue(issueKey, fields) {
await axios.put(
`${JIRA_BASE}/rest/api/3/issue/${issueKey}`,
{ fields },
{ auth: AUTH }
);
}

export async function getIssue(issueKey) {
const res = await axios.get(
`${JIRA_BASE}/rest/api/3/issue/${issueKey}`,
{ auth: AUTH }
);
return res.data;
}
3. schema.js (strict validation)
JavaScript
import { z } from "zod";

export const TaskSchema = z.object({
title: z.string().min(1),
description: z.string().optional(),
category: z.string().min(1),
due_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
priority: z.enum(["Low", "Medium", "High"]).optional(),
project_key: z.string().min(1)
});
4. fieldMapping.js
JavaScript
export const FIELD_MAP = {
title: "summary",
description: "description",
category: "customfield_12345",
due_date: "duedate",
priority: "priority"
};
5. tools.js (your “deterministic” tools)
JavaScript
import { createIssue, updateIssue, getIssue } from "./jiraClient.js";
import { FIELD_MAP } from "./fieldMapping.js";

export async function createTask(task) {
const fields = {
project: { key: task.project_key },
issuetype: { name: "Task" }
};

for (const key in FIELD_MAP) {
if (task[key]) {
fields[FIELD_MAP[key]] = task[key];
}
}

return await createIssue({ fields });
}

export async function updateTask(issueKey, updates) {
const fields = {};

for (const key in updates) {
if (FIELD_MAP[key]) {
fields[FIELD_MAP[key]] = updates[key];
}
}

await updateIssue(issueKey, { fields });
}

export async function verifyTask(issueKey, expected) {
const issue = await getIssue(issueKey);

const actual = {
category: issue.fields[FIELD_MAP.category],
due_date: issue.fields[FIELD_MAP.due_date],
priority: issue.fields[FIELD_MAP.priority]
};

return { actual };
}
6. controller.js (this is the important part)
JavaScript
import { TaskSchema } from "./schema.js";
import { createTask, updateTask, verifyTask } from "./tools.js";

// Simulated category source
const VALID_CATEGORIES = ["Bug", "Feature", "Tech Debt"];

export async function handleCreateTask(input) {
// 1. Validate structure
const parsed = TaskSchema.safeParse(input);

if (!parsed.success) {
return {
status: "missing_fields",
errors: parsed.error.issues
};
}

const task = parsed.data;

// 2. Validate category
if (!VALID_CATEGORIES.includes(task.category)) {
return {
status: "invalid_category",
message: `Invalid category. Available: ${VALID_CATEGORIES.join(", ")}`
};
}

// 3. Create task
const created = await createTask(task);
const issueKey = created.key;

// 4. Verify
const { actual } = await verifyTask(issueKey, task);

if (actual.category !== task.category) {
return {
status: "verification_failed",
issueKey,
expected: task.category,
actual: actual.category
};
}

return {
status: "success",
issueKey
};
}
7. LLM Integration (important pattern)
You DO NOT let the LLM call Jira.
You let it only produce structured JSON like:
JSON
{
"intent": "create_task",
"data": {
"title": "Fix login bug",
"category": "Bug",
"project_key": "PRJ"
}
}
Then your controller decides:
missing fields → ask user
valid → execute
🟨 Python Version (lean but solid)
Install
Bash
pip install requests pydantic
jira_client.py
Python
import requests
import os

BASE = os.getenv("JIRA_BASE_URL")
AUTH = (os.getenv("JIRA_EMAIL"), os.getenv("JIRA_API_TOKEN"))

def create_issue(fields):
res = requests.post(
f"{BASE}/rest/api/3/issue",
json={"fields": fields},
auth=AUTH
)
return res.json()

def get_issue(issue_key):
res = requests.get(
f"{BASE}/rest/api/3/issue/{issue_key}",
auth=AUTH
)
return res.json()
schema.py
Python
from pydantic import BaseModel, validator
import re

class Task(BaseModel):
title: str
category: str
project_key: str
due_date: str | None = None
priority: str | None = None

@validator("due_date")
def validate_date(cls, v):
if v and not re.match(r"\d{4}-\d{2}-\d{2}", v):
raise ValueError("Invalid date format")
return v
controller.py
Python
from schema import Task
from jira_client import create_issue, get_issue

FIELD_MAP = {
"title": "summary",
"category": "customfield_12345",
"due_date": "duedate",
"priority": "priority"
}

VALID_CATEGORIES = ["Bug", "Feature", "Tech Debt"]


def handle_create_task(data):
# 1. Validate schema
try:
task = Task(**data)
except Exception as e:
return {"status": "error", "message": str(e)}

# 2. Validate category
if task.category not in VALID_CATEGORIES:
return {
"status": "invalid_category",
"allowed": VALID_CATEGORIES
}

# 3. Map fields
fields = {
"project": {"key": task.project_key},
"issuetype": {"name": "Task"},
"summary": task.title
}

if task.category:
fields[FIELD_MAP["category"]] = task.category

if task.due_date:
fields[FIELD_MAP["due_date"]] = task.due_date

# 4. Create
created = create_issue(fields)
issue_key = created["key"]

# 5. Verify
issue = get_issue(issue_key)

actual_category = issue["fields"].get(FIELD_MAP["category"])

if actual_category != task.category:
return {
"status": "verification_failed",
"expected": task.category,
"actual": actual_category
}

return {
"status": "success",
"issue_key": issue_key
}
🚨 Final Reality Check
This setup gives you:
zero hallucinated updates
enforced required fields
real validation
deterministic execution
But it requires discipline:
keep logic OUT of prompts
keep logic IN controller
treat LLM like a parser, not a decision-makerhttps://bpsinghcommx.com.discourse.group 

Suggest an answer

Log in or Sign up to answer
TAGS
AUG Leaders

Atlassian Community Events