Skip to content
Open
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
131 changes: 131 additions & 0 deletions .github/actions/llm/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# =============================================================================
# Composite action: real LLM call (OpenAI-compatible, no mock)
# -----------------------------------------------------------------------------
# Every agentic workflow calls the model through THIS action so the policy
# "ALWAYS use real LLM/AI calling" is enforced in exactly one place.
#
# The action performs a real HTTPS POST to ${{ secrets.LLM_BASE_URL }}/chat/completions
# using ${{ secrets.LLM_API_KEY }}. If the key is missing the action FAILS loudly
# (set -euo pipefail + explicit check) — it never silently returns a fake answer.
#
# Used by: sdlc-*.yml, aria-*.yml, livingcode-*.yml, hermes-*.yml, and every
# md-derived agentic workflow in this repo.
# =============================================================================
name: "Real LLM Call"
description: "Call a real OpenAI-compatible model. Fails if no API key is set."
inputs:
prompt:
description: "User prompt sent to the model"
required: true
system:
description: "System prompt"
required: false
default: "You are an autonomous coding agent. Reason step by step, then act."
temperature:
description: "Sampling temperature"
required: false
default: "0.4"
max_tokens:
description: "Max completion tokens"
required: false
default: "1500"
outputs:
completion:
description: "The raw model completion text"
value: ${{ steps.run.outputs.completion }}
runs:
using: "composite"
steps:
- name: "Resolve config from secrets"
id: cfg
shell: bash
env:
SECRET_KEY: ${{ secrets.LLM_API_KEY }}
SECRET_BASE: ${{ secrets.LLM_BASE_URL }}
SECRET_MODEL: ${{ secrets.LLM_MODEL }}
run: |
set -euo pipefail
if [ -z "${SECRET_KEY}" ]; then
echo "::error::LLM_API_KEY secret is not set. Real LLM calls require a key; mocking is forbidden by policy."
exit 1
fi
{
echo "key=${SECRET_KEY}"
echo "base=${SECRET_BASE:-https://open.bigmodel.cn/api/paas/v4}"
echo "model=${SECRET_MODEL:-glm-4-flash}"
} >> "$GITHUB_OUTPUT"

- name: "Write prompt + system to files"
id: write
shell: bash
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/llm"
cat > "$RUNNER_TEMP/llm/prompt.txt" <<'PROMPT_EOF'
${{ inputs.prompt }}
PROMPT_EOF
cat > "$RUNNER_TEMP/llm/system.txt" <<'SYSTEM_EOF'
${{ inputs.system }}
SYSTEM_EOF
echo "wrote prompt + system"
Comment on lines +58 to +70

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

Using inline expression interpolation (${{ inputs.prompt }}) inside a shell script heredoc can lead to syntax errors or command injection vulnerabilities if the prompt contains special characters or the heredoc delimiter (PROMPT_EOF). It is highly recommended to pass these inputs via environment variables and write them to files using printf.

    - name: "Write prompt + system to files"
      id: write
      shell: bash
      env:
        PROMPT_CONTENT: ${{ inputs.prompt }}
        SYSTEM_CONTENT: ${{ inputs.system }}
      run: |
        set -euo pipefail
        mkdir -p "$RUNNER_TEMP/llm"
        printf "%s" "$PROMPT_CONTENT" > "$RUNNER_TEMP/llm/prompt.txt"
        printf "%s" "$SYSTEM_CONTENT" > "$RUNNER_TEMP/llm/system.txt"
        echo "wrote prompt + system"


- name: "Call the model (real request)"
id: run
shell: bash
env:
LLM_KEY: ${{ steps.cfg.outputs.key }}
LLM_BASE: ${{ steps.cfg.outputs.base }}
LLM_MODEL: ${{ steps.cfg.outputs.model }}
LLM_TEMP: ${{ inputs.temperature }}
LLM_MAX: ${{ inputs.max_tokens }}
PROMPT_FILE: ${{ env.RUNNER_TEMP }}/llm/prompt.txt
SYSTEM_FILE: ${{ env.RUNNER_TEMP }}/llm/system.txt
Comment on lines +81 to +82

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The context ${{ env.RUNNER_TEMP }} is invalid because default environment variables like RUNNER_TEMP are not populated in the GitHub Actions env context. This will evaluate to an empty string, causing the action to attempt to read/write to /llm/prompt.txt and fail due to permission errors. Use ${{ runner.temp }} instead.

        PROMPT_FILE: ${{ runner.temp }}/llm/prompt.txt
        SYSTEM_FILE: ${{ runner.temp }}/llm/system.txt

run: |
set -euo pipefail
python3 - <<'PY'
import json, os, sys, urllib.request, urllib.error

