Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 114 additions & 43 deletions agents/manifests/github_issue_triage_agent.json
Original file line number Diff line number Diff line change
@@ -1,45 +1,116 @@
{
"id": "github_issue_triage",
"name": "GitHub Issue Triage Agent",
"description": "Analyzes open GitHub issues, suggests labels, priority, and assignees.",
"category": "Developer Tools",
"creator": "AgentStore Team",
"version": "1.0.0",
"status": "published",
"rating": 4.7,
"downloads": 980,
"installs": 720,
"runs": 3100,
"tags": ["github", "developer", "triage", "issues"],
"tools_required": ["github_reader"],
"permissions_required": ["github.read"],
"inputs": {
"type": "object",
"properties": {
"repo": { "type": "string", "description": "owner/repo" },
"state": { "type": "string", "enum": ["open", "closed", "all"] }
}
},
"outputs": {
"type": "object",
"properties": {
"triaged_issues": {
"type": "array",
"items": {
"type": "object",
"properties": {
"issue_number": { "type": "integer" },
"suggested_labels": { "type": "array" },
"priority": { "type": "string" },
"suggested_assignee": { "type": "string" }
}
"id": "github_issue_triage",
"name": "GitHub Issue Triage Agent",
"description": "Reads open issues from any GitHub repository and automatically classifies each one by type (bug, enhancement, question, docs), assigns a priority level (high, medium, low), and recommends the best team or individual to assign it to — based on labels, keywords, and past assignee patterns. Saves hours of manual backlog grooming for engineering teams.",
"category": "Developer Tools",
"creator": "AgentStore Team",
"version": "1.0.0",
"status": "published",
"rating": 4.7,
"downloads": 980,
"installs": 720,
"runs": 3100,
"tags": [
"github",
"developer-tools",
"triage",
"issues",
"backlog",
"open-source",
"project-management",
"automation"
],
"tools_required": ["github_reader"],
"permissions_required": ["github.read"],
"permissions_explanation": {
"github.read": "Read-only access to public or private repository issues and labels. The agent never writes to GitHub, creates labels, or modifies any issue — it only reads and classifies."
},
"inputs": {
"type": "object",
"required": ["repo"],
"properties": {
"repo": {
"type": "string",
"description": "The GitHub repository to triage in owner/repo format. Example: 'microsoft/vscode' or 'myorg/myrepo'."
},
"state": {
"type": "string",
"enum": ["open", "closed", "all"],
"default": "open",
"description": "Which issues to fetch. Use 'open' for active backlog triage (recommended), 'closed' for historical review, or 'all' for both."
},
"max_issues": {
"type": "integer",
"default": 50,
"description": "Maximum number of issues to triage in one run. Increase for large backlogs (up to 200). Defaults to 50."
},
"label_filter": {
"type": "array",
"items": { "type": "string" },
"description": "Optional list of existing GitHub labels to filter by before triaging. Example: ['needs-triage', 'bug']. Leave empty to triage all issues."
}
}
}
}
},
"example_use_case": "Automatically triage new issues in an open-source repo every morning.",
"example_prompts": [
"Triage all open issues in myorg/myrepo",
"Which issues need urgent attention?"
]
}
},
"outputs": {
"type": "object",
"properties": {
"triaged_issues": {
"type": "array",
"description": "One entry per issue containing the classification results.",
"items": {
"type": "object",
"properties": {
"issue_number": {
"type": "integer",
"description": "The GitHub issue number (e.g. #42)."
},
"title": {
"type": "string",
"description": "The original issue title."
},
"issue_type": {
"type": "string",
"enum": ["bug", "enhancement", "question", "docs", "chore", "unknown"],
"description": "Classified issue type based on title, body, and existing labels."
},
"suggested_labels": {
"type": "array",
"items": { "type": "string" },
"description": "New labels the agent recommends applying. Example: ['bug', 'frontend', 'good-first-issue']."
},
"priority": {
"type": "string",
"enum": ["high", "medium", "low"],
"description": "Suggested fix priority. 'high' = crash/data loss/security. 'medium' = broken feature. 'low' = nice-to-have."
},
"suggested_assignee": {
"type": "string",
"description": "GitHub username recommended for this issue based on past ownership of similar areas. May be empty if no clear match."
},
"reasoning": {
"type": "string",
"description": "One-sentence explanation of why this priority and assignee were suggested."
}
}
}
},
"triage_summary": {
"type": "object",
"description": "Aggregate stats across all triaged issues.",
"properties": {
"total_triaged": { "type": "integer", "description": "Total number of issues processed." },
"high_priority_count": { "type": "integer", "description": "Number of issues classified as high priority." },
"by_type": { "type": "object", "description": "Breakdown of issue count by type. Example: { 'bug': 5, 'enhancement': 3 }." }
}
}
}
},
"example_use_case": "A maintainer of an open-source repo runs this agent every Monday morning on the previous week's new issues. The agent classifies each issue as bug or enhancement, flags 3 high-priority crashes, and recommends assignees — turning 30 minutes of manual triage into a 10-second review.",
"example_prompts": [
"Triage all open issues in myorg/myrepo and tell me which are highest priority",
"Which open issues in microsoft/vscode are bugs that need urgent attention?",
"Classify and assign the 20 newest issues in our repo",
"Find all untracked enhancement requests in myorg/backend and suggest labels",
"Give me a triage summary for techx/agentstore — how many bugs vs feature requests?"
]
}
139 changes: 109 additions & 30 deletions agents/runner/agent_runner.py
Original file line number Diff line number Diff line change
@@ -1,53 +1,132 @@
"""Mock agent runner — simulates agent execution without real API calls."""
"""
Mock agent runner — simulates agent execution without real API calls.
"""

