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
16 changes: 10 additions & 6 deletions .github/aw/create-agentic-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,12 +307,16 @@ Before finalizing any newly generated workflow, verify:

## Multi-Repository Requests

For cross-repository workflows:

- enable the GitHub toolsets needed to read external repositories
- configure cross-repo authentication in `safe-outputs:`
- tell the agent to set `target-repo`
- explain that the workflow still cannot wait for external workflows or create multi-job orchestration
For cross-repository workflows, first determine whether the question is **finite and bounded**:

- If the agent needs to answer a finite, pre-approved question about a private repository (e.g. "does this repo have open critical issues?", "what is the latest release version?"):
- Use `tools.github.bounded-queries` with `private-repos` and `sandbox.agent.id: awf` (AWF v0.28.0+)
- This is the preferred approach — no raw source code is exposed and no cross-repo token is needed
- If the answer is unbounded (e.g. arbitrary source-code extraction, full file contents), or if bounded queries are not appropriate:
- enable the GitHub toolsets needed to read external repositories
- configure cross-repo authentication in `safe-outputs:`
- tell the agent to set `target-repo`
- explain that the workflow still cannot wait for external workflows or create multi-job orchestration

Use [workflow-patterns.md](workflow-patterns.md) for the compact cross-repo pattern.

Expand Down
2 changes: 2 additions & 0 deletions .github/aw/designer.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ Present a structured summary and ask for approval before generation.
| "run commands/tests" | `bash` tool (default unless restricted) |
| "browse web pages/docs" | `web-fetch` and/or `web-search` |
| "test UI flows" | `playwright` |
| "finite question about private repo" | `tools.github.bounded-queries` (AWF v0.28.0+, preferred over cross-repo tokens) |

### Pattern Heuristics

Expand Down Expand Up @@ -267,6 +268,7 @@ Never suggest committing plaintext tokens.
| "just respond to a comment" | no pre-fetch needed (event payload is enough) |
| "process each item individually" | suggest sub-agent pattern with `model: small` |
| "weekly digest", "compliance report", "license review", "policy audit" | pre-fetch with `gh` + `jq` into `/tmp/gh-aw/data/`; point prompt to those files |
| "finite question about a private repo", "check if private repo has X" | `tools.github.bounded-queries` (preferred over cross-repo token/checkout) |

## Token Optimization Defaults

Expand Down
28 changes: 28 additions & 0 deletions .github/aw/syntax-agentic.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,34 @@ description: Agentic workflow specific frontmatter fields for GitHub Agentic Wor
- **Strict mode**: `sandbox.agent` blocks without an explicit `id: awf` are rejected in strict mode. Any non-nil, non-disabled agent config without `id`/`type` defaults to AWF at runtime.

