Skip to content

Latest commit

 

History

History
647 lines (489 loc) · 25.1 KB

File metadata and controls

647 lines (489 loc) · 25.1 KB

policy.json reference

policy.json controls which GitHub notifications the bridge trusts, which repositories are in scope, which actions are automatic, where OpenClaw agent work is delivered, which operating posture the agent uses, and whether feedback learning is captured.

Quick map

flowchart TD
    A[source] --> B[enabledRepos]
    B --> C[actions]
    C --> D[repoRoutes/orgRoutes]
    D --> E[repoRoles/orgRoles]
    E --> F[promptOverrides]
    F --> G[feedbackLearning]
    G --> H[OpenClaw dispatch]
Loading
Question Policy area
Is this a trusted GitHub notification? source
Is this repo currently in live/canary scope? enabledRepos
Is this action automatic, trusted-only, approval-only, or denied? actions
Where should accepted work be delivered? repoRoutes, orgRoutes
How much authority should the agent use? repoRoles, orgRoles
Which prompt text should be customized? promptOverrides
Should feedback-like GitHub comments be captured as rule candidates? feedbackLearning

Default path in the packaged CLI/systemd examples:

~/.config/github-agent-bridge/policy.json

The policy is loaded by commands that make decisions or dispatch work, for example:

gab --policy ~/.config/github-agent-bridge/policy.json read-imap-once ...
gab --policy ~/.config/github-agent-bridge/policy.json run --mode live ...
gab --policy ~/.config/github-agent-bridge/policy.json enqueue-comment-url ...

Complete example

{
  "source": {
    "from": "notifications@github.com",
    "requiredAuth": ["spf=pass", "dkim=pass", "dmarc=pass"],
    "requiredUrlPrefix": "https://github.com/",
    "messageIdDomain": "github.com"
  },
  "trustedRepos": ["your-org/your-repo"],
  "trustedOrgs": ["your-org"],
  "enabledRepos": ["your-org/your-repo"],
  "repoRoutes": {
    "another-org/another-repo": {
      "agent": "another-openclaw-agent",
      "channel": "telegram",
      "to": "ANOTHER_CHAT_ID"
    }
  },
  "orgRoutes": {
    "your-org": {
      "agent": "your-openclaw-agent",
      "channel": "telegram",
      "to": "YOUR_CHAT_ID"
    }
  },
  "repoRoles": {
    "your-org/your-repo": "maintainer"
  },
  "orgRoles": {
    "your-bot-user": "maintainer"
  },
  "actions": {
    "auto": ["archive_notification"],
    "ask": [],
    "trustedAuto": ["reply_comment", "open_issue", "submit_review", "sync_after_merge", "workflow_run_failed", "docs_update", "content_change"],
    "deny": ["merge_main", "org_permissions_change", "manage_secrets", "delete_remote_repo_or_branch"]
  },
  "feedbackLearning": {
    "enabled": true,
    "minConfidence": 0.5
  }
}

Top-level keys

Key Type Default Meaning
source object built-in GitHub defaults Defines which notifications count as trusted GitHub source mail.
trustedRepos array of strings [] Exact owner/repo names trusted for trustedAuto actions. Case-insensitive.
trustedOrgs array of strings [] GitHub org/user names trusted for all repos under that owner. Case-insensitive.
enabledRepos array of strings [] Optional hard allowlist/canary scope. If non-empty, all repos not listed here are denied before other checks. Case-insensitive.
repoRoutes object {} Exact per-repo delivery routes. Takes precedence over orgRoutes.
orgRoutes object {} Per-owner delivery routes used when no repoRoutes entry matches.
repoRoles object {} Exact per-repo operating role. Takes precedence over orgRoles.
orgRoles object {} Per-owner operating role used when no repoRoles entry matches.
actions object built-in action defaults Maps classified notification actions to policy decisions.
promptOverrides object {} Optional Markdown files that replace selected packaged prompt resources.
feedbackLearning object { "enabled": true, "minConfidence": 0.5, "autoApproveConfidence": 0.8 } Controls candidate capture, autonomous learning, and prompt threshold for feedback rules.

Unknown top-level keys are ignored by the current implementation.

source

source controls source trust. A notification must pass source trust before it can become auto, auto_trusted, or ask.

Key Type Default Meaning
from string notifications@github.com Required substring in the decoded email From header.
requiredUrlPrefix string https://github.com/ At least one extracted URL must start with this prefix.
messageIdDomain string github.com Required substring in the email Message-ID.
requiredAuth array of strings currently documented only Intended SPF/DKIM/DMARC requirements. See note below.

