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
14 changes: 14 additions & 0 deletions .go-arch-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,9 @@ components:
infra-otel:
in: infrastructure/otel

infra-roles:
in: infrastructure/roles

infra-skills:
in: infrastructure/skills

Expand Down Expand Up @@ -347,6 +350,7 @@ deps:
- infra-http
- infra-notify
- infra-repository
- infra-roles
- infra-skills
- infra-xdg
canUse:
Expand Down Expand Up @@ -539,6 +543,15 @@ deps:
- go-stdlib
- otel

infra-roles:
mayDependOn:
- domain-workflow
- domain-ports
- infra-skills
- infra-xdg
canUse:
- go-stdlib

infra-skills:
mayDependOn:
- domain-workflow
Expand Down Expand Up @@ -575,6 +588,7 @@ deps:
- infra-otel
- infra-plugin
- infra-repository
- infra-roles
- infra-skills
- infra-store
- infra-tokenizer
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- **F098**: Agent role injection via AGENTS.md — new optional `role: <name-or-path>` field on agent steps loads `<role-dir>/AGENTS.md`, strips YAML frontmatter, and injects the body as `system_prompt` (Claude native `--system-prompt`; CLI providers via existing first-turn concat). Role discovery searches in strict priority order: `AWF_AGENTS_PATH` (exclusive override), `.awf/agents/`, `.agents/`, `$XDG_CONFIG_HOME/awf/agents/`, `~/.agents/`. Explicit path references supported (relative to workflow file, absolute, tilde-expanded); `role` value is interpolated (`role: {{.inputs.persona}}`) before discovery. When combined with inline `system_prompt`, the role content is prepended (`<role>\n\n<inline>`). Works in both `mode: single` and `mode: conversation` — three injection sites (`executeAgentStep`, `executeResumableAgentCall`, `ConversationManager.ExecuteConversation`) unified through a single `ComposeSystemPrompt` helper. `awf validate` reports hard errors for missing role directories / missing AGENTS.md and non-blocking warnings for empty bodies, `AGENTS.md` > 500KB, and combined `role + system_prompt` > 10KB. Path-traversal patterns (`..`) rejected during validation. New error code `USER.INPUT.MISSING_ROLE`. Backward compatible: agent steps without `role:` behave identically to pre-F098. Implementation mirrors the existing skills mechanism (new `AgentRole` entity, `AgentRoleRepository` port, `internal/infrastructure/roles/` adapter, `SetAgentRoleRepository()` optional wiring). Roles establish **who** the agent is (system prompt); skills define **what** it knows (user-prompt XML block) — orthogonal channels by design
- **F097**: HTTP REST API server (`awf serve`) — new `internal/interfaces/api/` adapter alongside `cli/` and `tui/` exposing workflow discovery (`GET /api/workflows`, `GET /api/workflows/{name}`, `POST /api/workflows/{name}/validate`), async execution (`POST /api/workflows/{name}/run` returning 202 + `execution_id`), lifecycle control (`GET /api/executions`, `GET /api/executions/{id}`, `DELETE /api/executions/{id}`, `POST /api/executions/{id}/resume`), Server-Sent Events streaming (`GET /api/executions/{id}/events` emitting `step.started`, `step.completed`, `step.failed`, `workflow.completed`, `workflow.failed`, `output`), and history queries (`GET /api/history`, `GET /api/history/stats`); Huma v2 + chi v5 generate OpenAPI 3.1 spec served at `/openapi.json`, `/openapi.yaml`, and Swagger UI at `/docs`; Bridge adapter pattern mirrors `tui/bridge.go` with `sync.Map` tracking active executions; SSE polling at 200ms cadence matching TUI; graceful shutdown via `signal.NotifyContext` waits up to 30s for active streams; default binding `127.0.0.1:2511` (loopback-only, non-loopback opt-in via `--host`); arch-lint enforces `interfaces-api` may import only `application/` and `domain/*` (no `infrastructure/`, `cli/`, or `tui/`); see [ADR-016](docs/ADR/016-http-interface-adapter-huma-sse-streaming.md)