from pathlib import Path
import json
from typing import Optional

import time
import uuid
from pathlib import Path
from typing import Optional, Any

TRACES_DIR = Path(__file__).parent.parent / "traces"

# Mock Tools (no real APIs)
def mock_fetch_emails(limit: int = 5) -> dict:
return {
"emails": [
{"from": "alice@example.com", "subject": "Q3 Budget Review", "snippet": "Please review the attached budget..."},
{"from": "bob@corp.com", "subject": "Team standup notes", "snippet": "Action items from today's standup..."},
{"from": "carol@vendor.com", "subject": "Invoice #4821", "snippet": "Please find attached invoice..."},
][:limit]
}

def mock_summarize_text(text: str) -> dict:
summary = text[:120].strip() + ("..." if len(text) > 120 else "")
return {"summary": summary, "word_count": len(text.split())}

# Tool dispatch table
TOOL_DISPATCH = {
"fetch_emails": mock_fetch_emails,
"summarize_text": mock_summarize_text,
}

# email_summarizer

def email_chain(user_input: dict, prev: dict) -> list[tuple[str, dict]]:
return [
("fetch_emails", {"limit": user_input.get("limit", 5)}),
("summarize_text", {
"text": " | ".join(
e["snippet"] for e in prev.get("fetch_emails", {}).get("emails", [])
)
}),
]

AGENT_CHAINS = {
"email_summarizer": email_chain,
}

# Final Output Builder
def build_final_output(agent_id: str, outputs: dict) -> str:
if agent_id == "email_summarizer":
emails = outputs["fetch_emails"]["emails"]
summary = outputs["summarize_text"]["summary"]
return f"Summarized {len(emails)} emails: {summary}"
return f"Agent '{agent_id}' executed successfully."

