Skip to content
Closed
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
24 changes: 22 additions & 2 deletions agent/prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def _load_default_prompt() -> str:
- Project context and files
- Shell commands and code editing tools
- A sandboxed, git-backed workspace
- Project-specific rules and conventions from the repository's `AGENTS.md` file (read after cloning — see Repository Setup)"""
- Project-specific rules and conventions (see Repository Rules section below, if present)"""


REPO_SETUP_SECTION = """---
Expand All @@ -77,11 +77,20 @@ def _load_default_prompt() -> str:

4. **Checkout your branch** — Always fetch and checkout your branch before making any changes.

5. **Read and follow AGENTS.md** — After cloning, check if `AGENTS.md` exists at the repository root (`{working_dir}/<repo>/AGENTS.md`). If it exists, you MUST read it immediately and treat its contents as **mandatory rules** for all work in that repository. AGENTS.md contains project-specific conventions, coding standards, and constraints that override your default behavior. Violating AGENTS.md rules is equivalent to violating the system prompt. If AGENTS.md does not exist, skip this step.
5. **Follow repository rules** — If a "Repository Rules (AGENTS.md)" section is included below, you MUST treat its contents as **mandatory rules** for all work in that repository. These rules override your default behavior. Violating them is equivalent to violating the system prompt. If no such section is present, skip this step.

You MUST complete ALL of these steps before doing any other work. The sandbox starts clean — no repo is pre-cloned."""


AGENTS_MD_SECTION = """---

### Repository Rules (AGENTS.md)

The following rules were loaded from the repository's `AGENTS.md` file. You MUST follow them for all work in this repository. These rules override your default behavior.

{agents_md_content}"""


FILE_MANAGEMENT_SECTION = """---

### File & Code Management
Expand Down Expand Up @@ -321,6 +330,7 @@ def _load_default_prompt() -> str:
+ TASK_OVERVIEW_SECTION
+ "{default_prompt_section}"
+ REPO_SETUP_SECTION
+ "{agents_md_section}"
+ FILE_MANAGEMENT_SECTION
+ TASK_EXECUTION_SECTION
+ TOOL_USAGE_SECTION
Expand All @@ -339,11 +349,21 @@ def construct_system_prompt(
working_dir: str,
linear_project_id: str = "",
linear_issue_number: str = "",
agents_md_content: str = "",
) -> str:
default_prompt_section = _load_default_prompt()

if agents_md_content:
# Escape curly braces in user content so .format() doesn't choke
escaped = agents_md_content.replace("{", "{{").replace("}", "}}")
agents_md_section = AGENTS_MD_SECTION.format(agents_md_content=escaped)
else:
agents_md_section = ""

return SYSTEM_PROMPT_TEMPLATE.format(
working_dir=working_dir,
linear_project_id=linear_project_id or "<PROJECT_ID>",
linear_issue_number=linear_issue_number or "<ISSUE_NUMBER>",
default_prompt_section=default_prompt_section,
agents_md_section=agents_md_section,
)
12 changes: 12 additions & 0 deletions agent/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
web_search,
)
from .utils.auth import resolve_github_token
from .utils.github import fetch_agents_md
from .utils.github_app import get_github_app_installation_token
from .utils.model import make_model
from .utils.sandbox import create_sandbox
Expand Down Expand Up @@ -271,6 +272,16 @@ async def get_agent(config: RunnableConfig) -> Pregel:
linear_project_id = linear_issue.get("linear_project_id", "")
linear_issue_number = linear_issue.get("linear_issue_number", "")

repo_config = config["configurable"].get("repo", {})
repo_owner = repo_config.get("owner", "")
repo_name = repo_config.get("name", "")

agents_md_content = ""
if repo_owner and repo_name and github_token:
agents_md_content = await fetch_agents_md(repo_owner, repo_name, github_token)
if agents_md_content:
logger.info("Loaded AGENTS.md for %s/%s", repo_owner, repo_name)

work_dir = await aresolve_sandbox_work_dir(sandbox_backend)

logger.info("Returning agent with sandbox for thread %s", thread_id)
Expand All @@ -283,6 +294,7 @@ async def get_agent(config: RunnableConfig) -> Pregel:
working_dir=work_dir,
linear_project_id=linear_project_id,
linear_issue_number=linear_issue_number,
agents_md_content=agents_md_content,
),
tools=[
http_request,
Expand Down
40 changes: 40 additions & 0 deletions agent/utils/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

import base64
import logging
import shlex

Expand Down Expand Up @@ -280,3 +281,42 @@ async def get_github_default_branch(
except httpx.HTTPError:
logger.exception("Failed to get default branch from GitHub API, falling back to 'main'")
return "main"


async def fetch_agents_md(
repo_owner: str,
repo_name: str,
github_token: str,
) -> str:
"""Fetch AGENTS.md from the root of a GitHub repository.

Returns the file content as a string, or empty string if not found.
"""
try:
async with httpx.AsyncClient() as http_client:
response = await http_client.get(
f"https://api.github.com/repos/{repo_owner}/{repo_name}/contents/AGENTS.md",
headers={
"Authorization": f"Bearer {github_token}",
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
},
)

if response.status_code == 200: # noqa: PLR2004
data = response.json()
content_b64 = data.get("content", "")
return base64.b64decode(content_b64).decode("utf-8").strip()

logger.debug(
"AGENTS.md not found for %s/%s (status %s)",
repo_owner,
repo_name,
response.status_code,
)
return ""

except (httpx.HTTPError, Exception):
logger.warning("Failed to fetch AGENTS.md for %s/%s", repo_owner, repo_name)
return ""

Loading