## [0.9.0] - 2026-05-14
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,6 @@ func TestWorkflowValidation(t *testing.T) {

## Architecture Rules

- Commit generated protobuf files (.pb.go, _grpc.pb.go) to git; treat as source artifacts for build reproducibility, not ephemeral build outputs
- CLI command implementations must call infrastructure layer methods rather than reimplementing HTTP requests, parsing, or validation; avoid logic duplication
- Application layer must persist source metadata (SetSourceData) after successful infrastructure installation; omitting state blocks downstream operations like updates
- Use dual import aliases (e.g., infrastructurePlugin + registry) when consuming refactored packages; explicitly requalify all symbol references to prevent ambiguity
- Keep thin wrapper functions in original location for backward compatibility; delegate completely to extracted packages to maintain single source of truth
Expand All @@ -240,6 +238,8 @@ func TestWorkflowValidation(t *testing.T) {
- Use provider name prefixes for all infrastructure provider helper methods (buildCopilot, extractCopilot, parseCopilot, validateCopilot) to prevent naming collisions across implementations
- Use mutex-protected getter/setter methods (Get*/Set*) for concurrent shared state; apply consistently across all goroutine-accessed fields
- Server owns background task coordination (WaitGroup); pass by pointer to handlers and coordinate shutdown: httpSrv.Shutdown() then sseWG.Wait()
- Always update `.go-arch-lint.yml` when adding new infrastructure components; register the package and document its dependency rules in the commit message
- When implementing infrastructure adapters that follow established patterns (e.g., FilesystemAgentRoleRepository mirrors FilesystemSkillRepository), reuse shared utilities (skills.StripFrontmatter) to maintain single source of truth

## Common Pitfalls

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ A Go CLI tool for orchestrating AI agents (Claude, Gemini, Codex, GitHub Copilot
- **Agent Steps** - Invoke AI agents via CLI tools (Claude, Codex, Gemini, GitHub Copilot) or direct HTTP (OpenAI, Ollama, vLLM, Groq) with prompt templates, response parsing, and accurate token tracking
- **Output Formatting for Agent Steps** - Automatically strip markdown code fences and validate JSON output; human-readable streaming display controlled by `output_format` field (text vs raw NDJSON); unified display-event abstraction across all 6 providers with optional verbose mode showing tool-use markers (`[tool: Name(Arg)]`)
- **Agent Skills** - Inject deterministic domain knowledge into agent steps via `skills:` declarations in workflow YAML; filesystem-based multi-directory discovery (project `.awf/skills/`, `.agents/skills/`, `.claude/skills/`, XDG global) with priority ordering; SKILL.md frontmatter stripping and agentskills.io-compliant `<skill_content>` structured wrapping with bundled resource enumeration; validated by `awf validate`
- **Agent Roles** - Inject reusable personas into agent steps via `role:` field referencing AGENTS.md files; filesystem-based multi-directory discovery (project `.awf/agents/`, `.agents/`, XDG global) with priority ordering; AGENTS.md frontmatter stripping and system prompt injection with optional composition via inline `system_prompt` field; validated by `awf validate`
- **External Prompt Files** - Load agent prompts from `.md` files with full template interpolation, helper functions, and local override support
- **External Script Files** - Load commands from external script files with shebang-based interpreter dispatch, template interpolation, path resolution, and local override support
- **Conversation Mode** - Multi-turn conversations with native session resume for CLI providers (`claude`, `codex`, `gemini`, `opencode`, `github_copilot`), automatic context window management for HTTP providers, mid-conversation context injection via `inject_context` field, and token tracking across all turns
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Learn how to use AWF effectively:
- [Interactive Input Collection](user-guide/interactive-inputs.md) - Automatic prompting for missing workflow inputs
- [Agent Steps](user-guide/agent-steps.md) - Invoke AI agents via CLI (Claude, Codex, Gemini, GitHub Copilot) or HTTP APIs (OpenAI, Ollama, vLLM, Groq)
- [Agent Skills](user-guide/agent-steps.md#agent-skills) - Inject deterministic domain knowledge into agent steps via `skills:` declarations with multi-directory discovery
- [Agent Roles](user-guide/agent-steps.md#agent-roles) - Inject reusable personas into agent steps via `role:` field referencing AGENTS.md files with system prompt composition
- [Output Formatting](user-guide/agent-steps.md#output-formatting) - Automatic code fence stripping and JSON validation (`output_format: json|text`)
- [Streaming Output Display & Tool Markers](user-guide/agent-steps.md#streaming-output-display--tool-markers) - Human-readable filtered output and tool-use markers for `--output streaming` and `--output buffered` modes
- [External Prompt Files](user-guide/agent-steps.md#external-prompt-files) - Load prompts from Markdown files with template interpolation
Expand Down
17 changes: 17 additions & 0 deletions docs/reference/error-codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,23 @@ awf run code-review

---

### USER.INPUT.MISSING_ROLE

**Description:** An agent role referenced in a workflow step could not be resolved. Either the role directory does not exist in any discovery path, the directory exists but contains no `AGENTS.md` file, or no `AgentRoleRepository` is wired when the step is executed.

**Resolution:** Verify the role name matches a directory in one of the discovery paths (`.awf/agents/`, `.agents/`, `$XDG_CONFIG_HOME/awf/agents/`, `~/.agents/`) and that `<role-dir>/AGENTS.md` is readable. For path-based references, verify the path is correct relative to the workflow file. Set `AWF_AGENTS_PATH=<dir>` to restrict discovery to a single directory.

**Example:**
```bash
awf run code-review
# Error [USER.INPUT.MISSING_ROLE]: role 'go-senior' not found in search paths:
# .awf/agents/, .agents/, ~/.config/awf/agents/, ~/.agents/
```

**Related codes:** `USER.INPUT.MISSING_SKILL`, `USER.INPUT.MISSING_FILE`, `WORKFLOW.VALIDATION.INVALID_REFERENCE`

---

### USER.INPUT.INVALID_FORMAT

**Description:** The file format does not match expected structure or contains invalid syntax.
Expand Down
231 changes: 231 additions & 0 deletions docs/user-guide/agent-steps.md
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,237 @@ Validation reports:
| Skill not in expected location | Wrong discovery directory | Move skill to one of the 7 standard directories or use `path:` reference |
| Large skill file warning | `SKILL.md` exceeds 500KB | Consider splitting into multiple smaller skills |

## Agent Roles

Agent roles define reusable personas that are injected into the agent's system prompt. Unlike skills (which inject knowledge into the user prompt), roles establish the agent's identity, behavior, and perspective via the system prompt. Roles are stored in AGENTS.md files following the [agents.md](https://github.com/agentsmd/agents.md) specification.

### Basic Usage

Create a role directory with an `AGENTS.md` file:

**Directory structure:**
```
.awf/agents/
├── go-senior/
│ └── AGENTS.md # Agent persona (frontmatter optional)
├── security-reviewer/
│ └── AGENTS.md
└── docs-writer/
└── AGENTS.md
```

**File:** `.awf/agents/go-senior/AGENTS.md`
```markdown
---
name: go-senior
description: Senior Go engineer persona
---

You are a senior Go engineer with 10+ years of experience.

When reviewing code:
- Prioritize readability and maintainability
- Suggest idiomatic Go patterns
- Consider performance implications
- Flag potential race conditions
- Reference Go proverbs and best practices
```

Reference the role by name in your workflow:

```yaml
review:
type: agent
provider: claude
role: go-senior
prompt: "Review this Go code: {{.inputs.code}}"
on_success: done
```

When executed, the AGENTS.md content is stripped of frontmatter and injected as the system prompt to the agent, establishing the persona before the user prompt is sent.

### Multiple Roles

A single agent step uses one role only. To combine multiple personas, either:
1. Create a composite role that includes all relevant perspectives
2. Chain multiple agent steps with different roles

```yaml
# ❌ Invalid: only one role per step
step:
type: agent
role: go-senior
role: security-reviewer # Error

# ✅ Valid: chain steps for multiple perspectives
initial_review:
type: agent
provider: claude
role: go-senior
prompt: "Review this code: {{.inputs.code}}"
on_success: security_review

security_review:
type: agent
provider: claude
role: security-reviewer
prompt: |
After this Go review:
{{.states.initial_review.Output}}

Now analyze for security issues: {{.inputs.code}}
on_success: done
```

### Role Discovery

AWF searches for roles in the following directories, in priority order:

1. **`AWF_AGENTS_PATH` environment variable** (if set, exclusive — overrides all others)
2. **`.awf/agents/`** (project-level, AWF-specific)
3. **`.agents/`** (project-level, cross-client compatibility)
4. **`$XDG_CONFIG_HOME/awf/agents/`** (global user, AWF-specific — defaults to `~/.config/awf/agents/`)
5. **`~/.agents/`** (global user, cross-client)

This enables shared global personas while allowing project-specific overrides.

**Example with environment variable:**

```bash
# Use only roles from custom directory
AWF_AGENTS_PATH=/shared/agents awf run workflow
```

### Role Content Format

**Frontmatter is optional** — AWF strips YAML frontmatter (content between `---` delimiters) if present, preserving only the Markdown body:

```markdown
---
name: go-senior
description: Senior Go engineer persona
license: MIT
tags: [go, senior, code-review]
---

You are a senior Go engineer...
```

Only the Markdown body is injected as the system prompt. The role name comes from the directory name (e.g., `go-senior`), not from frontmatter.

### Combining Role with Inline System Prompt

A step can combine a role with an inline `system_prompt` field. The role content is injected first, followed by the inline prompt, separated by a blank line:

```yaml
review:
type: agent
provider: claude
role: go-senior
system_prompt: "Focus on performance optimizations and memory leaks."
prompt: "Review this code: {{.inputs.code}}"
on_success: done
```

The effective system prompt sent to the agent is:

```
<go-senior AGENTS.md content>

Focus on performance optimizations and memory leaks.
```

This allows you to reuse a base persona while adding step-specific context or overrides.

### Explicit Path References

Reference roles by explicit path instead of discovery:

```yaml
review:
type: agent
provider: claude
role: ./custom-agents/senior-go
prompt: "Review: {{.inputs.code}}"
on_success: done
```

Paths can be:
- **Relative** to the workflow directory: `role: ./custom-agents/senior-go`
- **Absolute**: `role: /home/user/agents/senior-go`
- **Home-relative**: `role: ~/shared-agents/senior-go`

### Dynamic Role Selection

The `role` field supports template interpolation, enabling dynamic role selection based on workflow inputs or state:

```yaml
inputs:
- name: persona
type: string
default: go-senior

review:
type: agent
provider: claude
role: "{{.inputs.persona}}"
prompt: "Review: {{.inputs.code}}"
on_success: done
```

```bash
awf run workflow --input persona=security-reviewer --input code="..."
```

### Validation

Use `awf validate` to check that all role references exist and are readable:

```bash
awf validate workflow.yaml
```

Validation reports:
- ✓ Roles found with valid `AGENTS.md` files
- ✗ Roles referenced but not found in any discovery path
- ✗ Role directories found but missing `AGENTS.md`
- ⚠ Empty `AGENTS.md` files (warning — content will be empty)
- ⚠ `AGENTS.md` exceeds 500KB (warning — context window impact)
- ⚠ Combined `role + system_prompt` exceeds 10KB (warning — context window impact)

### Troubleshooting

| Issue | Cause | Solution |
|-------|-------|----------|
| `AgentRoleNotFoundError` | Referenced role not found in discovery paths | Check role directory name matches YAML declaration; verify `AGENTS.md` exists |
| Empty role content | `AGENTS.md` is 0 bytes or contains only frontmatter | Add Markdown body to `AGENTS.md` |
| Role not in expected location | Wrong discovery directory | Move role to one of the 5 standard directories or use explicit path reference |
| Large role file warning | `AGENTS.md` exceeds 500KB | Consider splitting into multiple smaller roles or removing verbose content |
| Combined prompt too large | `role + system_prompt` over 10KB | Reduce role/prompt size or split into separate steps |

### Conversation Mode with Roles

Roles work seamlessly in conversation mode (`mode: conversation`). The role is resolved and injected once at the start of the conversation, establishing the agent's persona for all subsequent user turns:

```yaml
chat:
type: agent
provider: claude
mode: conversation
role: go-senior
system_prompt: "Be helpful and concise."
prompt: "{{.inputs.topic}}"
timeout: 600
on_success: done
```

### Limitations

- A single agent step uses one role only — no composition or inheritance
- No template interpolation is applied to role content — it is injected as-is
- Roles do not reference other roles; only workflow steps reference roles
- Roles establish system-level persona via the standard `system_prompt` mechanism available to all providers (Claude native `--system-prompt`, CLI-based providers via first-turn concat, HTTP providers via API field)

## External Prompt Files

Instead of inlining prompts in YAML, you can load prompts from external Markdown files using the `prompt_file` field:
Expand Down
23 changes: 23 additions & 0 deletions docs/user-guide/workflow-syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,28 @@ review:

See [Agent Steps — Agent Skills](agent-steps.md#agent-skills) for discovery paths, SKILL.md format, and validation.

### Agent Step with Role

Inject a reusable persona into agent steps via `role:` field. Roles are loaded from AGENTS.md files and establish the agent's identity via system prompt.

```yaml
review:
type: agent
provider: claude
role: go-senior
system_prompt: "Focus on performance and memory safety."
prompt: "Review this Go code: {{.states.read.Output}}"
options:
model: claude-sonnet-4-20250514
timeout: 120
on_success: done
on_failure: error
```

Roles and inline `system_prompt` are combined: role content is injected first, followed by the inline prompt, separated by a blank line.

See [Agent Steps — Agent Roles](agent-steps.md#agent-roles) for discovery paths, AGENTS.md format, dynamic role selection, and validation.

### Conversation Mode

`mode: conversation` runs an **interactive user-driven loop**: the agent replies, AWF prompts for your next message, and the loop continues until you submit an empty line, `exit`, or `quit`. It requires a terminal (or piped stdin).
Expand Down Expand Up @@ -438,6 +460,7 @@ For **automated cross-step session resume** (no stdin loop), use `mode: single`
| `prompt` | string | Yes* | Prompt template (supports `{{.inputs.*}}` and `{{.states.*}}` interpolation); in `mode: conversation` this serves as the first user message |
| `prompt_file` | string | No* | Path to external prompt template file (mutually exclusive with `prompt`; not supported in `mode: conversation`) |
| `system_prompt` | string | No | System message preserved for the whole session |
| `role` | string | No | Role reference — name-based (e.g., `go-senior`) or path-based (e.g., `./custom-agents/senior`); loaded from AGENTS.md file and injected as system prompt; see [Agent Roles](agent-steps.md#agent-roles) |
| `output_format` | string | No | Post-processing format: `json` (strip fences + validate JSON) or `text` (strip fences only) |
| `conversation` | object | No | Session tracking sub-struct. Presence opts the step into session tracking (marker flag); contents: see [Session Tracking](#session-tracking) |
| `skills` | array | No | Skill references for agent context injection — name-based (e.g., `go-conventions`) or path-based (e.g., `path: ./custom/audit`); see [Agent Skills](agent-steps.md#agent-skills) |
Expand Down
Loading
Loading