# Runner
def run_agent(agent_id: str, user_input: dict) -> dict:
"""Simulate running an agent with the given input.

TODO: Implement full simulation flow:
1. Load agent manifest
2. Identify required tools
3. Call mock tools (return canned responses)
4. Build trace steps
5. Generate final output
6. Save run history

For MVP skeleton, return a placeholder response.
"""
return {
run_id = str(uuid.uuid4())[:8]
start = time.time()
trace = []
outputs = {}

if agent_id not in AGENT_CHAINS:
return {
"agent_id": agent_id,
"status": "error",
"message": f"Agent '{agent_id}' not found.",
"input_received": user_input,
}

chain_builder = AGENT_CHAINS[agent_id]
tool_steps = chain_builder(user_input, {})

for i, (tool_id, _) in enumerate(tool_steps):
refreshed_steps = chain_builder(user_input, outputs)
_, kwargs = refreshed_steps[i]

tool_fn = TOOL_DISPATCH[tool_id]
t0 = time.time()
output = tool_fn(**kwargs)
latency = int((time.time() - t0) * 1000) + 15

outputs[tool_id] = output
trace.append({
"step": i + 1,
"tool_id": tool_id,
"input": kwargs,
"output": output,
"status": "success",
"latency_ms": latency,
})

final_output = build_final_output(agent_id, outputs)
duration = int((time.time() - start) * 1000)

result = {
"run_id": run_id,
"agent_id": agent_id,
"status": "simulated",
"message": "TODO: Implement agent runner simulation",
"input_received": user_input,
"user_input": user_input,
"final_output": final_output,
"status": "success",
"trace": trace,
"duration_ms": duration,
}

_save_trace(result)
return result

# Trace Loader
def get_trace_for_agent(agent_id: str) -> Optional[dict]:
"""Load a pre-built mock trace for an agent if one exists.

TODO: Support loading traces by run_id
TODO: Support listing all traces for an agent
"""
trace_map = {
"email_summarizer": "email_summarizer_trace.json",
"github_issue_triage": "github_issue_triage_trace.json",
"meeting_notes": "meeting_notes_trace.json",
}

trace_file = trace_map.get(agent_id)
if not trace_file:
return None

trace_path = TRACES_DIR / trace_file
if not trace_path.exists():
path = TRACES_DIR / trace_file
if not path.exists():
return None

with open(trace_path, encoding="utf-8") as f:
with open(path, encoding="utf-8") as f:
return json.load(f)

# Save Trace
def _save_trace(result: dict) -> None:
TRACES_DIR.mkdir(parents=True, exist_ok=True)
path = TRACES_DIR / f"{result['agent_id']}_{result['run_id']}.json"
path.write_text(json.dumps(result, indent=2), encoding="utf-8")

29 changes: 13 additions & 16 deletions backend/app/main.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"""AgentStore Backend — FastAPI placeholder application."""
"""AgentStore Backend — FastAPI application."""

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from app.routers import agents, tools, runs, ratings
from app.routers import agents, tools, runs, ratings, health

app = FastAPI(
title="AgentStore API",
description="The App Store for Agents — backend API (skeleton)",
description="The App Store for Agents — backend API",
version="0.1.0",
)

Expand All @@ -19,22 +19,19 @@
allow_headers=["*"],
)

app.include_router(agents.router, prefix="/agents", tags=["agents"])
app.include_router(tools.router, prefix="/tools", tags=["tools"])
app.include_router(runs.router, prefix="/agents", tags=["runs"])
app.include_router(ratings.router, prefix="/agents", tags=["ratings"])
app.include_router(agents.router, prefix="/agents", tags=["agents"])
app.include_router(tools.router, prefix="/tools", tags=["tools"])
app.include_router(runs.router, prefix="/agents", tags=["runs"])
app.include_router(ratings.router, prefix="/agents", tags=["ratings"])
app.include_router(health.router, prefix="/health", tags=["health"])


@app.get("/")
def root():
return {
"name": "AgentStore API",
"name": "AgentStore API",
"tagline": "The App Store for Agents",
"status": "skeleton",
"docs": "/docs",
}


@app.get("/health")
def health():
return {"status": "ok"}
"version": "0.1.0",
"docs": "/docs",
"health": "/health",
}
Loading