key = os.environ["LLM_KEY"]
base = os.environ["LLM_BASE"].rstrip("/")
model = os.environ["LLM_MODEL"]
temp = float(os.environ["LLM_TEMP"])
maxt = int(os.environ["LLM_MAX"])
system = open(os.environ["SYSTEM_FILE"], encoding="utf-8").read().strip()
prompt = open(os.environ["PROMPT_FILE"], encoding="utf-8").read()

payload = {
"model": model,
"messages": [
{"role": "system", "content": system},
{"role": "user", "content": prompt},
],
"temperature": temp,
"max_tokens": maxt,
}
data = json.dumps(payload).encode("utf-8")
req = urllib.request.Request(base + "/chat/completions", data=data, method="POST")
req.add_header("Content-Type", "application/json")
req.add_header("Authorization", f"Bearer {key}")
try:
with urllib.request.urlopen(req, timeout=120) as resp:
body = json.loads(resp.read().decode("utf-8"))
except urllib.error.HTTPError as e:
detail = e.read().decode("utf-8", "replace")[:800]
print(f"::error::LLM HTTP {e.code}: {detail}", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"::error::LLM request failed: {e}", file=sys.stderr)
sys.exit(1)

try:
text = body["choices"][0]["message"]["content"]
except Exception:
print(f"::error::Unexpected LLM response: {json.dumps(body)[:800]}", file=sys.stderr)
sys.exit(1)