Current auth behavior:

  • Parsed email notifications with auth results must have spf, dkim, and dmarc truthy.
  • Synthetic notifications, such as gab enqueue-comment-url, set all three auth values to true.
  • The exact strings in source.requiredAuth are not currently interpreted; they document the expected policy but the code currently checks the three booleans directly.

Source trust fails when any of these are false:

source.from is in From header
AND auth is OK
AND at least one GitHub URL has source.requiredUrlPrefix
AND Message-ID contains source.messageIdDomain

If source trust fails, the decision is always deny.

trustedRepos

Exact repositories trusted for trustedAuto actions.

Example:

{
  "trustedRepos": ["your-org/your-repo", "another-org/another-repo"]
}

A repo listed here makes repo_trusted(repo) true even if its owner is not in trustedOrgs.

trustedOrgs

Owners trusted for all repositories under that owner.

Example:

{
  "trustedOrgs": ["your-org", "another-org"]
}

your-org trusts your-org/your-repo, your-org/another-repo, etc., unless enabledRepos narrows the active scope.

enabledRepos

Hard allowlist for canary/live scope.

Default:

{
  "enabledRepos": []
}

Semantics:

  • Empty array: no extra scope restriction.
  • Non-empty array: only listed repos may be processed.
  • Repos not listed are denied before source trust, action policy, or routes are considered.

Example canary policy:

{
  "trustedOrgs": ["your-org"],
  "enabledRepos": ["your-org/your-repo"]
}

Result:

Repo Result
your-org/your-repo Eligible for normal decisions.
your-org/another-repo deny.
another-org/another-repo deny.

This is the preferred key for staged rollout from the legacy inbox worker to the bridge.

repoRoutes and orgRoutes

Routes decide where the OpenClaw agent task is delivered after a job is accepted.

Route object:

Key Type Meaning
agent string or null OpenClaw agent id, for example your-openclaw-agent.
channel string or null Delivery channel, for example telegram.
to string or null Delivery target, for example a Telegram chat id.

Route precedence:

  1. Exact repoRoutes[owner/repo].
  2. Owner-level orgRoutes[owner].
  3. CLI defaults passed to gab run with --channel and --to (or environment defaults).

There is intentionally no hardcoded organization-to-agent fallback in code; configure routing in repoRoutes or orgRoutes.

Example:

{
  "repoRoutes": {
    "another-org/another-repo": {
      "agent": "another-openclaw-agent",
      "channel": "telegram",
      "to": "ANOTHER_CHAT_ID"
    }
  },
  "orgRoutes": {
    "your-org": {
      "agent": "your-openclaw-agent",
      "channel": "telegram",
      "to": "YOUR_CHAT_ID"
    }
  }
}

With this policy:

Repo Route
another-org/another-repo another-openclaw-agent to ANOTHER_CHAT_ID, because repoRoutes takes precedence.
your-org/your-repo your-openclaw-agent to YOUR_CHAT_ID, because orgRoutes.your-org defines that route.
other/repo CLI default channel/target, no configured agent unless dispatch fallback applies.

Routes do not grant trust. A repo can have a route and still be denied by source/action/scope policy.

repoRoles and orgRoles

repoRoles and orgRoles define the operating posture that is injected into the OpenClaw agent prompt. They do not change queue trust decisions in v1; they change how the agent should reason once work is dispatched.

Repository role and work intent are intentionally separate:

  • Role controls judgment and authority: owner, maintainer, contributor, or reviewer.
  • Work intent controls allowed actions: for example review_only versus work_allowed.

For PR review/discussion follow-ups, the bridge classifies the work intent as review_only by default unless the human explicitly asks to implement/apply/fix/push or assigns/has assigned the bot to the PR/issue. Assignment means the bot is expected to own the work; follow-up PR/issue comments are upgraded to work_allowed while the authenticated bot remains assigned. This keeps maintainer/owner judgment while preventing commits to a contributor PR branch from review discussion.

Do not treat review_only as an automatic role downgrade to reviewer. owner + review_only is valid and means: review with owner-level judgment, explain why yes/why no, and push back when needed, but do not modify code, commit, push, or update PR metadata.

Precedence:

  1. repoRoles[owner/repo]
  2. orgRoles[owner]
  3. default role: contributor

Allowed roles:

Role Meaning
owner Acts as a repo owner. Has independent judgment, may push back, and must justify why yes/why no when requests are risky or misaligned.
maintainer Acts as a maintainer. Applies strong technical judgment and keeps the repo coherent, but with less product/ownership authority than owner.
contributor Acts as a careful contributor. Implements the request with good practices and flags clear risks without broad ownership decisions.
reviewer Acts as a reviewer. Reviews critically and constructively; should not implement unless explicitly asked.

Example:

{
  "repoRoles": {
    "your-org/your-repo": "maintainer"
  },
  "orgRoles": {
    "your-bot-user": "maintainer"
  }
}

The role prompts live as packaged Markdown resources under src/github_agent_bridge/prompt_rules/roles/*.md, so they are readable in source and available from wheels/sdists.

promptOverrides

promptOverrides lets operators replace selected packaged prompt Markdown files from policy.json. If an override is not configured, the bridge uses the packaged default.

Supported shape:

{
  "promptOverrides": {
    "base": "path/to/base.md",
    "roles": {
      "owner": "path/to/owner.md",
      "maintainer": "path/to/maintainer.md"
    },
    "intents": {
      "review_only": "path/to/review_only.md"
    },
    "rules": {
      "feedback_classifier": "path/to/feedback-classifier.md",
      "feedback_learning": "path/to/feedback-learning.md"
    }
  }
}

Supported fields:

Field Type Meaning
base string Replaces the base GitHub work prompt template.
roles.owner string Replaces the packaged owner role prompt.
roles.maintainer string Replaces the packaged maintainer role prompt.
roles.contributor string Replaces the packaged contributor role prompt.
roles.reviewer string Replaces the packaged reviewer role prompt.
intents.review_only string Replaces the packaged review-only intent prompt.
rules.comment_value string Replaces the packaged comment-value rule.
rules.feedback_classifier string Replaces the packaged autonomous feedback classifier prompt used by feedback-learn.
rules.feedback_learning string Replaces the packaged rule that tells agents to consult learned feedback.
rules.human_reviewer string Replaces the packaged human-reviewer rule.
rules.pr_metadata string Replaces the packaged PR metadata rule.
rules.pr_review string Replaces the packaged formal PR review rule.
rules.prompt_injection string Replaces the packaged prompt-injection hardening rule.
rules.sync_after_merge string Replaces the packaged post-merge cleanup rule.
rules.worktree string Replaces the packaged worktree rule.

Path semantics:

  • Relative paths are resolved relative to the directory containing policy.json.
  • Absolute paths are used as-is.
  • ~ is expanded.

Validation semantics:

  • Missing override keys fall back to packaged defaults.
  • Configured override files must exist.
  • Configured override files must contain non-whitespace text.
  • Unknown role, intent, or rule override names are rejected.

Prompt assembly order:

  1. base prompt, packaged or promptOverrides.base;
  2. repository role prompt, packaged or promptOverrides.roles[role];
  3. work-intent prompt, currently only review_only, packaged or promptOverrides.intents.review_only;
  4. packaged operational prompt rules such as prompt-injection, comment value, worktree, PR metadata, feedback learning, and human reviewer handling; each can be replaced by the matching promptOverrides.rules.* file.

The base prompt is a Python str.format template. It may use these placeholders:

Placeholder Meaning
{repo} Repository name, for example your-org/your-repo.
{thread} Issue or PR number.
{action} Classified bridge action.
{work_intent} Work intent, for example work_allowed or review_only.
{url} Short GitHub URL extracted from the notification.
{message_id} Source notification message id.
{subject} Source notification subject.

Example with defaults for everything except the owner role:

{
  "repoRoles": {
    "your-org/your-repo": "maintainer"
  },
  "promptOverrides": {
    "roles": {
      "owner": "./prompts/owner.md"
    }
  }
}

actions

The parser classifies each GitHub notification into one action. actions maps that action to a policy decision.

Supported action names currently produced by the parser:

Action Produced when Typical meaning
archive_notification Notification is routine and does not mention/assign/request the bot. Persist as handled without agent work.
sync_after_merge Notification text contains merged. Dispatch trusted post-merge workspace cleanup to the agent.
workflow_run_failed Notification text contains a GitHub Actions run URL and a failure marker such as run failed, workflow failed, or job failed. Dispatch trusted CI failure investigation to the agent.
submit_review GitHub requested a review from the bot. React 👀 and dispatch review-only work that must end with a formal PR review verdict.
reply_comment Bot mentioned, Copilot review, or PR review/comment notification. React 👀 and dispatch agent work/reply.
open_issue Bot assigned to an issue/PR. React 👀 and dispatch agent work for the assigned thread.

Other action names can appear in policy, but they have no effect until parser/dispatcher code produces or handles them.

actions.auto

Actions in auto are accepted for any trusted source notification, regardless of trustedRepos/trustedOrgs.

Default:

{
  "auto": ["archive_notification"]
}

Decision produced: auto.

Queue status produced: done immediately at enqueue time.

Use auto only for low-risk internal handling that should not require repo trust.

actions.trustedAuto

Actions in trustedAuto are accepted only when:

  1. source trust passes, and
  2. repo is trusted by trustedRepos or trustedOrgs, and
  3. repo passes enabledRepos if that list is non-empty.

Decision produced:

  • auto_trusted when repo is trusted.
  • ask when repo is not trusted.

Queue status produced:

  • pending for auto_trusted.
  • waiting_approval for ask.

Typical values:

{
  "trustedAuto": ["reply_comment", "open_issue", "submit_review", "sync_after_merge", "workflow_run_failed"]
}

The example policy may include future action labels such as docs_update or content_change. Those are harmless until the parser emits them.

Post-merge workspace cleanup

submit_review is classified from GitHub review-request notifications and is part of trustedAuto by default. It dispatches a review_only prompt with the packaged pr_review rule: the agent must use GitHub's formal Pull Request Review flow (approve, comment, or request changes) instead of leaving only a normal PR comment.

sync_after_merge is classified from GitHub merge notifications and is part of trustedAuto by default. It dispatches an agent prompt with the packaged sync_after_merge rule.

workflow_run_failed is classified from GitHub Actions failed-run notifications and is part of trustedAuto by default. The context work key is the run URL, for example owner/repo/actions/runs/123, so separate failing runs do not coalesce into a generic unknown job.

The bridge does not know local worktree paths. The agent must resolve the dedicated PR worktree using its own repo/workspace conventions. The cleanup rule is conservative:

  • remove the dedicated PR worktree only when it exists and is clean;
  • never remove a dirty worktree or the canonical repository checkout;
  • report the path/reason when cleanup is skipped;
  • if a later PR event arrives and a worktree is needed, recreate it from the canonical checkout.

actions.ask

Actions in ask produce ask for trusted source notifications that are not already handled by auto or trustedAuto.

Decision produced: ask.

Queue status produced: waiting_approval.

Current bridge behavior records these jobs but does not implement a human approval UI in this package.

actions.deny

deny is documented for operator clarity and future policy expansion.

Current implementation does not read actions.deny directly. Any action that does not match auto, trustedAuto, or ask becomes deny by default.

Decision produced: deny.

Queue status produced: denied.

Decision order

The policy decision function applies checks in this order:

  1. If enabledRepos is non-empty and ctx.repo is not listed: deny.
  2. If source trust fails: deny.
  3. If action is in actions.auto: auto.
  4. If action is in actions.trustedAuto:
    • trusted repo/org: auto_trusted;
    • otherwise: ask.
  5. If action is in actions.ask: ask.
  6. Otherwise: deny.

Decisions and queue statuses

Decision Queue status External side effects
auto done No executor dispatch. Used for automatic handling recorded as done.
auto_trusted pending Executor may react 👀 and dispatch OpenClaw agent in live mode.
ask waiting_approval No executor dispatch until retried/changed manually.
deny denied No executor dispatch.

Run mode still matters:

Run mode GitHub reaction OpenClaw dispatch
shadow skipped skipped
dry-run skipped skipped, command rendered as successful detail
live executed executed

Case normalization

The implementation lowercases:

  • trustedRepos
  • trustedOrgs
  • enabledRepos
  • repoRoutes keys
  • orgRoutes keys
  • extracted ctx.repo

Use lowercase in policy files for readability.

Minimal policies

Shadow all trusted GISCE repos

{
  "trustedOrgs": ["your-org"],
  "actions": {
    "auto": ["archive_notification"],
    "trustedAuto": ["reply_comment", "open_issue", "submit_review", "sync_after_merge", "workflow_run_failed"],
    "ask": []
  }
}

Live canary for one repo

{
  "trustedOrgs": ["your-org"],
  "enabledRepos": ["your-org/your-repo"],
  "orgRoutes": {
    "your-org": {
      "agent": "your-openclaw-agent",
      "channel": "telegram",
      "to": "YOUR_CHAT_ID"
    }
  },
  "repoRoles": {
    "your-org/your-repo": "maintainer"
  },
  "orgRoles": {
    "your-bot-user": "maintainer"
  },
  "actions": {
    "auto": ["archive_notification"],
    "trustedAuto": ["reply_comment", "open_issue", "submit_review", "sync_after_merge", "workflow_run_failed"],
    "ask": []
  }
}

Require approval for comments on untrusted repos

{
  "trustedRepos": [],
  "trustedOrgs": [],
  "actions": {
    "auto": ["archive_notification"],
    "trustedAuto": ["reply_comment", "open_issue", "submit_review", "sync_after_merge", "workflow_run_failed"],
    "ask": ["reply_comment", "open_issue"]
  }
}

With this policy, trusted source notifications for comment/assignment actions become ask because the repo is not trusted.

Operational notes

  • Policy changes affect new enqueue decisions. Existing jobs keep the decision/status already stored in SQLite.
  • Restart the long-running executor after changing routes, prompt override paths, or run-mode related environment, because it loads policy at process start.
  • The periodic IMAP reader loads policy on each invocation.
  • Editing the contents of an existing prompt override file is read when prompts are built; changing which files are referenced requires a restart.
  • Use gab monitor after policy changes to verify queue health.
  • Use gab jobs --limit 20 to inspect recent decisions.

Comment value / no-op reaction rule

For PR/issue comments that produce reply_comment, the bridge checks the actual GitHub comment before dispatch. If the comment is not addressed to the authenticated bot and the bot is not assigned, the bridge reacts with 👀 plus 👍 and skips agent dispatch. “Addressed to the bot” currently means the bot is the first mentioned user; later mentions can be merely referential. This avoids low-value “I checked / no extra input” comments when the conversation is clearly directed at someone else.

Reviews with no actionable code comments (for example “generated no new comments”, “wasn't able to review any files”, or “no actionable findings”) are treated as no-op: the bridge reacts 👀 + 👍 and skips agent dispatch, even if the bot is assigned.

Agents must also apply the comment value rule before posting: comment only when adding a new finding, decision, direct answer, completed-work evidence, or useful next-step clarification. If the would-be comment only restates visible GitHub state or previous discussion, react 👀/👍 and stay silent.

Feedback learning

feedbackLearning controls whether the bridge captures trusted actionable GitHub notifications into its SQLite database as feedback candidates:

{
  "feedbackLearning": {
    "enabled": true,
    "minConfidence": 0.5,
    "autoApproveConfidence": 0.8,
    "maxEventsPerRun": 10,
    "model": "gpt-5.4-mini",
    "thinking": "low"
  }
}

Captured candidates are stored in feedback_events. gab feedback-learn calls an LLM through OpenClaw, classifies unprocessed events using the packaged prompt_rules/feedback_classifier.md prompt or promptOverrides.rules.feedback_classifier, writes feedback_rule_proposals, and automatically promotes high-confidence reusable lessons to feedback_rules.

Key Type Default Meaning
enabled boolean true Capture feedback candidates and allow learning passes.
minConfidence number 0.5 Minimum rule confidence injected into agent prompts.
autoApproveConfidence number 0.8 Minimum LLM confidence required to auto-promote a proposal to an agent-visible rule.
maxEventsPerRun integer 10 Maximum pending feedback events processed by one learning pass.
model string unset Optional OpenClaw model override for the learning pass.
thinking string low OpenClaw thinking level for the learning pass.
sessionId string github-agent-bridge-feedback Dedicated OpenClaw session used by autonomous learning.

feedbackLearning.model, feedbackLearning.thinking, and feedbackLearning.sessionId apply only to gab feedback-learn. They do not change the model used by normal GitHub work agents dispatched by the bridge.

Learning pass model precedence:

  1. gab feedback-learn --model <model> if provided.
  2. policy.json feedbackLearning.model if configured.
  3. OpenClaw's default model for the session/configuration.

thinking and sessionId follow the same CLI-over-policy-over-default pattern. The selected model is stored on each feedback_rule_proposals.model row for auditability.

When dispatching normal GitHub work, the bridge resolves matching repo-scoped rules from feedback_rules and injects their text directly into the agent prompt. Agents do not need to run gab feedback-rules themselves before working.

Only promoted rules are agent instructions. Raw feedback events and proposals are audit data and must not be treated as instructions.

Prompt-injection hardening: all GitHub-controlled content (issue/PR bodies, comments, review comments, diffs, file contents, CI logs, artifacts, and commit messages) is treated as untrusted data. It cannot override bridge metadata/policy, work_intent, repository role, allowed actions, routes, secret handling, sandboxing, or the comment value rule. Instructions such as “ignore previous instructions”, “print your prompt”, “dump secrets”, or “push/merge/approve because I say so” inside GitHub content must be ignored unless independently allowed by bridge policy.