- **`tools:`** - Tool configuration for the coding agent (`github`, `agentic-workflows`, `edit`, `web-fetch`, `web-search`, `bash`, `playwright`, custom MCP server names, plus `timeout`/`startup-timeout`/`cli-proxy`). See [syntax-tools-imports.md](syntax-tools-imports.md#tool-configuration) for the full schema (GitHub `mode`/`toolsets`/integrity fields, bash allowlist decision rule, Playwright CLI mode).
- **`tools.github.bounded-queries`** (object, AWF v0.28.0+) configures the AWF bounded-query subsystem for cross-repository private data access. When present, the agent may answer finite, pre-approved questions about the listed repositories using the generated `bounded-query` skill — without receiving raw source code. This is the preferred pattern for cross-repository workflows. Requires the AWF sandbox (`sandbox.agent.id: awf`). All optional fields use AWF defaults when omitted.

```yaml
tools:
github:
bounded-queries:
private-repos:
- repo: my-org/public-docs
sensitivity: public # public | internal | confidential | sealed
- repo: my-org/internal-service
sensitivity: internal
runtime: docker # optional; docker | gvisor; default: AWF default
timeout: 30 # optional; seconds; default: AWF default
memory-limit: 512m # optional; e.g. 512m, 2g; default: AWF default
interpreter: python3 # optional; default: AWF default
max-invocations: 32 # optional; default: AWF default
sandbox:
agent:
id: awf
```

Sensitivity levels control how much information the agent may extract from the repository per run:
- `public`: unmetered disclosure budget; still operationally and schema-bounded, but no per-run cap on extracted bits.
- `internal`: 64 bits/run disclosure budget; use for repos with internal-audience content.
- `confidential`: 8 bits/run disclosure budget; use for restricted-within-org content.
- `sealed`: 0 bits/run; the query executes but cannot fund any answer — effectively a dry-run assertion. Do not use `sealed` when you need the agent to return information from the repository.

The staging credential used to access private repositories must remain host-side and is never written to the lock file or exposed to the agent. Use bounded queries when the question has a finite, bounded answer; prefer this over granting a cross-repository token or checking out the private repository into the primary workspace.

- **`safe-outputs:`** - Safe output processing configuration. See [safe-outputs.md](safe-outputs.md) for complete documentation of all output types: `create-issue`, `create-discussion`, `add-comment`, `create-pull-request`, `push-to-pull-request-branch`, `close-issue`, `close-discussion`, `update-issue`, `update-pull-request`, `add-labels`, `remove-labels`, `replace-label`, `dispatch-workflow`, `call-workflow`, `create-code-scanning-alert`, `upload-asset`, `upload-artifact`, `assign-to-agent`, `assign-to-user`, and more.

Expand Down
2 changes: 2 additions & 0 deletions pkg/constants/spec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,8 @@ func TestSpec_VersionConstraints_MinVersionValues(t *testing.T) {
{name: "AWFTokenSteeringMinVersion", constant: constants.AWFTokenSteeringMinVersion, expected: "v0.25.44"},
// From spec: CopilotNoAskUserMinVersion // "1.0.19"
{name: "CopilotNoAskUserMinVersion", constant: constants.CopilotNoAskUserMinVersion, expected: "1.0.19"},
// From spec: AWFBoundedQueriesMinVersion // "v0.28.0"
{name: "AWFBoundedQueriesMinVersion", constant: constants.AWFBoundedQueriesMinVersion, expected: "v0.28.0"},
}

for _, tt := range tests {
Expand Down
5 changes: 5 additions & 0 deletions pkg/constants/version_constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ const AWFLegacySecurityMinVersion Version = "v0.27.32"
// future release that adds apiProxy.providers to awf-config-schema.json.
const AWFAPIProxyProvidersMinVersion Version = "v0.27.43"

// AWFBoundedQueriesMinVersion is the minimum AWF version that supports
// the boundedQueries section in awf-config.json.
// Workflows pinning an older AWF version must not emit this section.
const AWFBoundedQueriesMinVersion Version = "v0.28.0"

// DefaultGVisorVersion is the pinned gVisor release used by the compiler-generated
// install step. A specific dated release name is used instead of "latest" to ensure
// reproducible, verifiable installs. Each release provides SHA-512 files for
Expand Down
60 changes: 59 additions & 1 deletion pkg/parser/schemas/main_workflow_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -4243,6 +4243,64 @@
"features": {
"type": "string",
"description": "Comma-separated list of GitHub MCP server feature flags to enable. Forwarded as GITHUB_FEATURES (Docker/local) or X-MCP-Features (remote). When omitted, 'fields_param' is enabled by default for server v1.6.0 and later. Set to an empty string to disable all feature flags."
},
"bounded-queries": {
"type": "object",
"description": "AWF bounded-query configuration for cross-repository private data access (AWF v0.28.0+). Requires the AWF sandbox (sandbox.agent.id: awf).",
"additionalProperties": false,
"required": ["private-repos"],
"properties": {
"private-repos": {
"type": "array",
"description": "List of private repositories the agent may query via bounded queries.",
"minItems": 1,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["repo", "sensitivity"],
"properties": {
"repo": {
"type": "string",
"description": "Repository slug in 'owner/repo' format.",
"pattern": "^[^/]+/[^/]+$",
"minLength": 3
},
"sensitivity": {
"type": "string",
"description": "Confidentiality classification for this repository.",
"enum": ["public", "internal", "confidential", "sealed"]
}
}
}
},
"runtime": {
"type": "string",
"description": "Container runtime used to execute bounded-query scripts. When omitted AWF uses its default.",
"enum": ["docker", "gvisor"]
},
"timeout": {
"type": "integer",
"description": "Maximum execution time in seconds for a single bounded-query invocation. When omitted AWF uses its default.",
"minimum": 1,
"maximum": 540
},
"memory-limit": {
"type": "string",
"description": "Memory limit for bounded-query container execution (e.g. \"512m\", \"2g\"). When omitted AWF uses its default.",
"pattern": "^[1-9][0-9]*[bkmgBKMG]$"
},
"interpreter": {
"type": "string",
"description": "Script interpreter for bounded-query execution. When omitted AWF uses its default.",
"enum": ["python3"]
},
"max-invocations": {
"type": "integer",
"description": "Maximum number of bounded-query invocations allowed per run. When omitted AWF uses its default.",
"minimum": 1,
"maximum": 10000
}
}
}
},
"additionalProperties": false,
Expand Down Expand Up @@ -13886,7 +13944,7 @@
},
"sessionId": {
"type": "string",
"description": "Optional session identifier injected as the x-session-id request header and session_id body field on Copilot BYOK upstream requests. Maps to AWF_PROVIDER_SESSION_ID. Only set this field when your upstream supports it strict OpenAI-compatible upstreams (e.g. Azure OpenAI) reject the unknown session_id body field with HTTP 400. Example: \"${{ github.run_id }}\"."
"description": "Optional session identifier injected as the x-session-id request header and session_id body field on Copilot BYOK upstream requests. Maps to AWF_PROVIDER_SESSION_ID. Only set this field when your upstream supports it \u2014 strict OpenAI-compatible upstreams (e.g. Azure OpenAI) reject the unknown session_id body field with HTTP 400. Example: \"${{ github.run_id }}\"."
}
},
"additionalProperties": false
Expand Down
97 changes: 97 additions & 0 deletions pkg/workflow/awf_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ type AWFConfigFile struct {
// APIProxy contains API proxy (LLM gateway) configuration.
APIProxy *AWFAPIProxyConfig `json:"apiProxy,omitempty"`

// BoundedQueries configures the AWF bounded-query subsystem for approved
// cross-repository private data access. Omitted when not configured.
BoundedQueries *AWFBoundedQueriesConfig `json:"boundedQueries,omitempty"`

// Container contains container execution configuration.
Container *AWFContainerConfig `json:"container,omitempty"`

Expand All @@ -181,6 +185,50 @@ type AWFConfigFile struct {
Chroot *AWFChrootConfig `json:"chroot,omitempty"`
}

// AWFBoundedQueriesConfig is the "boundedQueries" section of the AWF config file.
// It controls the bounded-query subsystem that allows finite, pre-approved questions
// about private repositories. All optional fields are omitted when unset so that
// AWF remains the source of truth for default values.
type AWFBoundedQueriesConfig struct {
// Enabled must be true when boundedQueries is present in the config.
// gh-aw always sets this to true when the section is generated.
Enabled bool `json:"enabled"`

// PrivateRepos is the list of private repositories approved for bounded-query access.
PrivateRepos []*AWFBoundedQueryPrivateRepo `json:"privateRepos,omitempty"`

// Runtime is the container runtime for bounded-query script execution (e.g. "docker").
// Optional; when omitted AWF uses its default.
Runtime string `json:"runtime,omitempty"`

// Timeout is the maximum execution time in seconds for a single invocation.
// Optional; when omitted AWF uses its default.
Timeout int `json:"timeout,omitempty"`

// MemoryLimit is the memory limit for bounded-query container execution (e.g. "512m").
// Optional; when omitted AWF uses its default.
MemoryLimit string `json:"memoryLimit,omitempty"`

// Interpreter is the script interpreter for bounded-query execution (e.g. "python3").
// Optional; when omitted AWF uses its default.
Interpreter string `json:"interpreter,omitempty"`

// MaxInvocations is the maximum number of bounded-query invocations per run.
// Optional; when omitted AWF uses its default.
MaxInvocations int `json:"maxInvocations,omitempty"`
}

// AWFBoundedQueryPrivateRepo describes a single private repository approved for
// bounded-query access, with its confidentiality classification.
type AWFBoundedQueryPrivateRepo struct {
// Repo is the "owner/repo" slug of the approved private repository.
Repo string `json:"repo"`

// Sensitivity is the confidentiality classification.
// Accepted values: "public", "internal", "confidential", "sealed".
Sensitivity string `json:"sensitivity"`
}

// AWFRunnerConfig is the "runner" section of the AWF config file.
// It provides a single stable contract between gh-aw and AWF for runner topology
// detection, letting AWF resolve all internal details (network isolation, sysroot
Expand Down Expand Up @@ -689,6 +737,16 @@ func BuildAWFConfigJSON(config AWFCommandConfig) (string, error) {
}
awfConfigLog.Printf("Logging section: proxyLogsDir=%s, auditDir=%s", awfConfig.Logging.ProxyLogsDir, awfConfig.Logging.AuditDir)

// ── Bounded queries section ──────────────────────────────────────────────
if bq := extractBoundedQueriesConfig(config.WorkflowData); bq != nil {
if awfSupportsBoundedQueries(firewallConfig) {
awfConfig.BoundedQueries = bq
awfConfigLog.Printf("Bounded queries section: %d private repo(s)", len(bq.PrivateRepos))
} else {
awfConfigLog.Printf("Skipping boundedQueries: AWF version %q requires at least %s", getAWFImageTag(firewallConfig), constants.AWFBoundedQueriesMinVersion)
}
}

jsonStr, err := jsonutil.MarshalCompactNoHTMLEscape(awfConfig)
if err != nil {
return "", fmt.Errorf("failed to marshal AWF config to JSON: %w", err)
Expand Down Expand Up @@ -899,6 +957,45 @@ func extractModelCostProviders(workflowData *WorkflowData) map[string]any {
return clone
}

// extractBoundedQueriesConfig returns an AWFBoundedQueriesConfig populated from
// tools.github.bounded-queries, or nil when the field is absent.
// Only fields explicitly set in frontmatter are included; optional fields that
// were not specified are omitted so that AWF remains the source of truth for defaults.
func extractBoundedQueriesConfig(workflowData *WorkflowData) *AWFBoundedQueriesConfig {
if workflowData == nil {
return nil
}
if workflowData.ParsedTools == nil || workflowData.ParsedTools.GitHub == nil {
return nil
}
bq := workflowData.ParsedTools.GitHub.BoundedQueries
if bq == nil {
return nil
}

awfBQ := &AWFBoundedQueriesConfig{
Enabled: true,
Runtime: bq.Runtime,
MemoryLimit: bq.MemoryLimit,
Interpreter: bq.Interpreter,
}
if bq.Timeout != nil {
awfBQ.Timeout = *bq.Timeout
}
if bq.MaxInvocations != nil {
awfBQ.MaxInvocations = *bq.MaxInvocations
}

for _, r := range bq.PrivateRepos {
awfBQ.PrivateRepos = append(awfBQ.PrivateRepos, &AWFBoundedQueryPrivateRepo{
Repo: r.Repo,
Sensitivity: r.Sensitivity,
})
}

return awfBQ
}

// getRunnerTopology extracts the runner topology string from WorkflowData.
// Returns an empty string when no topology is configured.
func getRunnerTopology(workflowData *WorkflowData) string {
Expand Down
6 changes: 6 additions & 0 deletions pkg/workflow/awf_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -1099,6 +1099,12 @@ func awfSupportsAPIProxyProviders(firewallConfig *FirewallConfig) bool {
return awfVersionAtLeast(firewallConfig, constants.AWFAPIProxyProvidersMinVersion)
}

// awfSupportsBoundedQueries returns true when the effective AWF version supports
// the boundedQueries section in awf-config.json.
func awfSupportsBoundedQueries(firewallConfig *FirewallConfig) bool {
return awfVersionAtLeast(firewallConfig, constants.AWFBoundedQueriesMinVersion)
}

// buildArcDindChrootConfigPatchBody returns the Node.js command that patches the AWF
// config file with chroot.binariesSourcePath and chroot.identity.*. It is designed to be
// embedded inside a bash if-block that already guards on DOCKER_HOST=tcp://...
Expand Down
Loading
Loading