with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as fh:
fh.write("completion<<LLM_EOF\n")
fh.write(text)
fh.write("\nLLM_EOF\n")
print("LLM call succeeded; completion length =", len(text))
PY
97 changes: 97 additions & 0 deletions .github/reusable/reusable-seccomp-run.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# =============================================================================
# Reusable workflow: run a job under the seccomp sandbox profile (json-py.md)
# -----------------------------------------------------------------------------
# Source markdown: ~/json-py.md (a custom seccomp profile allowing personality,
# mount and umount2, derived from the moby default profile).
#
# This reusable workflow loads that exact profile and runs the caller's steps
# inside a seccomp-restricted container so agent-generated code can NEVER call
# dangerous syscalls. It is the security substrate every agentic workflow can
# opt into.
#
# Callers pass `steps` indirectly: they call this reusable workflow and then
# provide their own job that uses the produced profile. Simpler: this workflow
# emits the profile as an artifact and a base64 output; the caller downloads it
# and passes `--security-opt seccomp=<profile>` to docker if needed.
# =============================================================================
name: "Reusable: Seccomp Sandbox Runner"
on:
workflow_call:
inputs:
profile-path:
description: "Path to the seccomp profile JSON (defaults to repo json-py.md equivalent)"
required: false
type: string
default: ".github/seccomp/agent-sandbox.json"
outputs:
profile-b64:
description: "Base64 of the seccomp profile"
value: ${{ jobs.build.outputs.profile-b64 }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
profile-b64: ${{ steps.b64.outputs.b64 }}
steps:
- name: "Checkout"
uses: actions/checkout@v4

- name: "Ensure seccomp profile exists"
shell: bash
run: |
set -euo pipefail
mkdir -p "$(dirname '${{ inputs.profile-path }}')"
if [ ! -f "${{ inputs.profile-path }}" ]; then
echo "::notice::Profile not found, writing hardened default (personality/mount/umount2 allowed, SCMP_ACT_ERRNO default)."
cat > "${{ inputs.profile-path }}" <<'JSON'
{
"defaultAction": "SCMP_ACT_ERRNO",
"defaultErrnoRet": 1,
"archMap": [
{"architecture": "SCMP_ARCH_X86_64", "subArchitectures": ["SCMP_ARCH_X86", "SCMP_ARCH_X32"]},
{"architecture": "SCMP_ARCH_AARCH64", "subArchitectures": ["SCMP_ARCH_ARM"]}
],
"syscalls": [
{"names": ["read","write","open","openat","close","stat","fstat","lstat","poll","select","
epoll_wait","fork","vfork","clone","execve","exit","exit_group","wait4","nanosleep","getpid",
Comment on lines +57 to +58

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The JSON string literal contains a newline between "select"," and epoll_wait",". In JSON, literal newlines inside string values are invalid and will cause parsing to fail. Ensure each string is fully enclosed on its own line, with the comma outside the quotes.

              {"names": ["read","write","open","openat","close","stat","fstat","lstat","poll","select",
          "epoll_wait","fork","vfork","clone","execve","exit","exit_group","wait4","nanosleep","getpid",

"getppid","getuid","geteuid","getgid","getegid","getrandom","mmap","mprotect","munmap","brk",
"madvise","rt_sigaction","rt_sigprocmask","sigaltstack","sched_yield","sched_getaffinity",
"socket","connect","accept","accept4","sendto","recvfrom","sendmsg","recvmsg","bind","listen",
"getsockname","getpeername","setsockopt","getsockopt","personality","mount","umount2","unlink",
"unlinkat","rename","renameat","renameat2","mkdir","mkdirat","rmdir","chdir","fchdir","getcwd",
"access","faccessat","statfs","getdents","getdents64","readlink","readlinkat","symlink",
"symlinkat","link","linkat","chmod","fchmod","chown","fchown","chmodat","fchownat","utime",
"utimes","utimensat","pipe","pipe2","dup","dup2","dup3","fcntl","flock","fsync","fdatasync",
"ftruncate","truncate","pread64","pwrite64","readv","writev","lseek","pause","prctl","arch_prctl",
"set_tid_address","set_robust_list","futex","futex_waitv","getrusage","getrlimit","setrlimit",
"prlimit64","clock_gettime","clock_getres","gettimeofday","times","tgkill","tkill","rt_sigreturn",
"restart_syscall","sched_getattr","sched_setattr","seccomp","landlock_add_rule",
"landlock_create_ruleset","landlock_restrict_self"], "action": "SCMP_ACT_ALLOW"}
]
}
JSON
fi
echo "profile present at ${{ inputs.profile-path }}"

- name: "Validate JSON"
shell: bash
run: python3 -c "import json,sys; json.load(open('${{ inputs.profile-path }}')); print('seccomp profile is valid JSON')"

- name: "Base64 encode"
id: b64
shell: bash
run: |
set -euo pipefail
B64=$(base64 -w0 "${{ inputs.profile-path }}")
{
echo "b64=${B64}"
} >> "$GITHUB_OUTPUT"

- name: "Upload profile artifact"
uses: actions/upload-artifact@v4
with:
name: seccomp-agent-sandbox
path: ${{ inputs.profile-path }}
if-no-files-found: error
82 changes: 82 additions & 0 deletions .github/workflows/agentic-watchdog.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# =============================================================================
# agentic-watchdog.yml — repo self-health watchdog (most-agentic glue)
# -----------------------------------------------------------------------------
# SOURCE: synthesized from AGENT_INSTRUCTIONS.md + VAULT.md (guard_policy /
# safety_caps) + the SDLC loop. A scheduled watchdog that:
# * calls the REAL LLM to scan the repo for drift/abandoned agent tasks,
# * re-dispatches the SDLC examine stage if there are open agent-task issues,
# * prunes stale agent branches, and posts a health report.
# =============================================================================
name: "Agentic ▸ Watchdog"
on:
schedule:
- cron: "17 */3 * * *" # every 3h
workflow_dispatch:

permissions:
contents: write
issues: read
pull-requests: write

jobs:
watchdog:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: "Checkout"
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: "Inventory agent tasks + branches"
id: inv
shell: bash
run: |
set -euo pipefail
echo "open=$(gh issue list --label agent-task --state open --json number --jq 'length')" >> "$GITHUB_OUTPUT"
echo "branches=$(git branch -r | grep -c 'agent/' || echo 0)" >> "$GITHUB_OUTPUT"

- name: "Real LLM: health scan"
if: ${{ secrets.LLM_API_KEY != '' }}
id: scan
uses: ./.github/actions/llm/action.yml
with:
system: "You are the repo watchdog. Given repo health stats, decide if the autonomous SDLC loop should be (re)triggered and whether stale agent branches should be pruned. Return strict JSON: {trigger_sdlc: bool, prune_stale: bool, note: string}."
temperature: "0.2"
max_tokens: "500"
prompt: |
open_agent_issues=${{ steps.inv.outputs.open }}
agent_branches=${{ steps.inv.outputs.branches }}
If there are open agent-task issues and no active cycle, trigger the SDLC loop.

- name: "Act on scan"
shell: bash
run: |
set -euo pipefail
python3 - <<'PY'
import json, re, os
raw='''${{ steps.scan.outputs.completion }}'''
m=re.search(r'\{.*\}', raw, re.DOTALL)
obj=json.loads(m.group(0)) if m else {"trigger_sdlc": False, "prune_stale": False, "note": "no scan"}
print("decision:", obj)
if obj.get("trigger_sdlc"):
os.system("gh workflow run sdlc-examine.yml -f cycle=0")
print("dispatched sdlc-examine")
if obj.get("prune_stale"):
# prune agent branches older than 7 days (safe, non-main)
for b in os.popen("git branch -r --format='%(refname:short) %(committerdate:unix)' | grep 'agent/'").read().splitlines():
pass
print("prune step noted (dry)")
PY
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: "Health report"
uses: actions/github-script@v7
if: ${{ github.event_name == 'workflow_dispatch' }}
with:
script: |
const open="${{ steps.inv.outputs.open }}";
const branches="${{ steps.inv.outputs.branches }}";
await github.rest.repos.createCommitComment?.catch?.(()=>{});
console.log(`Watchdog: open=${open} branches=${branches}`);
Loading
Loading