Skip to content
Merged
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
61 changes: 60 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,12 @@ If you need to add new environment variables:
3. Add a deterministic helper in `triage/classifier.py` or `worker/analyzer.py` if applicable.
4. Add the reasoner to `triage/agent.py` or `worker/agent.py`. Tag with `entry` only if it's a polling/claiming entrypoint.
5. Unit-test the helper and the reasoner wiring under `tests/unit/`.
6. **Lint and format before committing** — run `ruff check .` and `ruff format .` to fix all linting and formatting issues.
6. **Lint and format before committing**:
```bash
ruff check . # Check for linting issues
ruff format . # Auto-format code
```
All linting must pass before committing. CI enforces this.
7. **Run tests** — `pytest tests/unit` must pass before committing.
8. **Update both `CLAUDE.md` and `AGENTS.md`** with any relevant changes.

Expand All @@ -125,6 +130,60 @@ If you need to add new environment variables:

## Troubleshooting Common Issues

### Working in Git Worktrees

**This repository is often used with git worktrees.** Docker containers must be started from the worktree directory you're currently working in.

**Common Issue:** `/var/nd/work` directory not found

**Symptoms:**
- Agent execution fails with `[Errno 2] No such file or directory: '/var/nd/work'`
- WorkspaceClient.prepare() fails

**Root cause:** Docker containers were started from a different worktree. The volume mount in docker-compose.yml uses:
```yaml
volumes:
- ${ND_WORKSPACE_ROOT:-./.nd-workspace}:/var/nd
```

The `./.nd-workspace` path is relative to where `docker-compose up` was run, NOT the current directory.

**How to identify:**
```bash
# Check where containers are running from
docker inspect <container-name>-worker-1-1 --format '{{.Config.Labels}}' | grep working_dir

# Check volume mounts
docker inspect <container-name>-worker-1-1 --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'
```

**Solution:**
```bash
# 1. Stop containers from old worktree
docker-compose -p <old-project-name> down

# 2. Create workspace directory in current worktree
mkdir -p .nd-workspace/work

# 3. Start containers from current worktree
docker-compose up -d
```

**Alternative: Shared workspace across worktrees**

Set `ND_WORKSPACE_ROOT` to an absolute path in `.env.local`:
```bash
ND_WORKSPACE_ROOT=/Users/andy/.nd-workspace-shared
```

Then create the directory and restart:
```bash
mkdir -p /Users/andy/.nd-workspace-shared/work
docker-compose down && docker-compose up -d
```

This allows all worktrees to share the same workspace, making tasks visible across worktrees.

### AgentField Connectivity
If agents show "running in degraded mode" or "Could not resolve host: agentfield":
- Check for port conflicts on 8081
Expand Down
45 changes: 43 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,40 @@ ruff format --check .

CI (`.github/workflows/ci.yml`) runs `ruff check`, `ruff format --check`, unit tests, and enforces ≥50% coverage. Match this locally before pushing.

## Working in Git Worktrees

This repository is often used with git worktrees. **Important:** Docker containers must be started from the worktree directory you're currently working in.

### Common Issue: `/var/nd/work` not found

**Symptom:** Agent execution fails with `[Errno 2] No such file or directory: '/var/nd/work'`

**Root cause:** Docker containers were started from a different worktree, and the volume mount `${ND_WORKSPACE_ROOT:-./.nd-workspace}:/var/nd` is relative to where `docker-compose up` was run.

**Solution:**
```bash
# Stop containers from the old worktree
docker-compose -p <old-project-name> down

# Create workspace directory in current worktree
mkdir -p .nd-workspace/work

# Start containers from current worktree
docker-compose up -d
```

The `.nd-workspace/` directory is gitignored and local to each worktree.

### Alternative: Shared Workspace Across Worktrees

If you want all worktrees to share the same workspace:
```bash
# Set in .env.local
ND_WORKSPACE_ROOT=/Users/andy/.nd-workspace-shared
```

Then `mkdir -p /Users/andy/.nd-workspace-shared/work` and restart containers. This allows tasks created in one worktree to be visible in others.

## Refreshing AWS Credentials

When AWS credentials expire, follow this workflow to update the docker-compose environment:
Expand All @@ -91,7 +125,9 @@ docker-compose down
docker-compose up -d

# 4. Verify credentials loaded and have Bedrock access
docker exec hyper-furniture-worker-1-1 sh -c '
# Note: container name is <directory-name>-worker-1-1 (based on current directory name)
docker ps --filter "name=worker-1" --format "{{.Names}}" # Find exact container name
docker exec <container-name>-worker-1-1 sh -c '
python3 -c "import boto3; sts = boto3.client(\"sts\", region_name=\"us-east-1\"); print(\"Identity:\", sts.get_caller_identity()[\"Arn\"])"
'
# Should show: arn:aws:sts::657062785455:assumed-role/horizon-okta/andy
Expand Down Expand Up @@ -132,7 +168,12 @@ Full table is in `README.md`.
3. Add a deterministic helper in `triage/classifier.py` or `worker/analyzer.py` if applicable.
4. Add the reasoner to `triage/agent.py` or `worker/agent.py`. Tag with `entry` only if it's a polling/claiming entrypoint.
5. Unit-test the helper and the reasoner wiring under `tests/unit/`.
6. **Lint and format before committing** — run `ruff check .` and `ruff format .` to fix all linting and formatting issues.
6. **Lint and format before committing**:
```bash
ruff check . # Check for linting issues
ruff format . # Auto-format code
```
All linting must pass before committing. CI enforces this.
7. **Run tests** — `pytest tests/unit` must pass before committing.

## Things to avoid
Expand Down
10 changes: 6 additions & 4 deletions nd/worker/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ def _extract_source_metadata(
else:
# Fallback: construct from available data
if mr_number:
source_url = f"https://{platform_host}/{repo_owner}/{repo_name}/-/merge_requests/{mr_number}"
source_url = (
f"https://{platform_host}/{repo_owner}/{repo_name}/-/merge_requests/{mr_number}"
)
source_type = "mr"
source_identifier = f"{platform}:{repo_owner}/{repo_name}#{mr_number}"
elif issue_number:
Expand Down Expand Up @@ -421,7 +423,7 @@ async def _maybe_cleanup_on_failure() -> None:
repo_name=context.get("repo_name", project),
base_branch=ws.base_branch or context.get("base_branch", "main"),
title=context.get("mr_title", title),
source_url=context.get("mr_url", ""),
source_url=context.get("issue_url") or context.get("mr_url", ""),
is_issue=is_issue,
)
publish = PublishResult(**publish_result)
Expand Down Expand Up @@ -950,8 +952,8 @@ def _parse_task_body(body: str) -> dict | None:
context["category"] = "issue"
context["repo_owner"] = issue_match.group(1)
context["repo_name"] = issue_match.group(2)
context["mr_number"] = int(issue_match.group(3))
context["mr_url"] = issue_match.group(4)
context["issue_number"] = int(issue_match.group(3))
context["issue_url"] = issue_match.group(4)

title_match = re.search(r"\*\*Title:\*\* (.+)", body)
if title_match:
Expand Down
4 changes: 3 additions & 1 deletion tests/e2e/test_suite_structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,6 @@ def test_no_skipped_agent_integration_tests_in_ci():
if "test_simple_request_flow" in line and "SKIPPED" in line:
found_skipped = True
break
assert found_skipped, "test_simple_request_flow should be SKIPPED when SKIP_AGENT_INTEGRATION=true"
assert found_skipped, (
"test_simple_request_flow should be SKIPPED when SKIP_AGENT_INTEGRATION=true"
)
Loading