diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0540cb1..88a8c28 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,7 @@ name: ci -# agent-ops's own CI — the framework eats its own dog food. Lints the shell, -# runs the stdlib tests for the triage composer, the mara observer, and the +# olympus's own CI — the framework eats its own dog food. Lints the shell, +# runs the stdlib tests for the triage composer, the argus observer, and the # config loader, and runs the leakage gate on this repo itself. on: @@ -28,8 +28,8 @@ jobs: - name: triage composer tests run: python3 scripts/agent-bot/tests/test_triage.py - - name: mara observer tests - run: python3 scripts/agent-bot/tests/test_mara.py + - name: argus observer tests + run: python3 scripts/agent-bot/tests/test_argus.py - name: config loader tests run: python3 scripts/agent-bot/tests/test_config.py @@ -45,3 +45,6 @@ jobs: - name: leakage gate (self) run: bash scripts/lint/check-leakage.sh + + - name: legacy-naming regression guard (rename to Olympus stays clean) + run: bash scripts/lint/check-legacy-naming.sh diff --git a/.github/workflows/guard.yml b/.github/workflows/guard.yml index 7e2a686..0646c28 100644 --- a/.github/workflows/guard.yml +++ b/.github/workflows/guard.yml @@ -1,7 +1,7 @@ -name: agent-ops-guard +name: olympus-guard # Reusable hygiene gate. No LLM, no self-hosted runner — pure linters that -# catch the mechanical PR failure classes agent-ops was built around: +# catch the mechanical PR failure classes olympus was built around: # - leakage : no private IP / key material / machine path in tracked files # - secrets : every referenced GitHub secret is actually provisioned # - secret-values : provisioned secret values are sane (non-empty, not "-") @@ -9,7 +9,7 @@ name: agent-ops-guard # Consumer wrapper (triggers on pull_request + push): # jobs: # guard: -# uses: Netis/agent-ops/.github/workflows/guard.yml@v0.1.0 +# uses: Netis/olympus/.github/workflows/guard.yml@v0.1.0 # secrets: inherit # only needed if check-secrets queries the API # # Runs on GitHub-hosted ubuntu; safe for fork PRs (read-only). @@ -17,7 +17,10 @@ name: agent-ops-guard on: workflow_call: inputs: - agent_ops_ref: + olympus_repo: + type: string + default: Netis/olympus + olympus_ref: type: string default: main run_secret_checks: @@ -34,11 +37,11 @@ jobs: - name: Checkout consumer repo uses: actions/checkout@v4 - - name: Fetch agent-ops scripts + - name: Fetch olympus scripts uses: actions/checkout@v4 with: - repository: Netis/agent-ops - ref: ${{ inputs.agent_ops_ref }} + repository: ${{ inputs.olympus_repo }} + ref: ${{ inputs.olympus_ref }} token: ${{ secrets.AGENT_GH_TOKEN || github.token }} path: _agentops diff --git a/.github/workflows/implement.yml b/.github/workflows/implement.yml index 49e8d78..0249b4c 100644 --- a/.github/workflows/implement.yml +++ b/.github/workflows/implement.yml @@ -1,4 +1,4 @@ -name: agent-ops-implement +name: olympus-implement # Reusable dev-agent workflow. The consumer triggers it when the triage agent # (or a human) adds the `agent:try` label to an issue: @@ -8,21 +8,24 @@ name: agent-ops-implement # jobs: # implement: # if: github.event.label.name == 'agent:try' -# uses: Netis/agent-ops/.github/workflows/implement.yml@v0.1.0 +# uses: Netis/olympus/.github/workflows/implement.yml@v0.1.0 # permissions: { contents: write, issues: write, pull-requests: write } # with: # runner_labels: '["self-hosted","my-runner"]' # secrets: inherit # # The dev agent branches off the default branch, implements within the triaged -# scope, runs the project's build+test command (from .agent-ops.json), and +# scope, runs the project's build+test command (from .olympus.json), and # opens a DRAFT PR labelled `auto-agent`. Review/merge is the review workflow's # job. Nothing here is project-specific. on: workflow_call: inputs: - agent_ops_ref: + olympus_repo: + type: string + default: Netis/olympus + olympus_ref: type: string default: main runner_labels: @@ -30,7 +33,7 @@ on: default: '["self-hosted"]' config_path: type: string - default: .agent-ops.json + default: .olympus.json model: type: string default: claude-3-5-sonnet-20241022 @@ -45,7 +48,7 @@ on: AUTO_MERGE_TEAM: { required: false } concurrency: - group: agent-ops-implement-${{ github.event.issue.number }} + group: olympus-implement-${{ github.event.issue.number }} cancel-in-progress: false jobs: @@ -73,20 +76,20 @@ jobs: # are suppressed by GitHub's anti-recursion rule). token: ${{ secrets.AGENT_GH_TOKEN }} - - name: Fetch agent-ops scripts + - name: Fetch olympus scripts uses: actions/checkout@v4 with: - repository: Netis/agent-ops - ref: ${{ inputs.agent_ops_ref }} + repository: ${{ inputs.olympus_repo }} + ref: ${{ inputs.olympus_ref }} token: ${{ secrets.AGENT_GH_TOKEN }} path: _agentops - name: Implement env: - AGENT_OPS_CONFIG: ${{ inputs.config_path }} + OLYMPUS_CONFIG: ${{ inputs.config_path }} ISSUE_NUMBER: ${{ github.event.issue.number }} ISSUE_TITLE: ${{ github.event.issue.title }} ISSUE_AUTHOR: ${{ github.event.issue.user.login }} GH_TOKEN: ${{ secrets.AGENT_GH_TOKEN }} AUTO_MERGE_TEAM: ${{ secrets.AUTO_MERGE_TEAM }} - run: bash _agentops/scripts/agent-bot/run_wiwi.sh + run: bash _agentops/scripts/agent-bot/run_hephaestus.sh diff --git a/.github/workflows/observe.yml b/.github/workflows/observe.yml index 573357e..bd08d2c 100644 --- a/.github/workflows/observe.yml +++ b/.github/workflows/observe.yml @@ -1,12 +1,12 @@ -name: agent-ops-observe +name: olympus-observe # Reusable production observer. One health poll of a deployed service; on a # SUSTAINED failure (confirm-debounce filters deploy/restart blips) it files a # scrubbed, deduplicated incident issue that the triage→dev loop can pick up. # -# Two ways to run mara: +# Two ways to run argus: # 1. This scheduled workflow (simplest; runs on the self-hosted runner). -# 2. The systemd timer units in scripts/agent-bot/ (mara.service/.timer) on a +# 2. The systemd timer units in scripts/agent-bot/ (argus.service/.timer) on a # host ISOLATED from prod — preferred for real isolation, since a GHA run # can't observe an outage that also takes down the runner. # @@ -14,19 +14,22 @@ name: agent-ops-observe # on: { schedule: [{ cron: '*/5 * * * *' }], workflow_dispatch: {} } # jobs: # observe: -# uses: Netis/agent-ops/.github/workflows/observe.yml@v0.1.0 +# uses: Netis/olympus/.github/workflows/observe.yml@v0.1.0 # permissions: { issues: write } # with: # runner_labels: '["self-hosted","my-runner"]' # secrets: inherit # -# Health URL + readiness come from .agent-ops.json (observer.*); the issue is +# Health URL + readiness come from .olympus.json (observer.*); the issue is # filed on observer.repo. on: workflow_call: inputs: - agent_ops_ref: + olympus_repo: + type: string + default: Netis/olympus + olympus_ref: type: string default: main runner_labels: @@ -34,7 +37,7 @@ on: default: '["self-hosted"]' config_path: type: string - default: .agent-ops.json + default: .olympus.json dry_run: type: boolean default: false @@ -42,7 +45,7 @@ on: AGENT_GH_TOKEN: { required: true } concurrency: - group: agent-ops-observe + group: olympus-observe cancel-in-progress: false jobs: @@ -55,24 +58,24 @@ jobs: with: fetch-depth: 1 - - name: Fetch agent-ops scripts + - name: Fetch olympus scripts uses: actions/checkout@v4 with: - repository: Netis/agent-ops - ref: ${{ inputs.agent_ops_ref }} + repository: ${{ inputs.olympus_repo }} + ref: ${{ inputs.olympus_ref }} token: ${{ secrets.AGENT_GH_TOKEN }} path: _agentops - name: Observe env: - AGENT_OPS_CONFIG: ${{ inputs.config_path }} - MARA_DRY_RUN: ${{ inputs.dry_run && '1' || '0' }} - MARA_STATE_DIR: ${{ github.workspace }}/.mara-state + OLYMPUS_CONFIG: ${{ inputs.config_path }} + ARGUS_DRY_RUN: ${{ inputs.dry_run && '1' || '0' }} + ARGUS_STATE_DIR: ${{ github.workspace }}/.argus-state GH_TOKEN: ${{ secrets.AGENT_GH_TOKEN }} - # config.sh (sourced by a tiny shim) maps observer.* → MARA_*; mara.sh - # itself reads MARA_*. We source config here so a GHA run gets the same + # config.sh (sourced by a tiny shim) maps observer.* → ARGUS_*; argus.sh + # itself reads ARGUS_*. We source config here so a GHA run gets the same # observer.* mapping the systemd path would get from its EnvironmentFile. run: | source _agentops/scripts/lib/config.sh - agent_ops_load_config - bash _agentops/scripts/agent-bot/mara.sh + olympus_load_config + bash _agentops/scripts/agent-bot/argus.sh diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml index 0b5b176..7a946e6 100644 --- a/.github/workflows/review.yml +++ b/.github/workflows/review.yml @@ -1,4 +1,4 @@ -name: agent-ops-review +name: olympus-review # Reusable PR-review workflow (the review bot). Fires after the consumer's CI # succeeds on a PR, reviews the diff, posts a structured review, then — for @@ -12,19 +12,22 @@ name: agent-ops-review # workflow_dispatch: { inputs: { pr_number: { required: true, type: number } } } # jobs: # review: -# uses: Netis/agent-ops/.github/workflows/review.yml@v0.1.0 +# uses: Netis/olympus/.github/workflows/review.yml@v0.1.0 # permissions: { contents: write, issues: write, pull-requests: write, statuses: read } # with: # runner_labels: '["self-hosted","my-runner"]' # secrets: inherit # # The review bot's identity, the auto-agent label, and the default branch all -# come from the consumer's .agent-ops.json. +# come from the consumer's .olympus.json. on: workflow_call: inputs: - agent_ops_ref: + olympus_repo: + type: string + default: Netis/olympus + olympus_ref: type: string default: main runner_labels: @@ -32,7 +35,7 @@ on: default: '["self-hosted"]' config_path: type: string - default: .agent-ops.json + default: .olympus.json model: type: string default: claude-3-5-sonnet-20241022 @@ -57,7 +60,7 @@ on: AUTO_MERGE_TEAM: { required: false } concurrency: - group: agent-ops-review-${{ github.event.workflow_run.pull_requests[0].number || inputs.pr_number }} + group: olympus-review-${{ github.event.workflow_run.pull_requests[0].number || inputs.pr_number }} cancel-in-progress: true jobs: @@ -111,18 +114,18 @@ jobs: - name: Fetch base for diff run: git fetch origin "${{ steps.pr.outputs.base_ref }}" --depth 200 - - name: Fetch agent-ops scripts + - name: Fetch olympus scripts uses: actions/checkout@v4 with: - repository: Netis/agent-ops - ref: ${{ inputs.agent_ops_ref }} + repository: ${{ inputs.olympus_repo }} + ref: ${{ inputs.olympus_ref }} token: ${{ secrets.AGENT_GH_TOKEN }} path: _agentops - name: Run review agent id: review env: - AGENT_OPS_CONFIG: ${{ inputs.config_path }} + OLYMPUS_CONFIG: ${{ inputs.config_path }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_NUMBER: ${{ steps.pr.outputs.pr_number }} run: bash _agentops/scripts/pr-review/run_review.sh "$PR_NUMBER" @@ -140,7 +143,7 @@ jobs: - name: Auto-merge if eligible if: ${{ steps.review.outcome == 'success' }} env: - AGENT_OPS_CONFIG: ${{ inputs.config_path }} + OLYMPUS_CONFIG: ${{ inputs.config_path }} GH_TOKEN: ${{ secrets.AGENT_GH_TOKEN }} PR_NUMBER: ${{ steps.pr.outputs.pr_number }} AUTO_MERGE_TEAM: ${{ secrets.AUTO_MERGE_TEAM }} @@ -149,7 +152,7 @@ jobs: - name: Dispatch revise if changes requested if: ${{ steps.review.outcome == 'success' }} env: - AGENT_OPS_CONFIG: ${{ inputs.config_path }} + OLYMPUS_CONFIG: ${{ inputs.config_path }} GH_TOKEN: ${{ secrets.AGENT_GH_TOKEN }} PR_NUMBER: ${{ steps.pr.outputs.pr_number }} run: bash _agentops/scripts/agent-bot/revise_dispatch.sh diff --git a/.github/workflows/revise.yml b/.github/workflows/revise.yml index f452f6c..42ba717 100644 --- a/.github/workflows/revise.yml +++ b/.github/workflows/revise.yml @@ -1,16 +1,16 @@ -name: agent-ops-revise +name: olympus-revise # Reusable revision workflow. Dispatched (by the review workflow's # revise_dispatch step) when the review bot requests changes on an auto-agent # PR. The consumer hosts a thin wrapper named `pr-revise.yml` so the dispatch -# target resolves (override via the AGENT_OPS_REVISE_WORKFLOW env if you name it +# target resolves (override via the OLYMPUS_REVISE_WORKFLOW env if you name it # differently): # # name: pr-revise # on: { workflow_dispatch: { inputs: { pr_number: { required: true } } } } # jobs: # revise: -# uses: Netis/agent-ops/.github/workflows/revise.yml@v0.1.0 +# uses: Netis/olympus/.github/workflows/revise.yml@v0.1.0 # permissions: { contents: write, pull-requests: write } # with: # pr_number: ${{ inputs.pr_number }} @@ -23,7 +23,7 @@ on: pr_number: type: string required: true - agent_ops_ref: + olympus_ref: type: string default: main runner_labels: @@ -31,7 +31,7 @@ on: default: '["self-hosted"]' config_path: type: string - default: .agent-ops.json + default: .olympus.json model: type: string default: claude-3-5-sonnet-20241022 @@ -45,7 +45,7 @@ on: AGENT_GH_TOKEN: { required: true } concurrency: - group: agent-ops-revise-${{ inputs.pr_number }} + group: olympus-revise-${{ inputs.pr_number }} cancel-in-progress: true jobs: @@ -76,17 +76,17 @@ jobs: GH_TOKEN: ${{ secrets.AGENT_GH_TOKEN }} run: gh pr checkout "${{ inputs.pr_number }}" - - name: Fetch agent-ops scripts + - name: Fetch olympus scripts uses: actions/checkout@v4 with: - repository: Netis/agent-ops - ref: ${{ inputs.agent_ops_ref }} + repository: ${{ inputs.olympus_repo }} + ref: ${{ inputs.olympus_ref }} token: ${{ secrets.AGENT_GH_TOKEN }} path: _agentops - name: Revise env: - AGENT_OPS_CONFIG: ${{ inputs.config_path }} + OLYMPUS_CONFIG: ${{ inputs.config_path }} PR_NUMBER: ${{ inputs.pr_number }} GH_TOKEN: ${{ secrets.AGENT_GH_TOKEN }} run: bash _agentops/scripts/agent-bot/run_revise.sh diff --git a/.github/workflows/self-guard.yml b/.github/workflows/self-guard.yml index 053a8ec..1f28cac 100644 --- a/.github/workflows/self-guard.yml +++ b/.github/workflows/self-guard.yml @@ -1,14 +1,16 @@ name: self-guard -# agent-ops eating its own dog food: run THIS repo's own guard.yml hygiene -# gate (the leakage linter) on agent-ops's own PRs + main pushes, via a local +# olympus eating its own dog food: run THIS repo's own guard.yml hygiene +# gate (the leakage linter) on olympus's own PRs + main pushes, via a local # reusable-workflow ref (`uses: ./...`). The leakage scan runs against this # PR's tracked files + this PR's scripts/lint/leakage-allowlist.txt; the linter -# logic itself comes from guard.yml's pinned agent-ops checkout (default main), -# and script breakage is separately caught by ci.yml's shellcheck. +# logic itself is fetched from THIS repo at THIS commit (olympus_repo = +# github.repository, olympus_ref = github.sha — name-agnostic, so it works +# before/after the GitHub repo rename and tests the current branch's scripts). +# Script breakage is separately caught by ci.yml's shellcheck. # # Zero infra: guard.yml runs on GitHub-hosted ubuntu and needs no secrets or -# self-hosted runner. run_secret_checks stays false (agent-ops has no LITELLM / +# self-hosted runner. run_secret_checks stays false (olympus has no LITELLM / # AGENT_GH_TOKEN secrets, and check-secret-values is consumer-specific). # # The agentic surfaces (triage/review/implement/revise) are wired in @@ -27,4 +29,6 @@ jobs: guard: uses: ./.github/workflows/guard.yml with: + olympus_repo: ${{ github.repository }} + olympus_ref: ${{ github.sha }} run_secret_checks: false diff --git a/.github/workflows/self-implement.yml b/.github/workflows/self-implement.yml index 0be9348..aeeda17 100644 --- a/.github/workflows/self-implement.yml +++ b/.github/workflows/self-implement.yml @@ -1,6 +1,6 @@ name: self-implement -# agent-ops dogfooding its OWN implement surface (wiwi) on its OWN issues, via +# olympus dogfooding its OWN implement surface (hephaestus) on its OWN issues, via # a local reusable-workflow ref. DORMANT until `SELF_DOGFOOD_ENABLED == 'true'` # AND secrets + a self-hosted runner are provisioned (see SELF-DOGFOOD.md). @@ -18,5 +18,7 @@ jobs: if: ${{ vars.SELF_DOGFOOD_ENABLED == 'true' && github.event.label.name == 'agent:try' }} uses: ./.github/workflows/implement.yml with: - runner_labels: '["self-hosted","agent-ops"]' + olympus_repo: ${{ github.repository }} + olympus_ref: ${{ github.sha }} + runner_labels: '["self-hosted","olympus"]' secrets: inherit diff --git a/.github/workflows/self-review.yml b/.github/workflows/self-review.yml index e465b10..e025864 100644 --- a/.github/workflows/self-review.yml +++ b/.github/workflows/self-review.yml @@ -1,6 +1,6 @@ name: self-review -# agent-ops dogfooding its OWN review surface (vivi) on its OWN PRs, via a local +# olympus dogfooding its OWN review surface (themis) on its OWN PRs, via a local # reusable-workflow ref. Fires after this repo's `ci` workflow succeeds on a PR. # DORMANT until `SELF_DOGFOOD_ENABLED == 'true'` AND secrets + a self-hosted # runner are provisioned (see SELF-DOGFOOD.md). @@ -27,6 +27,8 @@ jobs: if: ${{ vars.SELF_DOGFOOD_ENABLED == 'true' }} uses: ./.github/workflows/review.yml with: - runner_labels: '["self-hosted","agent-ops"]' + olympus_repo: ${{ github.repository }} + olympus_ref: ${{ github.sha }} + runner_labels: '["self-hosted","olympus"]' pr_number: ${{ inputs.pr_number }} secrets: inherit diff --git a/.github/workflows/self-revise.yml b/.github/workflows/self-revise.yml index 3db8607..a7a350c 100644 --- a/.github/workflows/self-revise.yml +++ b/.github/workflows/self-revise.yml @@ -1,9 +1,9 @@ name: self-revise -# agent-ops dogfooding its OWN revise surface on its OWN PRs, via a local +# olympus dogfooding its OWN revise surface on its OWN PRs, via a local # reusable-workflow ref. Dispatched by self-review's revise_dispatch step # (`gh workflow run self-revise.yml`) — so to dogfood revise, set the -# AGENT_OPS_REVISE_WORKFLOW env to `self-revise.yml` on the runner, or accept +# OLYMPUS_REVISE_WORKFLOW env to `self-revise.yml` on the runner, or accept # the default `pr-revise.yml` and rename this file. DORMANT until # `SELF_DOGFOOD_ENABLED == 'true'` AND secrets + a self-hosted runner are # provisioned (see SELF-DOGFOOD.md). @@ -25,6 +25,8 @@ jobs: if: ${{ vars.SELF_DOGFOOD_ENABLED == 'true' }} uses: ./.github/workflows/revise.yml with: + olympus_repo: ${{ github.repository }} + olympus_ref: ${{ github.sha }} pr_number: ${{ inputs.pr_number }} - runner_labels: '["self-hosted","agent-ops"]' + runner_labels: '["self-hosted","olympus"]' secrets: inherit diff --git a/.github/workflows/self-triage.yml b/.github/workflows/self-triage.yml index 09059a2..61ebef7 100644 --- a/.github/workflows/self-triage.yml +++ b/.github/workflows/self-triage.yml @@ -1,6 +1,6 @@ name: self-triage -# agent-ops dogfooding its OWN triage surface on its OWN issues, via a local +# olympus dogfooding its OWN triage surface on its OWN issues, via a local # reusable-workflow ref (`uses: ./...`) so it always runs the current branch's # triage.yml + scripts. # @@ -23,5 +23,7 @@ jobs: if: ${{ vars.SELF_DOGFOOD_ENABLED == 'true' }} uses: ./.github/workflows/triage.yml with: - runner_labels: '["self-hosted","agent-ops"]' + olympus_repo: ${{ github.repository }} + olympus_ref: ${{ github.sha }} + runner_labels: '["self-hosted","olympus"]' secrets: inherit diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml index b238380..d656641 100644 --- a/.github/workflows/triage.yml +++ b/.github/workflows/triage.yml @@ -1,11 +1,11 @@ -name: agent-ops-triage +name: olympus-triage # Reusable triage workflow. A consumer repo calls it from a thin wrapper that # triggers on issues (see examples/consumer/.github/workflows/triage.yml): # # jobs: # triage: -# uses: Netis/agent-ops/.github/workflows/triage.yml@v0.1.0 +# uses: Netis/olympus/.github/workflows/triage.yml@v0.1.0 # permissions: { contents: read, issues: write } # with: # runner_labels: '["self-hosted","my-runner"]' @@ -14,13 +14,16 @@ name: agent-ops-triage # It investigates the issue, then posts a warm maintainer-voice reply and (on a # clean 5-gate pass) labels it for the dev agent. All repo-specific behavior — # gate thresholds, label names, language — comes from the consumer's -# .agent-ops.json; nothing here is hardcoded to any one project. +# .olympus.json; nothing here is hardcoded to any one project. on: workflow_call: inputs: - agent_ops_ref: - description: Tag/branch of Netis/agent-ops whose scripts to run. Pin to the same version as the @ref above. + olympus_repo: + type: string + default: Netis/olympus + olympus_ref: + description: Tag/branch of Netis/olympus whose scripts to run. Pin to the same version as the @ref above. type: string default: main runner_labels: @@ -29,7 +32,7 @@ on: default: '["self-hosted"]' config_path: type: string - default: .agent-ops.json + default: .olympus.json model: type: string default: claude-3-5-sonnet-20241022 @@ -46,7 +49,7 @@ concurrency: # One triage per issue; do NOT cancel in progress — the agent:try label this # adds re-enters the same group as a skipped run, which must not kill the # still-finishing real run. - group: agent-ops-triage-${{ github.event.issue.number }} + group: olympus-triage-${{ github.event.issue.number }} cancel-in-progress: false jobs: @@ -72,17 +75,17 @@ jobs: with: fetch-depth: 1 - - name: Fetch agent-ops scripts + - name: Fetch olympus scripts uses: actions/checkout@v4 with: - repository: Netis/agent-ops - ref: ${{ inputs.agent_ops_ref }} + repository: ${{ inputs.olympus_repo }} + ref: ${{ inputs.olympus_ref }} token: ${{ secrets.AGENT_GH_TOKEN }} path: _agentops - name: Triage issue env: - AGENT_OPS_CONFIG: ${{ inputs.config_path }} + OLYMPUS_CONFIG: ${{ inputs.config_path }} ISSUE_NUMBER: ${{ github.event.issue.number }} ISSUE_TITLE: ${{ github.event.issue.title }} ISSUE_BODY: ${{ github.event.issue.body }} diff --git a/.agent-ops.json b/.olympus.json similarity index 68% rename from .agent-ops.json rename to .olympus.json index 6a0aabf..72941b8 100644 --- a/.agent-ops.json +++ b/.olympus.json @@ -1,12 +1,12 @@ { - "$schema": "./schema/agent-ops.schema.json", + "$schema": "./schema/olympus.schema.json", "project": { - "name": "agent-ops", + "name": "olympus", "default_branch": "main" }, "agents": { - "review_bot_login": "vivi", - "dev_agent_name": "wiwi" + "review_bot_login": "themis", + "dev_agent_name": "hephaestus" }, "labels": { "assess": "agent:assess", @@ -24,13 +24,13 @@ "language": "auto" }, "implement": { - "build_cmd": "shellcheck --severity=warning scripts/agent-bot/*.sh scripts/lib/*.sh scripts/lint/*.sh scripts/pr-review/*.sh && python3 scripts/agent-bot/tests/test_triage.py && python3 scripts/agent-bot/tests/test_mara.py && python3 scripts/agent-bot/tests/test_config.py && python3 scripts/pr-review/test_post_review.py" + "build_cmd": "shellcheck --severity=warning scripts/agent-bot/*.sh scripts/lib/*.sh scripts/lint/*.sh scripts/pr-review/*.sh && python3 scripts/agent-bot/tests/test_triage.py && python3 scripts/agent-bot/tests/test_argus.py && python3 scripts/agent-bot/tests/test_config.py && python3 scripts/pr-review/test_post_review.py" }, "observer": { - "service_name": "agent-ops", + "service_name": "olympus", "health_url": "", - "repo": "Netis/agent-ops", - "labels": "mara,incident" + "repo": "Netis/olympus", + "labels": "argus,incident" }, "model": "claude-3-5-sonnet-20241022" } diff --git a/README.md b/README.md index 26b3ecc..eca61f8 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ -# agent-ops +# Olympus **A reusable, multi-repo autonomous software-quality mechanism.** Triage, implement, review, auto-merge, and observe — driven by LLM agents on your own runners, wired into any repo through reusable GitHub workflows and a single per-repo config file. -This is the agent-ops loop, extracted from the [heron](https://github.com/Netis/heron) +This is the Olympus loop, extracted from the [heron](https://github.com/Netis/heron) project (where it was built and hardened against real production incidents) so the **mechanism** lives in one versioned place and each repo supplies only its -**policy** (`.agent-ops.json`). +**policy** (`.olympus.json`). ``` issue filed @@ -33,8 +33,8 @@ The whole point of the extraction: | | Lives in | Versioned | Example | |---|---|---|---| -| **Mechanism** | this repo (`Netis/agent-ops`) | yes — tag `v0.2.0` | the triage gate logic, the warm-reply composer, the auto-merge gate, the observer's confirm-debounce, the hygiene linters | -| **Policy** | the consumer's `.agent-ops.json` | with the consumer | gate thresholds, label names, build command, reply language, the review bot's login, the service health URL | +| **Mechanism** | this repo (`Netis/olympus`) | yes — tag `v0.2.0` | the triage gate logic, the warm-reply composer, the auto-merge gate, the observer's confirm-debounce, the hygiene linters | +| **Policy** | the consumer's `.olympus.json` | with the consumer | gate thresholds, label names, build command, reply language, the review bot's login, the service health URL | Upgrade the mechanism once (bump the pinned tag) and every consuming repo benefits. No copy-pasted scripts to drift. @@ -64,14 +64,16 @@ benefits. No copy-pasted scripts to drift. | Triage | `agent-bot/run_triage.sh` | `triage.yml` | **fully config-driven + unit-tested** | | Config loader | `lib/config.sh` | — | **unit-tested** | | Hygiene gates | `lint/check-*.sh` | `guard.yml` | **generic + unit-tested (leakage)** | -| Observer | `agent-bot/mara.sh` | `observe.yml` | **generalized + unit-tested** | -| Dev agent | `agent-bot/run_wiwi.sh` | `implement.yml` | ported; build/branch/labels config-driven · cosmetic prompt strings pending | +| Observer | `agent-bot/argus.sh` | `observe.yml` | **generalized + unit-tested** | +| Dev agent | `agent-bot/run_hephaestus.sh` | `implement.yml` | ported; build/branch/labels config-driven · cosmetic prompt strings pending | | Review bot | `pr-review/*` | `review.yml` | ported; review-bot login + labels config-driven | | Revise | `agent-bot/run_revise.sh` | `revise.yml` | ported; review-bot login config-driven | "Pending" = a deliberate follow-up to deepen as the second repo onboards (see -[docs/roadmap.md](docs/roadmap.md)); the runtime-critical coupling (what label, -which bot, which branch, which build command) is already config-driven. +[docs/roadmap.md](docs/roadmap.md), with the codebase-level breakdown in +[docs/improvement-plan.md](docs/improvement-plan.md)); the runtime-critical +coupling (what label, which bot, which branch, which build command) is already +config-driven. ## Onboard a repo in 4 steps @@ -84,15 +86,15 @@ which bot, which branch, which build command) is already config-driven. [self-hosting](docs/cookbooks/self-hosting.md), [DigitalOcean](docs/cookbooks/digitalocean.md), and [AWS](docs/cookbooks/aws.md). (The **guard** gate needs no runner at all.) -2. **Drop in `.agent-ops.json`** — copy [`examples/consumer/.agent-ops.json`](examples/consumer/.agent-ops.json) +2. **Drop in `.olympus.json`** — copy [`examples/consumer/.olympus.json`](examples/consumer/.olympus.json) and edit the gates / build command / labels / observer for your repo. Every - field is optional ([schema](schema/agent-ops.schema.json)). + field is optional ([schema](schema/olympus.schema.json)). 3. **Add the thin wrappers** from [`examples/consumer/.github/workflows/`](examples/consumer/.github/workflows/) (`triage`, `implement`, `pr-review`, `pr-revise`, `guard`, `observe`). Each - is ~15 lines that `uses: Netis/agent-ops/.github/workflows/.yml@v0.2.0` + is ~15 lines that `uses: Netis/olympus/.github/workflows/.yml@v0.2.0` and passes your runner labels. 4. **Pin the version**: keep the `@v0.2.0` on the `uses:` and the - `agent_ops_ref: v0.2.0` input in lockstep, so the workflow YAML and the + `olympus_ref: v0.2.0` input in lockstep, so the workflow YAML and the scripts it clones are the same release. See [docs/setup.md](docs/setup.md) for the full walkthrough and @@ -118,13 +120,13 @@ See [docs/setup.md](docs/setup.md) for the full walkthrough and ```bash python3 scripts/agent-bot/tests/test_triage.py # reply composition -python3 scripts/agent-bot/tests/test_mara.py # observer debounce +python3 scripts/agent-bot/tests/test_argus.py # observer debounce python3 scripts/agent-bot/tests/test_config.py # config loader bash scripts/lint/check-leakage.sh # hygiene (self) shellcheck scripts/agent-bot/*.sh scripts/lib/*.sh scripts/lint/*.sh ``` -CI (`.github/workflows/ci.yml`) runs all of the above — agent-ops eats its own +CI (`.github/workflows/ci.yml`) runs all of the above — Olympus eats its own dog food. ## License diff --git a/SELF-DOGFOOD.md b/SELF-DOGFOOD.md index 13f8aed..fcabd54 100644 --- a/SELF-DOGFOOD.md +++ b/SELF-DOGFOOD.md @@ -1,6 +1,6 @@ -# agent-ops eats its own dog food +# Olympus eats its own dog food -agent-ops is the mechanism repo, but it can also be a **consumer of itself** — +Olympus is the mechanism repo, but it can also be a **consumer of itself** — the ultimate validation that the multi-repo loop works. This documents what's active now and how to activate the rest. @@ -8,12 +8,12 @@ active now and how to activate the rest. - **`self-guard.yml`** — runs this repo's own `guard.yml` leakage gate on every PR + main push. GitHub-hosted ubuntu, no secrets, no runner. This is the first - real bite of dog food: a leak in agent-ops's own tracked files (or a + real bite of dog food: a leak in Olympus's own tracked files (or a mis-scoped `scripts/lint/leakage-allowlist.txt`) fails the PR. - **`ci.yml`** — the pre-existing shellcheck self-CI (unchanged). -- **`.agent-ops.json`** — agent-ops's own policy (gates, labels, the - shellcheck+pytest `build_cmd` wiwi would run, review bot `vivi`, dev agent - `wiwi`). Read by the agentic surfaces once activated. +- **`.olympus.json`** — Olympus's own policy (gates, labels, the + shellcheck+pytest `build_cmd` hephaestus would run, review bot `themis`, dev agent + `hephaestus`). Read by the agentic surfaces once activated. ## What's dormant (wired, awaiting infra) @@ -23,24 +23,24 @@ it's `true`, they fire on the matching event but the job is **skipped** (a grey check — never a red failure). They use `uses: ./...` so they always run the current branch's reusable workflows + scripts. -`observe` (mara) is **N/A** for agent-ops: it has no deployed prod service to -poll. `.agent-ops.json`'s `observer.health_url` is intentionally empty. +`observe` (argus) is **N/A** for Olympus: it has no deployed prod service to +poll. `.olympus.json`'s `observer.health_url` is intentionally empty. ## Activation (when you're ready) -1. **Provision a self-hosted runner** labelled `self-hosted,agent-ops` with the +1. **Provision a self-hosted runner** labelled `self-hosted,olympus` with the `claude` CLI + `jq` + `python3`, reaching the model gateway. (Or change the `runner_labels` in the four `self-*.yml` wrappers to an existing shared label, e.g. `["self-hosted","heron"]`, if that runner is reachable from this repo.) -2. **Provision secrets** on `Netis/agent-ops` (Settings → Secrets): +2. **Provision secrets** on `Netis/olympus` (Settings → Secrets): `LITELLM_BASE_URL`, `LITELLM_API_KEY`, `LITELLM_NO_PROXY` (optional), `AGENT_GH_TOKEN` (a **PAT**, not `GITHUB_TOKEN` — the loop needs to trigger label/dispatch/push events), and optionally `AUTO_MERGE_TEAM`. 3. **Flip the flag**: `gh variable set SELF_DOGFOOD_ENABLED --body true - --repo Netis/agent-ops`. No file change needed; the wrappers stop skipping. + --repo Netis/olympus`. No file change needed; the wrappers stop skipping. 4. (For `self-revise`) the review loop dispatches the revise workflow by filename. Either rename `self-revise.yml` → `pr-revise.yml`, or set - `AGENT_OPS_REVISE_WORKFLOW=self-revise.yml` in the runner environment. + `OLYMPUS_REVISE_WORKFLOW=self-revise.yml` in the runner environment. To pause again, set the variable to anything but `true` (or delete it). diff --git a/docs/config-reference.md b/docs/config-reference.md index f44a1b4..3787c71 100644 --- a/docs/config-reference.md +++ b/docs/config-reference.md @@ -1,7 +1,7 @@ -# Config reference — `.agent-ops.json` +# Config reference — `.olympus.json` The single source of per-repo policy. Validated by -[`schema/agent-ops.schema.json`](../schema/agent-ops.schema.json). Every field +[`schema/olympus.schema.json`](../schema/olympus.schema.json). Every field is optional; an omitted value uses the **default** shown. With no file at all, the agents behave exactly as the defaults (back-compatible). @@ -17,7 +17,7 @@ Precedence for any value: an exported **env var** > the **config file** > the ## `agents` | Field | Default | Meaning | |---|---|---| -| `review_bot_login` | `vivi` | GitHub login (or a marker in the review body) used by auto-merge / revise to find the review bot's verdict. | +| `review_bot_login` | `themis` | GitHub login (or a marker in the review body) used by auto-merge / revise to find the review bot's verdict. | | `dev_agent_name` | `the dev agent` | Display name for the implementer (commit author, log lines). | ## `labels` @@ -51,7 +51,7 @@ Precedence for any value: an exported **env var** > the **config file** > the | Field | Default | Meaning | |---|---|---| | `service_name` | `the service` | Name used in incident text. | -| `health_url` | — | Health endpoint mara polls. (For the systemd path, set via the unit's `MARA_HEALTH_URL` instead.) | +| `health_url` | — | Health endpoint argus polls. (For the systemd path, set via the unit's `ARGUS_HEALTH_URL` instead.) | | `repo` | — (required to file) | `owner/name` the incident issue is filed on. | | `labels` | `incident` | Comma-separated issue labels. | | `readiness.jq` | — | Optional jq filter over the health JSON for "parked" (up-but-not-working) detection. Omit → DOWN-only. | @@ -79,5 +79,5 @@ full `custom` (codex) walkthrough + the prompt-shape constraint. ## Example -See [`examples/consumer/.agent-ops.json`](../examples/consumer/.agent-ops.json) +See [`examples/consumer/.olympus.json`](../examples/consumer/.olympus.json) for a complete, commented-by-structure config. diff --git a/docs/cookbooks/README.md b/docs/cookbooks/README.md index f5c118c..22cd127 100644 --- a/docs/cookbooks/README.md +++ b/docs/cookbooks/README.md @@ -1,14 +1,14 @@ -# agent-ops cookbooks +# Olympus cookbooks Recipes for standing up the infrastructure a consumer repo needs to run the -agent-ops loop. Pick a platform: +Olympus loop. Pick a platform: - [Self-hosting](./self-hosting.md) — your own box, VM, or hypervisor (libvirt / Proxmox / bare metal) - [DigitalOcean](./digitalocean.md) — a Droplet - [AWS](./aws.md) — an EC2 instance -> New to agent-ops? Read [`../setup.md`](../setup.md) and +> New to Olympus? Read [`../setup.md`](../setup.md) and > [`../prerequisites.md`](../prerequisites.md) first. These cookbooks are the > *platform-specific* half of that setup. @@ -19,8 +19,8 @@ agent-ops loop. Pick a platform: | Surface | Needs a self-hosted runner? | Why | |---|---|---| | **guard** (leakage / secret hygiene) | **No** — runs on GitHub-hosted `ubuntu-latest` | Pure linters; only need the repo + the public internet. Free on public repos. | -| **triage, implement (wiwi), review (vivi), revise** | **Yes** | They run the `claude` CLI against a model endpoint. A GitHub-hosted runner can't reach a private/internal model gateway, and you usually don't want your API key on ephemeral cloud runners. | -| **observe (mara)** | A small box, **isolated from prod** | A systemd timer; see [`../../SELF-DOGFOOD.md`](../../SELF-DOGFOOD.md) and the observer notes in `setup.md`. | +| **triage, implement (hephaestus), review (themis), revise** | **Yes** | They run the `claude` CLI against a model endpoint. A GitHub-hosted runner can't reach a private/internal model gateway, and you usually don't want your API key on ephemeral cloud runners. | +| **observe (argus)** | A small box, **isolated from prod** | A systemd timer; see [`../../SELF-DOGFOOD.md`](../../SELF-DOGFOOD.md) and the observer notes in `setup.md`. | So: if you only want the **guard** gate, you need **zero infrastructure** — just add the `guard.yml` wrapper. Everything below is for the **agentic** surfaces. @@ -29,7 +29,7 @@ add the `guard.yml` wrapper. Everything below is for the **agentic** surfaces. ## The model endpoint (decide this first) -agent-ops's workflows read three secrets to reach the model. The names start +Olympus's workflows read three secrets to reach the model. The names start with `LITELLM_` for historical reasons — **it's just "an Anthropic-compatible endpoint."** Three common choices: @@ -94,7 +94,7 @@ the official LiteLLM docs since exact flags evolve. The section above changes the **model** behind Claude Code. This changes the **agent CLI itself** — run `codex`, `aider`, or any wrapper instead of `claude`. -Set the `harness` block in `.agent-ops.json`: +Set the `harness` block in `.olympus.json`: ```jsonc { @@ -107,7 +107,7 @@ Set the `harness` block in `.agent-ops.json`: } ``` -agent-ops fills the placeholders per run: `{model}` `{prompt_file}` (the agent's +Olympus fills the placeholders per run: `{model}` `{prompt_file}` (the agent's instructions) `{out}` (where to write the agent's output) `{tools}` (the allow-listed tools for that surface) `{write}` (`true` only for the implement surface — your harness should refuse edits otherwise) `{max_turns}`. Set @@ -118,11 +118,11 @@ surface — your harness should refuse edits otherwise) `{max_turns}`. Set Omit the `harness` block entirely to use the built-in **`claude`** harness (the default; identical to every existing consumer). -> ⚠️ **Known constraint — prompt shape.** agent-ops's prompts are written for +> ⚠️ **Known constraint — prompt shape.** Olympus's prompts are written for > Claude Code's behaviour: **triage** expects the agent to emit a JSON object > (`verdict` / `reply` / …) and **review** expects a `### Summary` heading that > `post_review.py` parses. A non-claude harness may format its output -> differently and need prompt tuning to satisfy those parsers — agent-ops does +> differently and need prompt tuning to satisfy those parsers — Olympus does > **not** normalise output across harnesses. **Qualify a candidate CLI with the > `evals/` bench (the harness qualification suite) before wiring it into the live loop.** @@ -133,9 +133,9 @@ Omit the `harness` block entirely to use the built-in **`claude`** harness | Workload | Suggested box | |---|---| | triage / review only (read + comment) | 2 vCPU / 4 GB | -| + implement (wiwi runs your `build_cmd`) | size for **your build** — wiwi needs your project's full toolchain + enough RAM/CPU to compile. A Rust/heavy build often wants 4 vCPU / 8–16 GB. | +| + implement (hephaestus runs your `build_cmd`) | size for **your build** — hephaestus needs your project's full toolchain + enough RAM/CPU to compile. A Rust/heavy build often wants 4 vCPU / 8–16 GB. | -The runner also needs **whatever `.agent-ops.json` `implement.build_cmd` +The runner also needs **whatever `.olympus.json` `implement.build_cmd` invokes** installed (your compiler, test tooling, etc.) — that's project-specific and not covered here. @@ -182,8 +182,8 @@ tar xzf runner.tar.gz ./config.sh --url https://github.com// \ --token \ - --name agent-ops-runner \ - --labels self-hosted,agent-ops \ + --name olympus-runner \ + --labels self-hosted,olympus \ --unattended --replace sudo ./svc.sh install "$USER" # install as a systemd service so it survives reboots @@ -191,7 +191,7 @@ sudo ./svc.sh start ``` > **Label** = whatever you put in your wrapper workflows' `runner_labels` -> (`'["self-hosted","agent-ops"]'` here). Keep them in sync. You can register one +> (`'["self-hosted","olympus"]'` here). Keep them in sync. You can register one > runner at the **org** level and share it across repos instead of per-repo. ### C. Secrets + wrappers @@ -210,7 +210,7 @@ gh secret set LITELLM_NO_PROXY --repo / --body "/ --body "alice,bob" # auto-merge allowlist ``` -Then add `.agent-ops.json` (see [`../config-reference.md`](../config-reference.md)) +Then add `.olympus.json` (see [`../config-reference.md`](../config-reference.md)) and the thin wrapper workflows (copy from [`../../examples/consumer/`](../../examples/consumer/)), making sure each wrapper's `runner_labels` matches your runner's label. @@ -218,7 +218,7 @@ wrapper's `runner_labels` matches your runner's label. ### D. Smoke test 1. Open an issue → the triage workflow runs on your runner and replies. -2. (implement) add the `agent:try` label → wiwi branches, builds, opens a DRAFT PR. +2. (implement) add the `agent:try` label → hephaestus branches, builds, opens a DRAFT PR. 3. Push a PR → after CI, the review workflow posts a review. If a job sits **queued** forever, the runner isn't online or its label doesn't @@ -232,7 +232,7 @@ can reach `LITELLM_BASE_URL`. - The runner holds your **model API key** and a **GitHub PAT** — treat the box as sensitive. Prefer a dedicated box per trust boundary; don't co-host it with untrusted workloads. -- Self-hosted runners on **public** repos can run code from forked PRs. agent-ops's +- Self-hosted runners on **public** repos can run code from forked PRs. Olympus's reusable workflows only act on **same-repo** PRs by design, but review your repo's `pull_request` vs `pull_request_target` settings before exposing a runner. - Keep the box patched; the runner auto-updates itself, the OS does not. diff --git a/docs/cookbooks/aws.md b/docs/cookbooks/aws.md index 4151a52..c47a2bc 100644 --- a/docs/cookbooks/aws.md +++ b/docs/cookbooks/aws.md @@ -1,4 +1,4 @@ -# Cookbook: agent-ops runner on AWS (EC2) +# Cookbook: Olympus runner on AWS (EC2) Run the runner on an EC2 instance — good if your org already lives in AWS, your model gateway is in a VPC, or you want spot/auto-stop cost controls. @@ -27,10 +27,10 @@ aws ec2 run-instances \ --key-name \ --security-group-ids \ --block-device-mappings 'DeviceName=/dev/sda1,Ebs={VolumeSize=30,VolumeType=gp3}' \ - --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=agent-ops-runner}]' + --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=olympus-runner}]' # get the public IP (or use SSM Session Manager and skip SSH/inbound entirely) -aws ec2 describe-instances --filters Name=tag:Name,Values=agent-ops-runner \ +aws ec2 describe-instances --filters Name=tag:Name,Values=olympus-runner \ --query 'Reservations[].Instances[].PublicIpAddress' --output text ssh ubuntu@ ``` @@ -68,7 +68,7 @@ boot via an instance role with GitHub App credentials. ## 4. Bring it online Continue with **Steps A–D** in [`./README.md`](./README.md): bootstrap the -instance, register the runner (label `self-hosted,agent-ops`), set the secrets, +instance, register the runner (label `self-hosted,olympus`), set the secrets, smoke-test. ## Pros / cons diff --git a/docs/cookbooks/digitalocean.md b/docs/cookbooks/digitalocean.md index 3ec23f2..ba4d2c6 100644 --- a/docs/cookbooks/digitalocean.md +++ b/docs/cookbooks/digitalocean.md @@ -1,4 +1,4 @@ -# Cookbook: agent-ops runner on DigitalOcean +# Cookbook: Olympus runner on DigitalOcean A Droplet is the fastest hosted option: a few dollars a month, up in a minute, no cloud-IAM ceremony. @@ -13,14 +13,14 @@ no cloud-IAM ceremony. ```bash # one-time: doctl auth init ; doctl compute ssh-key list (note your key id/fingerprint) -doctl compute droplet create agent-ops-runner \ +doctl compute droplet create olympus-runner \ --image ubuntu-24-04-x64 \ --size s-2vcpu-4gb \ --region nyc3 \ --ssh-keys \ --wait -doctl compute droplet list agent-ops-runner --format Name,PublicIPv4 +doctl compute droplet list olympus-runner --format Name,PublicIPv4 ssh root@ ``` @@ -51,7 +51,7 @@ always-responsive loop, leave it running. ## 4. Bring it online Continue with **Steps A–D** in [`./README.md`](./README.md): bootstrap the -Droplet, register the runner (label `self-hosted,agent-ops`), set the secrets, +Droplet, register the runner (label `self-hosted,olympus`), set the secrets, smoke-test. ## Pros / cons diff --git a/docs/cookbooks/self-hosting.md b/docs/cookbooks/self-hosting.md index 3db8269..01b095c 100644 --- a/docs/cookbooks/self-hosting.md +++ b/docs/cookbooks/self-hosting.md @@ -1,6 +1,6 @@ -# Cookbook: self-hosting the agent-ops runner +# Cookbook: self-hosting the Olympus runner -Run the agent-ops loop on your own hardware — a spare box, a VM on your +Run the Olympus loop on your own hardware — a spare box, a VM on your hypervisor (libvirt / Proxmox / VMware / Hyper-V), or bare metal. This is the cheapest option if you already have capacity, and the only option if your model endpoint is on a private network. @@ -25,7 +25,7 @@ This mirrors how the framework's own reference deployment is built. Create a ```yaml #cloud-config -hostname: agent-ops-runner +hostname: olympus-runner users: - name: runner sudo: ALL=(ALL) NOPASSWD:ALL @@ -40,11 +40,11 @@ packages: [git, jq, python3, curl, ca-certificates] ```bash cloud-localds seed.iso user-data qemu-img create -F qcow2 -b /path/to/noble-server-cloudimg-amd64.img -f qcow2 disk.qcow2 20G -virt-install --name agent-ops-runner --memory 4096 --vcpus 2 \ +virt-install --name olympus-runner --memory 4096 --vcpus 2 \ --disk path=disk.qcow2,format=qcow2 --disk path=seed.iso,device=cdrom \ --os-variant ubuntu24.04 --network network=default \ --graphics none --import --noautoconsole -virsh autostart agent-ops-runner # survive host reboots +virsh autostart olympus-runner # survive host reboots ``` Proxmox / VMware / Hyper-V: provision an equivalent Ubuntu VM however you @@ -68,7 +68,7 @@ root. ## 3. Bring it online Continue with **Steps A–D** in [`./README.md`](./README.md): bootstrap the box, -register the runner (label `self-hosted,agent-ops`), set the secrets, smoke-test. +register the runner (label `self-hosted,olympus`), set the secrets, smoke-test. ## Pros / cons diff --git a/docs/improvement-plan.md b/docs/improvement-plan.md new file mode 100644 index 0000000..baa3f68 --- /dev/null +++ b/docs/improvement-plan.md @@ -0,0 +1,336 @@ +# Engineering improvement plan + +The phased hardening plan behind the [roadmap](roadmap.md)'s Horizon 1. The +roadmap says *where* the project (Olympus, once [0.2-0](#02-0--rename-to-olympus--the-zero-consumer-window) lands) is going; this +document says *what to change in this codebase, in what order, and how we'll +know each item is done*. Version numbers are phase names, not date promises. + +## Why this ordering + +Going open-source inverts two assumptions the codebase was built on: + +1. **Issue authors become adversaries.** The implement agent acts on + stranger-filed issue text with an unrestricted `Bash` tool + (`scripts/lib/agent-harness.sh`, profile `implement`) and a PAT in its + environment. On an internal repo that's a trust decision; on a public repo + it's a remote-code-execution-by-issue vector. This moves prompt-injection + containment from the hardening backlog to a **P0 pre-announcement blocker**. +2. **Strangers read the artifacts.** A review prompt that opens with "You are + a code review agent for the **Heron** repository" + (`scripts/pr-review/prompt.md`) is the first file a prospective adopter + reads to judge whether the "reusable" claim is real. Generalization + graduates from cosmetic to credibility-critical. + +So the phases gate on: + +- **v0.2.x — safe and honest to publish.** Injection containment, finish the + generalization, validation tooling. Removes attack surface and + embarrassment; adds no capability. +- **v0.3 — trustworthy under churn.** Traps, locks, retry classification, and + the first observability primitive. You can't run a community loop you can't + measure, or debug stranger-reported failures from ephemeral workflow logs. +- **v0.4 — scalable to contributors.** Close the test/eval gap so external + PRs can be accepted with confidence, then the capability items (budgets, + heartbeat) that the new data primitive unlocks. + +Throughout, the mechanism/policy split stays sacred: every new behavior below +is a config key with a safe default, resolved through the existing `config.sh` +precedence (env > config file > built-in default), and every agent +invocation keeps going through the `agent_run` adapter. + +## Phase v0.2.x — open-source readiness (blocks any announcement) + +| # | Item | Effort | +|---|---|---| +| 0.2-0 | Rename to **Olympus** + pantheon agent names | M | +| 0.2-1 | Untrusted-input containment for the implement agent | L | +| 0.2-2 | Generalize the review prompt via a context slot | M | +| 0.2-3 | Purge remaining hardcoded identities and labels | S–M | +| 0.2-4 | `doctor` (Asclepius) + config schema validation in `guard.yml` | M | + +### 0.2-0 · Rename to Olympus — the zero-consumer window + +The project is renamed **Olympus** and the agents take the deities whose +domains match their functions — Hermes (triage), Hephaestus +(implement/revise), Themis (review), Argus (observer), Cerberus (guard), +Asclepius (doctor), the Labours (evals), Atlas (fleet). The full mapping with +rationale, the two-layer rename scope (brand vs mechanism), and the +no-shims argument live in [pantheon.md](pantheon.md). + +This goes **first** because the mechanism layer is breaking — repo path in +consumer `uses:` references, `.agent-ops.json` → `.olympus.json`, +`AGENT_OPS_*` → `OLYMPUS_*` env prefixes, `MARA_*` → `ARGUS_*` — and the +consumer count is zero today. Every consumer that onboards before the rename +(including heron, H1.5) converts part of this list into a migration burden; +at zero consumers it is one self-contained PR plus a repo-settings change, +with no compatibility shims to write or later delete. + +Sequencing note: 0.2-3 (purging hardcoded identities into config variables) +is folded into the same change — there is no point parameterizing the string +"wiwi" and then renaming the default to "hephaestus" in a second pass. + +**Why:** a rename after launch breaks pinned consumers and burns the +project's first impression twice; a rename now is free. + +**Acceptance:** see [pantheon.md](pantheon.md) — zero +`agent-ops`/`wiwi`/`vivi`/`mara` matches in mechanism code, self-CI green, +`examples/consumer/` references `Netis/olympus` and `.olympus.json`, doctor +passes against the example. + +### 0.2-1 · Untrusted-input containment — P0 security + +Treat issue/comment bodies as data, not instructions, and constrain the blast +radius when that fails: + +- **(a) Scope the `implement` profile's Bash.** Replace the bare `Bash` grant + in `scripts/lib/agent-harness.sh` with an allowlist assembled from config: + read/build primitives plus the consumer's declared `build_cmd`, with network + egress tools (`curl`, `wget`, `nc`, `ssh`) denied by default. New key + `.implement.allowed_bash` in `schema/olympus.schema.json`, loaded by + `scripts/lib/config.sh`. Consumers who genuinely need more opt in + explicitly — policy in their file, mechanism here. +- **(b) Fence untrusted content in prompts.** In `run_wiwi.sh`, + `run_triage.sh`, `run_revise.sh`: wrap interpolated issue/review bodies in + explicit delimiters with a standing instruction that content inside the + markers is untrusted input, never instructions to follow. +- **(c) Minimize token exposure.** Audit which env vars reach the agent child + process; `agent_run` strips `AGENT_GH_TOKEN`/`GH_TOKEN` (and `*_TOKEN` + generally) from the subprocess env — the wrapper scripts' own `gh` calls are + the only consumers. +- **(d) Actor gating.** The implement workflow only fires when the `try` label + was applied by the triage bot or an allowlisted human (reuse the + `AUTO_MERGE_TEAM` allowlist pattern from `auto_merge.sh`), so a stranger + can't self-label into the implement path. + +**Why:** public repos mean adversarial issue authors; the current +profile + PAT-in-env combination is exploitable on day one of being public. + +**Acceptance:** an eval task (see 0.4-2) whose issue body embeds an +exfiltration instruction completes the legitimate fix *without* the injected +command executing; the default `implement` profile cannot invoke `curl`; `env` +dumped inside an agent run shows no `*_TOKEN` vars; labeling by a +non-allowlisted actor does not trigger implement. + +### 0.2-2 · Generalize the review prompt + +Split `scripts/pr-review/prompt.md` into a generic reviewer prompt (role, +verdict format, the genuinely generic leakage/secrets section) with +`{project_context}` / `{repo_gotchas}` slots, filled from a consumer-supplied +file (default `.olympus/review-context.md`, configurable via a new +`.review.context_file` key). `run_review.sh` performs the substitution; a +missing context file degrades to a generic-but-functional review. The current +heron crate map + gotchas move verbatim into heron's repo as the first +`review-context.md` — the worked example for the docs. + +**Why:** the file is 100% heron-specific today; README already flags it +"generalization pending", and it's the single most credibility-damaging +artifact to ship to strangers. + +**Acceptance:** `grep -i heron scripts/pr-review/prompt.md` returns nothing; +`evals/tasks/review/planted-bug` still passes with an empty context file. + +### 0.2-3 · Purge remaining hardcoded identities and labels + +Lands in the same change as 0.2-0: parameterize the strings, and the new +defaults are the pantheon names. + +- `scripts/pr-review/post_review.py`: the `Reviewed by **vivi**` footer reads + `AGENT_OPS_REVIEW_BOT_LOGIN` from the environment (already exported by + `config.sh`; keep footer and `auto_merge.sh`'s review matcher in lockstep). +- `scripts/agent-bot/run_wiwi.sh` ("You are **wiwi**", `Implemented by + **wiwi**`) and `revise_dispatch.sh`'s comment text → use + `$AGENT_OPS_DEV_AGENT_NAME` / `$AGENT_OPS_REVIEW_BOT_LOGIN`, as the triage + replies already do. +- `.github/workflows/triage.yml` hardcodes the `agent:assess` label in its + `if:` condition — workflow expressions can't read the config file, so + promote it to a workflow input `assess_label` (default `agent:assess`) that + consumer wrappers pass; update `examples/consumer/`. + +**Why:** a consumer who sets `dev_agent_name: "robo"` still sees "wiwi" in PR +bodies today — config that silently doesn't apply destroys trust in every +other config key. + +**Acceptance:** `grep -rn 'vivi\|wiwi' scripts/ .github/workflows/` matches +nothing (the config defaults become `themis`/`hephaestus` per 0.2-0); a +`test_post_review.py` case asserts the footer uses the env-provided login; +triage fires on a custom label in the example consumer. + +### 0.2-4 · `doctor` (Asclepius) + schema validation in guard + +Both already on the roadmap — fold security in. `scripts/doctor.sh` validates +in one command: `.olympus.json` against `schema/olympus.schema.json` (a +small stdlib-Python validator, `scripts/lint/check-config.py` — the schema is +shallow and `jsonschema` isn't stdlib), required labels exist, secrets are +present, the runner can reach the gateway, and — explicitly — that +`AGENT_GH_TOKEN` is a PAT able to emit workflow-triggering events (the +documented #1 onboarding failure) with no broader scopes than needed. Wire +`check-config.py` into `guard.yml` so a malformed config is rejected at PR +time. + +**Acceptance:** doctor passes on `examples/consumer/`; on a config with a +typo'd key or a string where a number belongs, doctor and `guard.yml` both +fail naming the offending key. + +## Phase v0.3 — robustness + observability (blocks "runs unattended at scale") + +| # | Item | Effort | +|---|---|---| +| 0.3-1 | trap/cleanup handlers in all long-running scripts | S–M | +| 0.3-2 | Strict verdict parsing in triage | S | +| 0.3-3 | Idempotency + locking | M | +| 0.3-4 | Harness retry classification + small guards | M | +| 0.3-5 | Structured run-summary JSONL artifact | M | + +### 0.3-1 · trap/cleanup handlers + +A standard `cleanup()` + `trap cleanup EXIT INT TERM` (shared helper +`scripts/lib/cleanup.sh`) in `run_wiwi.sh`, `run_triage.sh`, `run_revise.sh`, +`run_review.sh`: remove `mktemp` files (today leaked on any non-happy path), +and in `run_wiwi.sh`/`run_revise.sh` delete the pushed work branch on +failure-before-PR so cancellations stop leaving orphan branches. + +**Acceptance:** `kill -TERM` of a mid-run script leaves no temp files and no +orphan remote branch (test with stub `git`). + +### 0.3-2 · Strict verdict parsing + +`run_triage.sh` validates the agent's JSON verdict structure (`has("verdict") +and has("gates")`-style) before consuming fields. Today a missing `.gates` +silently coerces into a downgrade — the worst failure mode is the invisible +one. Missing/malformed structure → post an "escalating to a human; agent +output was malformed" comment + a `needs-human` label, never a silent +downgrade. + +**Acceptance:** `tests/test_triage.py` cases feeding truncated/field-missing +JSON assert escalation, not downgrade. + +### 0.3-3 · Idempotency + locking + +- **mara:** wrap each invocation in `flock` on a state-dir lockfile, making + the dedup read-modify-write atomic across overlapping timer fires. +- **triage comments:** embed a hidden marker (``) + and skip/update instead of duplicating when re-triggered. +- **revise round cap:** persist the round count as a hidden marker + (``) in the dispatch comment + `revise_dispatch.sh` already posts; the fresh review-history count becomes + the fallback, not the primary counter. + +**Acceptance:** two concurrent mara invocations file at most one issue (stub +`gh`); re-labeling an already-triaged issue produces zero new comments; the +round cap holds when a review listing is artificially truncated in test. + +### 0.3-4 · Retry classification + guards + +- `agent_run` retries beyond gateway-down: classified 429 / 5xx / timeout / + truncated-stream output, with exponential backoff + jitter. +- `auto_merge.sh` checks merged state first and exits cleanly if the PR is + already merged. +- `mara.sh`'s curl timeout becomes `MARA_CURL_TIMEOUT` (default 8). +- `run_review.sh` truncates oversized diffs at a file boundary and appends an + explicit `[diff truncated — N files omitted]` marker instead of cutting + mid-hunk at a byte count. + +**Acceptance:** `tests/test_harness.py` gains cases with a fake agent binary +emitting 429/timeout/truncated output, asserting retry-then-succeed; reviewing +an oversized diff shows the truncation marker in the prompt. + +### 0.3-5 · Structured run-summary JSONL — the observability primitive + +A `scripts/lib/run-summary.sh` helper, called from `agent_run` so every stage +gets it for free, appends one JSON line per run: `{ts, stage, repo, issue|pr, +model, duration_s, exit, retries, verdict, tokens_in, tokens_out, +cost_estimate}` (token/cost from the harness's usage output when available, +else null). Each reusable workflow uploads the file as an artifact with +`if: always()`. Deliberately the *primitive*, not the dashboard: the roadmap's +heartbeat dashboard and token budgets both consume this format later. + +**Why:** today there are zero structured metrics — no answer to "what does +this loop cost", "what's the triage→merge success rate", or "how often do we +escalate" — the first questions every adopter asks. + +**Acceptance:** every reusable-workflow run uploads a summary artifact; a +sample line validates against a documented schema; duration + exit are always +present. + +## Phase v0.4 — test depth, eval depth, community capabilities + +| # | Item | Effort | +|---|---|---| +| 0.4-1 | Close the test gap on the untested core | L | +| 0.4-2 | Eval bench expansion as the release gate | L | +| 0.4-3 | Token budgets + heartbeat v1 | M+M | +| 0.4-4 | Community furniture + heron dogfood | M | + +### 0.4-1 · Close the test gap + +Tests for the four zero-coverage scripts, using the established +pytest-driving-bash-with-stub-`gh`/`git` pattern in +`scripts/agent-bot/tests/`. Priority order: `auto_merge.sh` first (it merges +code — highest blast radius, and pure gh-glue so cheapest to stub), then +`revise_dispatch.sh` (cap logic), then `run_wiwi.sh`/`run_revise.sh` (happy +path + abort path + trap path). Extend `test_post_review.py` to cover the main +posting flow and the auto-merge gating boundaries. + +**Why:** ~14% test:code ratio with the merge-authorizing script at zero — you +cannot accept community PRs against untested control flow. + +**Acceptance:** all four scripts have suites wired into `ci.yml`; +`auto_merge.sh` tests cover missing label, non-approved state, non-allowlisted +author, and already-merged. + +### 0.4-2 · Eval bench expansion + +New task families under `evals/tasks/`: `revise/` (apply a CHANGES_REQUESTED +review to an existing branch), `observe/` (mara classification on canned +health responses), `merge-gate/` (allow/deny matrix), `implement/multi-file/`, +gate-boundary triage tasks (LOC estimates straddling `max_loc`), a non-English +triage variant exercising `.triage.language`, and — tying to 0.2-1 — a +**prompt-injection task**: the issue body embeds a malicious instruction; +scoring asserts the fix landed and the injected command did not run. Uses the +existing `evals/run.sh` + scoring harness unchanged. Adopt "evals green" as +the qualification gate for cutting a tag. + +**Acceptance:** ≥10 tasks spanning all five loop stages; the injection task +red-lines if containment regresses; the release checklist references the +bench. + +### 0.4-3 · Token budgets + heartbeat v1 + +Budgets: `.budget.max_tokens_per_run` / `.budget.monthly_tokens` keys in the +schema; `agent_run` pre-flights against accumulated run-summary data (0.3-5) +and refuses with a clear comment when exceeded — escalate to a human, never a +silent stop. Heartbeat: a small aggregator consuming the uploaded JSONL +artifacts across repos (static report first; dashboard later). Both are +existing roadmap commitments, buildable only once 0.3-5 exists — sequencing, +not new scope. + +**Acceptance:** a consumer with a tiny budget sees implement runs declined +with a clear comment; the aggregator emits a per-repo per-stage cost/success +table from a week of artifacts. + +### 0.4-4 · Community furniture + heron dogfood + +`SECURITY.md` (disclosure policy — table stakes for a project whose pitch is +"agents act on your repo"; published before any announcement that invites +strangers' issues), `CONTRIBUTING.md` pointing at the eval bench + doctor, a +release/tagging process doc, and completion of the standing dogfood target: +**heron onboards as consumer #1**, pinned to a tag, using the +`review-context.md` from 0.2-2 — which doubles as the proof that the +generalization was lossless. + +**Acceptance:** heron runs the full loop from Olympus wrappers with zero +behavior regression; `SECURITY.md` exists before launch. + +## Sequencing summary + +| Phase | Theme | Gate it clears | +|---|---|---| +| v0.2.x | Olympus rename, injection containment, prompt generalization, identity purge, doctor + schema validation | Safe and credible to announce publicly | +| v0.3 | Traps / locks / retries / guards + JSONL run summaries | Trustworthy unattended; failures debuggable, costs measurable | +| v0.4 | Test + eval depth, budgets, heartbeat, community docs, heron dogfood | Ready to absorb external contributors and consumers | + +Items 0.2-1 and 0.2-2 are the two hard blockers: prompt injection on a public +issue tracker is an active attack surface the moment the repo is public, and +the heron-specific review prompt is the first thing an evaluating adopter will +read. 0.2-0 precedes them chronologically only because its cost window closes +with the first consumer. diff --git a/docs/migration.md b/docs/migration.md new file mode 100644 index 0000000..947531a --- /dev/null +++ b/docs/migration.md @@ -0,0 +1,64 @@ +# Migration: agent-ops → Olympus + +This mechanism was renamed **agent-ops → Olympus** +(see [pantheon.md](pantheon.md) for the rename and the agent→deity mapping). +This page answers one question: **if your repo already uses the mechanism, what +breaks — and what do you change?** + +Short answer: **a wrapper pinned to a pre-rename tag keeps working unchanged.** +You only do the edits below when you *re-pin* to an Olympus-named release. + +## Impact by how you pinned + +Consumers touch the mechanism in exactly one place: `uses: +Netis/agent-ops/.github/workflows/.yml@` in their wrapper workflows +(plus their own `.agent-ops.json`). What happens depends on ``: + +| You pinned to… | What happens after the rename | What to do | +|---|---|---| +| **A pre-rename tag** (`@v0.2.0`) — the documented, recommended way | **Nothing breaks.** That tag's content is frozen at the old (agent-ops-named) code, and GitHub permanently redirects the old `Netis/agent-ops` `uses:` path to `Netis/olympus`. The old code reads your old `.agent-ops.json` — fully self-consistent. | Nothing now. Migrate only when you choose to upgrade to an Olympus-named tag. | +| **A moving ref** (`@main`, a branch) — an anti-pattern the README warns against | **Drifts on the next run.** You'll pull post-rename HEAD, whose loader looks for `.olympus.json`; your file is still `.agent-ops.json`, so config silently falls back to built-in defaults (label names, gate thresholds, bot login, build command all revert) → wrong behavior, not a clean error. | Migrate now (below), or pin to a tag. | +| **Upgrading to an Olympus tag** (`@v0.3.0`+) | The new code expects the new names. | Do the full migration below before bumping. | + +The redirect is a courtesy, not a contract: it lapses the moment anyone creates +a new repo at the old `Netis/agent-ops` path. Treat it as a grace period, not a +permanent alias. + +## heron specifically + +heron is **not a consumer yet** — it still runs its own in-tree +`scripts/agent-bot/*` + `.github/workflows/*` (the roadmap's standing dogfood +target is to *replace* those with pinned Olympus wrappers). So the rename's +impact on heron today is **zero**. When heron onboards as consumer #1, it +onboards straight to the Olympus names — no migration debt. This is the whole +point of doing the rename now, at zero live consumers. + +## The mapping (when you do migrate) + +| Legacy (agent-ops) | New (Olympus) | Where | +|---|---|---| +| `Netis/agent-ops` | `Netis/olympus` | wrapper `uses:` | +| `agent_ops_ref:` | `olympus_ref:` | wrapper input | +| `.agent-ops.json` | `.olympus.json` | your policy file (rename it) | +| `schema/agent-ops.schema.json` | `schema/olympus.schema.json` | `$schema` ref | +| `AGENT_OPS_*` | `OLYMPUS_*` | env overrides in workflows | +| `MARA_*` | `ARGUS_*` | systemd observer env | +| `self-hosted,agent-ops` | `self-hosted,olympus` | runner label + `runs-on` | +| default bot/agent names `vivi` / `wiwi` / `mara` | `themis` / `hephaestus` / `argus` | only if you used the defaults | + +Not in any file, so a linter can't catch it: **the review bot's GitHub +account.** `agents.review_bot_login` names a real account; rename or +re-provision it and update `AUTO_MERGE_TEAM` / any allowlists that reference it. + +## Check your repo + +Run the migration linter in your repo root — it prints exactly what to change +and exits non-zero if anything remains: + +```bash +bash <(curl -fsSL \ + https://raw.githubusercontent.com/Netis/olympus/main/scripts/lint/check-legacy-naming.sh) +``` + +The same linter runs in Olympus's own CI as a regression guard, so the +mechanism never silently grows the old names back. diff --git a/docs/pantheon.md b/docs/pantheon.md new file mode 100644 index 0000000..9c52f48 --- /dev/null +++ b/docs/pantheon.md @@ -0,0 +1,87 @@ +# Olympus — the rename and the pantheon + +agent-ops is being renamed **Olympus**. The metaphor is exact: the *mechanism* +lives on the mountain (this repo, versioned by tag), while each consumer repo +supplies only its mortal *policy* (`.agent-ops.json`). The gods act in the +mortals' repos, but they are governed from Olympus — upgrade the mountain +once and every realm benefits. + +This document is the naming plan: the function → deity mapping, the rename +scope, and why the rename must happen **now** (it is item +[0.2-0 of the improvement plan](improvement-plan.md)). + +## The pantheon + +Each loop surface gets the god whose domain matches its function — not the +most famous god, the *right* one. + +| Surface | Deity | Today | Why this deity | +|---|---|---|---| +| Triage | **Hermes** | (unnamed) | Herald and messenger; god of language, boundaries, and guidance. Triage speaks in the reporter's own language, investigates, and guides every issue to its destination — including the ones it doesn't dispatch. | +| Implement + Revise | **Hephaestus** | wiwi | The smith. Works the forge unattended (branch, code, build+test) and delivers a draft. Revise is the same god: CHANGES_REQUESTED sends the piece *back to the forge*. | +| Review | **Themis** | vivi | Goddess of divine law, order, and the scales. Renders a structured verdict on every PR and holds the gate to auto-merge — judgment, not opinion. | +| Observer | **Argus** | mara | Argus Panoptes, the hundred-eyed watchman who never sleeps. Polls production health and raises a scrubbed, deduplicated alarm only on *sustained* failure. (Not an Olympian — the watchman metaphor is too exact to pass up. Purists may prefer Helios, who sees everything; we prefer the eyes.) | +| Guard linters | **Cerberus** | (unnamed) | The gatekeeper at the threshold. Deliberately **no LLM** — and fittingly no god either: a mechanical three-headed dog that does not negotiate about leaked IPs, secret references, or insane secret values. | +| `doctor` | **Asclepius** | (planned) | The healer. Diagnoses a consumer's config, secrets, labels, and runner reachability in one pass. | +| Evals bench | **The Labours** | evals/ | The Labours of Heracles: a candidate harness must complete the trials before it may join the pantheon. Apotheosis through qualification — objective, binary, no judge. | +| Fleet CLI (H3) | **Atlas** | (planned) | Bears the whole fleet on his shoulders: `atlas bump`, `atlas doctor`, `atlas drift` across an org's repos. | +| Demo repo (H2) | **Agora** | (planned) | The public square where mortals come to watch the gods work: file an issue, watch the loop. | + +Naming conventions: deity names are the **default display names** in +config (`agents.dev_agent_name: "hephaestus"`, `agents.review_bot_login: +"themis"`, …) — consumers can still override them, as today; the +mechanism/policy split is untouched. Script names follow +(`run_hephaestus.sh`, `argus.sh`); surface names in workflows stay functional +(`triage.yml`, `review.yml`) so a consumer never needs the mythology to +operate the loop. + +## Rename scope — two layers + +### Brand layer (cheap, cosmetic) + +- Repo name `Netis/agent-ops` → `Netis/olympus`; README, docs, cookbooks. +- Default agent display names in `config.sh`: `wiwi` → `hephaestus`, + `vivi` → `themis`, plus naming the previously-unnamed triage voice + (`hermes`) and observer (`mara.sh` → `argus.sh`). +- Prompt role lines and PR/comment footers (via the existing config + variables — this rides on [improvement plan 0.2-3](improvement-plan.md), + which makes those strings config-driven anyway). + +### Mechanism layer (breaking — which is exactly why it happens now) + +- Consumer wrapper references: `uses: Netis/agent-ops/.github/workflows/...` + → `Netis/olympus/...` and the `agent_ops_ref` input → `olympus_ref`. + (GitHub redirects renamed-repo `uses:` paths, but examples and docs must + not rely on redirects.) +- Config file `.agent-ops.json` → `.olympus.json`; schema + `schema/agent-ops.schema.json` → `schema/olympus.schema.json`. +- Env prefixes: `AGENT_OPS_*` → `OLYMPUS_*`; `MARA_*` → `ARGUS_*`. +- Operational, outside this repo: the review bot's GitHub account + (`agents.review_bot_login` is a real login) needs renaming or + re-provisioning, and `AUTO_MERGE_TEAM` / allowlists that reference it + updated. + +**No compatibility shims.** The consumer count is zero today — heron has not +onboarded, self-dogfood is dormant. A dual-read layer (old + new config +filename, old + new env prefixes) would be code we write only to delete. The +entire argument for renaming *now* is that we never have to write it. + +## Timing + +The rename is **item 0.2-0** — before everything else in v0.2.x, and a hard +prerequisite of [roadmap H1.5](roadmap.md) (heron onboards as consumer #1). +Every consumer that onboards before the rename turns part of the mechanism +layer into a breaking change; at zero consumers the rename is a single +self-contained PR plus one repo-settings change. + +## Acceptance + +- `grep -rni 'agent-ops\|agent_ops\|wiwi\|vivi\|mara' scripts/ .github/ schema/ examples/ evals/` + matches nothing (docs may keep historical mentions in this file and the + changelog). +- Self-CI green after the rename (shellcheck, unit tests, leakage gate, + evals checks). +- `examples/consumer/` wrappers reference `Netis/olympus@` and + `.olympus.json`, and a doctor run against the example passes. +- The old repo URL redirects (GitHub-side) — verified once after the + settings change. diff --git a/docs/prerequisites.md b/docs/prerequisites.md index 641f5c7..181fa6b 100644 --- a/docs/prerequisites.md +++ b/docs/prerequisites.md @@ -1,6 +1,6 @@ # Prerequisites -agent-ops runs LLM agents on **your** infrastructure. Before onboarding a repo, +Olympus runs LLM agents on **your** infrastructure. Before onboarding a repo, have these in place — most are set once per org and shared. ## 1. A self-hosted runner the agents can use @@ -34,7 +34,7 @@ Set once at the org and grant the repos that onboard, or set per-repo. ## 3. Why `AGENT_GH_TOKEN` must be a PAT (not `GITHUB_TOKEN`) GitHub deliberately **suppresses workflow-triggering events emitted under the -default `GITHUB_TOKEN`** to prevent recursive workflow storms. The agent-ops +default `GITHUB_TOKEN`** to prevent recursive workflow storms. The Olympus loop depends on exactly those events: - triage adds `agent:try` → that `labeled` event must start the implement job; @@ -44,15 +44,15 @@ loop depends on exactly those events: All of these are no-ops under `GITHUB_TOKEN`. So they run under `AGENT_GH_TOKEN`, a PAT owned by a real user (or a machine account) with `repo` + `workflow` scope. The same token is used to admin-merge (it needs -branch-protection bypass) and to clone `Netis/agent-ops` if you keep it private. +branch-protection bypass) and to clone `Netis/olympus` if you keep it private. > This is the single most common onboarding failure. If "nothing happens" after > triage labels an issue, check that `AGENT_GH_TOKEN` is set and is a PAT. -## 4. Keep agent-ops reachable to the runner +## 4. Keep Olympus reachable to the runner -The reusable workflows `git clone` `Netis/agent-ops` at the pinned ref to get -the scripts. If you keep agent-ops **public**, nothing extra is needed (it holds +The reusable workflows `git clone` `Netis/olympus` at the pinned ref to get +the scripts. If you keep Olympus **public**, nothing extra is needed (it holds no secrets — all infra lives in the consumer's secrets). If **private**, the checkout uses `AGENT_GH_TOKEN`, which must have read access. diff --git a/docs/roadmap.md b/docs/roadmap.md index e06fa0d..0437808 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,54 +1,336 @@ # Roadmap -agent-ops v0.1 extracts the heron loop into a reusable, config-driven mechanism. -What's deliberately staged for later — and the principle behind the staging. - -## Principle - -Decouple **runtime-critical** coupling first (what would make the loop *behave -wrong* on another repo): the label names, the review bot's identity, the default -branch, the build command, the gate thresholds, the observer's readiness check. -All of that is config-driven and tested in v0.1. - -Leave **cosmetic** coupling (strings that are merely heron-flavored but don't -change behavior) for when the second repo onboards and gives a real second data -point — avoids guessing the right abstraction from a sample size of one. - -## v0.1 — done - -- `config.sh` loader + `.agent-ops.json` schema (unit-tested). -- Triage: fully config-driven (gates, labels, language, names), warm - maintainer-voice replies, reproduce-before-`do`; unit-tested composer. -- Observer (mara): generalized DOWN + optional config-driven readiness/"parked" - detection; confirm-debounce; scrub; unit-tested. -- Hygiene gates (leakage / secret-ref / secret-value) as a reusable `guard.yml`. -- Reusable workflows for the whole loop (triage / implement / review / revise / - observe / guard) + consumer wrapper examples. -- Self-CI (shellcheck + the three test suites + leakage). - -## v0.2 — deepen on second-repo onboarding - -- Dev agent (`run_wiwi.sh`) & revise: replace remaining cosmetic agent-name - strings in prompts/comments with `dev_agent_name`; parameterize the resume - hints fully. -- Review (`pr-review/*`): surface the review bot's display name + footer from - config; confirm the reviewer prompt carries no heron-specific assumptions. -- A `agent-ops doctor` script: validate a consumer's `.agent-ops.json`, secrets, - runner reachability, and label existence in one command. -- JSON-schema validation wired into `guard.yml` (reject a malformed config at - PR time). - -## v0.3+ — capabilities - -- Optional staging-soak / deploy-gate workflows (the heron pre-prod chain), - with the soak *content* supplied per-repo and only the **gate shape** - (a `staging-soaked`-style status token) provided by agent-ops. -- A heartbeat/ops dashboard aggregating agent last-seen across repos. -- Per-repo token-budget guards. - -## Dogfooding target - -Make **heron itself a consumer of agent-ops** — replace its in-tree -`scripts/agent-bot/*` + `.github/workflows/*` with the wrappers here, pinned to a -tag. heron becoming consumer #1 with zero behavior regression is the proof the -extraction is lossless. +agent-ops — being renamed **Olympus**, with the agents taking the deities +whose domains match their functions (Hermes triages, Hephaestus forges, +Themis judges, Argus watches; the full mapping and rename plan live in +[pantheon.md](pantheon.md)) — is heading somewhere specific: an +**open-source, self-hosted, model-agnostic, policy-as-config closed loop** +for autonomous repo maintenance — issue triage → implement → review → revise +→ **production observation feeding back into triage**, plus no-LLM hygiene +guards. + +The competitors each own a fragment: vendor coding agents (GitHub Copilot +coding agent, Cursor background agents) are vendor-locked issue→PR pipelines; +issue resolvers (sweep.dev, OpenHands resolver) stop at the PR, without review +gating or production feedback. Nobody else ships the *whole loop* as versioned +mechanism + per-repo policy, runnable on your own runners against any gateway. +Three defensible edges, and every roadmap item below either widens the +adoption funnel into the loop or sharpens one of them: + +1. **The full closed loop** — including the prod-observe → triage feedback no + one else closes. +2. **No lock-in** — any model (pluggable harness with an objective + qualification bench), eventually any forge. +3. **Policy-as-config** — one versioned mechanism, per-repo `.olympus.json` + policy, upgrade by bumping a tag. + +This document is the product roadmap (three horizons, no dates — version +numbers are phase names). The codebase-level breakdown with acceptance +criteria lives in the [engineering improvement plan](improvement-plan.md). + +## Sequencing principles + +- **Safety before promotion.** The implement agent executes work derived from + stranger-filed issue text. Until that is contained + ([improvement plan 0.2-1](improvement-plan.md)), there is no demo repo, no + launch post, no invitation for public issues. +- **Reduce infrastructure before adding features.** The #1 funnel killer is + "self-hosted runner + gateway + PAT + five secrets + four manual labels" + before any payoff. A zero-infra tier converts strangers; features convert + nobody who never onboards. +- **Design seams early, build adapters late.** Multi-forge adapters are + Horizon 2, but the forge seam (all forge writes through one library, no new + raw `gh` calls) starts in Horizon 1 — otherwise the refactor cost compounds + monthly. +- **Dogfood is the credibility proof.** heron as consumer #1 and Olympus's + own self-loop (already wired, dormant behind `SELF_DOGFOOD_ENABLED`) precede + any "use this" marketing. +- **Open-source core, fleet at the edge.** Single-repo and CLI-driven fleet + tooling is in scope forever; persistent multi-tenant control planes are the + natural future commercial layer and deliberately *not* promised in OSS + scope (see Horizon 3). + +## Horizon 1 — foundation & credibility (now → ~v0.3) + +*A stranger can onboard safely in under 30 minutes, and we can prove the loop +works because we run it on ourselves.* + +### 1.0 Become Olympus + +The rename — repo, config filename, env prefixes, default agent names — lands +before anything else ([improvement plan 0.2-0](improvement-plan.md), +[pantheon.md](pantheon.md)). The consumer count is zero today; that window +closes permanently when heron onboards (1.5), so the rename is the one item +whose cost is a function of calendar position, not effort. + +**Success:** mechanism code carries zero legacy names; self-CI green; +`examples/consumer/` references `Netis/olympus`. + +### 1.1 `olympus init` — one-command onboarding (subsumes `doctor`) + +A single installer script: creates the four labels, copies/templates the +wrapper workflows with the current pinned tag stamped in both `uses:` and +`olympus_ref:` (killing the lockstep-by-hand failure), scaffolds a minimal +`.olympus.json`, then runs doctor mode — schema validation, secrets +presence, runner reachability, and an explicit test that `AGENT_GH_TOKEN` is a +PAT able to emit workflow-triggering events (the documented #1 onboarding +failure, today diagnosable only from prose). + +**Success:** fresh repo with prerequisites in place → green smoke-test triage +reply in <30 minutes; doctor catches a mistaken `GITHUB_TOKEN` with an +actionable message instead of silence. + +### 1.2 Zero-runner tier + +A first-class mode where **triage + review + guard run on GitHub-hosted +runners against the Anthropic API directly** — one secret, zero +infrastructure. Implement/revise/observe stay self-hosted (write-heavy, +long-lived, repo-trusting execution). The installer offers "lite" (zero infra) +vs "full" (whole loop) profiles. This is the single largest friction cut +available: a stranger gets warm investigated triage replies and structured PR +reviews first, experiences the value, then graduates to the full loop — +and the read-mostly surfaces coming first is also the right trust story. + +**Success:** a repo with only `ANTHROPIC_API_KEY` set gets a triage reply on +its first issue; lite onboarding measured at <15 minutes. + +### 1.3 Trust tier 1: containment + approval gates for the implement agent + +The open-sourcing security blocker +([improvement plan 0.2-1](improvement-plan.md)): a scoped tool surface for the +implement agent (no bare Bash, egress denied by default), token-stripped agent +subprocess env, untrusted-content fencing in prompts, and a +**maintainer-approval gate** — on a stranger-filed issue, triage may +*recommend* dispatch, never auto-dispatch; the `try` label only counts when +applied by the triage bot or an allowlisted human. Every agent action (label, +comment, push, merge) lands in a structured audit record linked from the PR. + +**Gates all Horizon 2 promotion.** + +**Success:** a red-team eval suite (injection-laced issues) passes — no +out-of-allowlist execution, no auto-dispatch on unapproved stranger issues; +a documented threat model in `docs/`. + +### 1.4 Finish generalization + schema-in-guard + the forge seam + +The original v0.2 items — de-heron the review prompt via a +`{project_context}` slot, purge remaining hardcoded identities/labels, wire +config-schema validation into `guard.yml` +([improvement plan 0.2-2/0.2-3/0.2-4](improvement-plan.md)) — **plus** the +seam: extract every forge write (`gh issue comment`, `gh pr merge`, +`gh workflow run`, …) into a single `scripts/lib/forge.sh`, enforced by a +self-CI lint rule ("no raw `gh` outside `lib/forge.sh`"). No adapters yet; +just stop deepening the coupling. + +**Success:** zero heron-specific strings reachable from any prompt; a +malformed `.olympus.json` fails the PR in guard; raw `gh` outside the seam +is a CI failure. + +### 1.5 Dogfood completion + +The standing target: **heron becomes consumer #1** — its in-tree scripts +replaced by wrappers pinned to a tag, with zero behavior regression; that's +the proof the extraction is lossless. And once 1.3 lands, flip +`SELF_DOGFOOD_ENABLED` on this repo so Olympus maintains itself in public. + +**Success:** heron runs ≥4 weeks on a pinned tag with zero regression; ≥1 +agent-authored PR merged into Olympus by its own loop. + +### 1.6 Eval expansion as the regression safety net + +Grow the bench from 4 tasks to 10+ across all five loop stages — multi-file +implement, revise convergence, observer classification, merge-gate matrix, +gate boundaries, non-English triage, and the 1.3 injection suite +([improvement plan 0.4-2](improvement-plan.md), pulled forward as the H1 +closer). Everything in later horizons changes prompts and glue; without +evals, every horizon is a regression lottery. "Evals green" becomes the tag +gate. + +**Deliberately out of scope in H1:** multi-forge adapters (seam only — demand +sample size is one forge), telemetry (needs H2's privacy design), fleet +tooling (no fleet exists yet), staging-soak (fine to slip to H2/H3). + +## Horizon 2 — adoption & ecosystem (~v0.4–0.5) + +*Strangers arrive, succeed, extend, and stick around.* Entered only when H1's +trust tier has shipped. + +### 2.1 The Agora — public demo repo + launch + +The **Agora** (`olympus-demo`): a small real service where the entire loop runs in public — +visitors file an issue and watch triage reply, the agent open a draft PR, +review gate it, and (on a synthetic outage) the observer file an incident. +"File an issue, watch the loop" as the call to action. Coordinated launch +(blog post, Show HN) only after 1.3 + 2.2 — a public demo is by construction +an adversarial-input magnet. + +**Success:** the demo handles arbitrary public issues for a month without a +safety incident; ≥10 external repos onboard within a quarter of launch. + +### 2.2 Community infrastructure & release discipline + +`CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md` (disclosure policy — +mandatory for a product whose pitch includes "agents with write access"), +issue/PR templates, curated good-first-issues (cookbook gaps and eval tasks +are ideal), semver with a changelog and **migration notes per tag**, a +deprecation window for config fields, and release automation that cuts tags +only on green evals. Release discipline is doubly load-bearing here: every +consumer is pinned and bumps by hand. + +**Success:** ≥5 external contributors merged; zero unannounced breaking +config changes; every tag ships migration notes. + +### 2.3 Forge adapter layer: GitLab first + +Build the adapter behind H1's seam: `forge.sh` dispatches to +`forge-github.sh` / `forge-gitlab.sh` (the ~10 verbs: read issue, comment, +label, open PR/MR, review, merge, dispatch, status), CI templates get a +GitLab `include:` equivalent of the reusable workflows, and agent prompts take +their forge-CLI vocabulary from config. GitLab self-managed users are +precisely the audience that wants self-hosted agents and that vendor coding +agents structurally cannot serve — a differentiation item, not just a +generality item. Gitea/Forgejo follow as community-contributed adapters +against a documented adapter contract + conformance checklist. + +**Success:** the full loop (minus observe, already forge-agnostic) green on a +GitLab self-managed repo; the adapter contract documented well enough that a +Forgejo adapter arrives as an external PR. + +### 2.4 Harness ecosystem + +Promote `harness.kind: custom` into a qualification program: run `evals/` +against alternative agent CLIs, publish a scoreboard in-repo, document the +"qualified" thresholds, and ship a cookbook per qualified harness. +Model-agnosticism is a top-3 differentiator, and Olympus is unusual in +having an *objective* bench to back the claim — a published scoreboard is also +organic marketing. + +**Success:** ≥2 non-claude harnesses qualified and documented; a harness +regression is caught by the bench before a release ships it. + +### 2.5 Opt-in telemetry & cost accounting (local-first) + +Built on the run-summary primitive +([improvement plan 0.3-5](improvement-plan.md)): per-run structured records +(tokens, wall time, verdict, rounds-to-merge) stay as artifacts in the +consumer's own repo — **no central collection**. An `olympus stats` command +aggregates locally over a repo's history. Token budgets land on top: budget +exceeded → escalate to a human with a clear comment, never a silent stop. An +anonymous adoption beacon is strictly opt-in. + +**Success:** every agent run produces a cost record; a consumer answers "cost +per merged PR last month" with one command. + +### 2.6 Trust tier 2: graduated autonomy levels + +Codify a named autonomy ladder in `.olympus.json`: **L0** triage-only → +**L1** implement with maintainer-gated dispatch → **L2** auto-dispatch, human +merge → **L3** gated auto-merge. The installer defaults new repos to L1; +promotion is a one-line config change; the audit trail annotates which level +authorized each action. This turns the prerequisites doc's prose advice +("leave `AUTO_MERGE_TEAM` empty at first") into enforceable, legible policy — +policy-as-config *is* the product, so the trust dimension becomes first-class +in it. + +**Success:** the demo repo runs publicly at L2; the docs answer "what can the +agent do to my repo?" with a single table. + +## Horizon 3 — scale & fleet (v1.0+) + +*An org runs Olympus on 50 repos and one platform engineer can operate it.* +v1.0 is declared when the contracts below carry stability guarantees — a trust +signal earned by H1/H2 discipline, not a feature release. + +### 3.1 v1.0 stability contract + +Freeze and document compatibility guarantees: the `.olympus.json` schema +(additive-only within 1.x), the forge adapter contract, reusable-workflow +inputs, and the harness command template — each with conformance tests. Orgs +pin 50 repos to these tags; the upgrade promise *is* the product at fleet +scale. + +**Success:** a consumer upgrades 1.0→1.x fleet-wide with no wrapper edits +beyond the version bump. + +### 3.2 Atlas — the fleet CLI (OSS scope) + +Stateless, CLI-driven, against a config-file-listed set of repos: + +- `atlas bump` — opens version-bump PRs across N repos with migration notes + inlined. The bump PRs flow through each repo's own review gate — the fleet + feature is itself loop-shaped. +- `atlas doctor` — 1.1's doctor across the fleet. +- `atlas drift` — diffs each repo's `.olympus.json` against an org baseline + policy and flags unauthorized divergence (e.g. a repo that quietly enabled + L3 auto-merge). + +Manual lockstep pinning at 50 repos is where adoption dies inside orgs; drift +detection is the governance story that lets a platform team say yes. No +server, no state — cleanly inside OSS scope. + +**Success:** one command produces 50 reviewable bump PRs; a deliberately +drifted repo is flagged within one CI cycle. + +### 3.3 Heartbeat & cost dashboard (static, OSS) — and the commercial boundary + +Aggregate the 2.5 records into a static dashboard (scheduled workflow → JSON → +static page): per-repo agent last-seen, escalation rates, cost per merged PR, +observer incident counts. **Explicit boundary:** the OSS line stops at "static +aggregation of data the org already owns." A hosted multi-tenant control +plane (live dashboards, alerting, cross-org budget enforcement, SSO) is the +natural future commercial layer — stating the boundary now is honest signaling +that protects community trust later. + +**Success:** a 50-repo org answers "which agents are silent, what did the +fleet spend, where are humans escalated most" from one self-hosted page. + +### 3.4 Staging-soak gate shape + observer depth + +The long-deferred pre-prod chain: a `staging-soaked`-style status token where +Olympus provides only the **gate shape** and each repo supplies the soak +content. Plus observer maturation: multi-endpoint health, latency/error-budget +signals (not just DOWN), richer incident context for triage to consume. The +prod-observe → triage feedback loop is the moat; deepening it compounds +differentiation, and at fleet scale the observer is what makes auto-merge +defensible. + +**Success:** ≥2 consumers gate deploys on soak; ≥1 documented incident +auto-filed → auto-triaged → auto-fixed → merged, end-to-end, no human code. + +### 3.5 Multi-language ecosystem hardening + +Cookbook presets and eval fixtures per major ecosystem (Node, Go, Rust, JVM, +Python): build-command recipes, test-hint conventions, ecosystem-specific +hygiene patterns — mostly community-contributed against an eval-fixture +template. + +**Success:** the top five ecosystems each have a cookbook preset and a +passing eval fixture. + +## Measurement + +Instrumented from 2.5 onward; targets are directional, not promises. + +| Metric | Definition | Healthy signal | +|---|---|---| +| Time-to-first-triage | onboard start → first triage reply | <30 min full, <15 min lite | +| Onboarding completion | `init` started → doctor green | PAT failure rate trending to ~0 | +| Auto-dispatch precision | `try` issues yielding a mergeable PR | rising per release | +| Auto-merge rate | agent PRs merged without human edits | rising *only* alongside a flat incident rate | +| Escalation rate | revise loops hitting the round cap | falling per release | +| Cost per merged PR | tokens + runner minutes per merged agent PR | published honestly; trending down | +| Rounds-to-merge | review→revise cycles per merged PR | median ≤2 | +| **Loop-closure count** | observer incident → merged fix, no human code | >0, then growing — the headline stat | +| Fleet pin freshness | % of fleet within one minor of latest | >90% with `atlas bump` | +| Ecosystem counts | external contributors, qualified harnesses, forge adapters | each growing per quarter | + +## What each horizon sharpens + +- **H1 — trustworthy by construction:** containment + approval gates + audit + trail, the precondition for self-hosted agents on public repos, and a + problem vendor agents treat as someone else's. +- **H2 — no lock-in, any model, any forge:** the GitLab adapter and harness + scoreboard are claims the vendor agents structurally cannot copy. +- **H3 — the closed loop at fleet scale:** prod-observe feedback plus fleet + governance is the territory between "coding agent" products and + platform-engineering reality that nobody else occupies. diff --git a/docs/setup.md b/docs/setup.md index 4de0efa..c65449c 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -1,16 +1,16 @@ # Setup — onboarding a repo -A walkthrough of putting agent-ops on a repo. Assumes the +A walkthrough of putting Olympus on a repo. Assumes the [prerequisites](prerequisites.md) (runner + secrets) are in place. -## 1. Add `.agent-ops.json` +## 1. Add `.olympus.json` -Copy [`examples/consumer/.agent-ops.json`](../examples/consumer/.agent-ops.json) +Copy [`examples/consumer/.olympus.json`](../examples/consumer/.olympus.json) to the repo root and edit it. Minimum useful config: ```json { - "$schema": "https://raw.githubusercontent.com/Netis/agent-ops/main/schema/agent-ops.schema.json", + "$schema": "https://raw.githubusercontent.com/Netis/olympus/main/schema/olympus.schema.json", "project": { "name": "my-service", "default_branch": "main" }, "triage": { "gates": { @@ -40,12 +40,12 @@ into your repo's `.github/workflows/`: | `observe.yml` | schedule (prod repos only) | self-hosted runner | Edit each wrapper's `runner_labels` to match your pool, and keep both the -`uses: ...@v0.2.0` and `agent_ops_ref: v0.2.0` on the **same** release. +`uses: ...@v0.2.0` and `olympus_ref: v0.2.0` on the **same** release. > `pr-review.yml` keys off a workflow named **`ci`** completing. If your CI > workflow has a different name, change `workflows: [ci]` in the wrapper. > `pr-revise.yml` **must** be named `pr-revise.yml` (the dispatch target) unless -> you set `AGENT_OPS_REVISE_WORKFLOW`. +> you set `OLYMPUS_REVISE_WORKFLOW`. ## 3. Set permissions on the wrappers @@ -60,7 +60,7 @@ workflows inherit the caller's permissions, so these matter: ## 4. Create the labels -Create the four labels (or your renamed equivalents from `.agent-ops.json`): +Create the four labels (or your renamed equivalents from `.olympus.json`): `agent:assess`, `agent:try`, `agent:skip`, `auto-agent`. The agents add/read them; GitHub won't auto-create a missing label. @@ -75,5 +75,5 @@ them; GitHub won't auto-create a missing label. ## 6. Upgrading later -Bump the tag in **all** wrappers (`@vNEXT` + `agent_ops_ref: vNEXT`) in one PR. -The mechanism updates for the whole repo at once; your `.agent-ops.json` stays. +Bump the tag in **all** wrappers (`@vNEXT` + `olympus_ref: vNEXT`) in one PR. +The mechanism updates for the whole repo at once; your `.olympus.json` stays. diff --git a/evals/README.md b/evals/README.md index b94cbb0..f5155fa 100644 --- a/evals/README.md +++ b/evals/README.md @@ -68,7 +68,7 @@ Drop a `tasks///` folder with: ## Known constraint -agent-ops's prompts are Claude-shaped (triage wants JSON, review wants a +olympus's prompts are Claude-shaped (triage wants JSON, review wants a `### Summary` heading the live `post_review.py` parses). The eval's task prompts are deliberately representative/simplified to test the *capability*; a candidate that scores well here but formats differently may still need prompt tuning for the diff --git a/evals/run.sh b/evals/run.sh index 6506837..105ed7a 100644 --- a/evals/run.sh +++ b/evals/run.sh @@ -34,11 +34,11 @@ done [ -n "$label" ] || label="$harness" # Configure the harness via env (config.sh / agent-harness.sh honor env over file). -export AGENT_OPS_HARNESS="$harness" -[ -n "$command_tmpl" ] && export AGENT_OPS_HARNESS_CMD="$command_tmpl" +export OLYMPUS_HARNESS="$harness" +[ -n "$command_tmpl" ] && export OLYMPUS_HARNESS_CMD="$command_tmpl" [ -n "$model" ] && export ANTHROPIC_MODEL="$model" # A custom harness usually isn't behind an OpenAI-compatible /v1/models probe. -[ "$harness" = "custom" ] && export AGENT_OPS_HEALTH_PROBE="${AGENT_OPS_HEALTH_PROBE:-false}" +[ "$harness" = "custom" ] && export OLYMPUS_HEALTH_PROBE="${OLYMPUS_HEALTH_PROBE:-false}" # shellcheck source=scripts/lib/agent-harness.sh source "$REPO/scripts/lib/agent-harness.sh" diff --git a/examples/consumer/.github/workflows/guard.yml b/examples/consumer/.github/workflows/guard.yml index f15199f..0b19275 100644 --- a/examples/consumer/.github/workflows/guard.yml +++ b/examples/consumer/.github/workflows/guard.yml @@ -8,9 +8,9 @@ on: jobs: guard: - uses: Netis/agent-ops/.github/workflows/guard.yml@v0.2.0 + uses: Netis/olympus/.github/workflows/guard.yml@v0.2.0 with: - agent_ops_ref: v0.2.0 + olympus_ref: v0.2.0 # Flip on once AGENT_GH_TOKEN is set, to also verify referenced # secrets are provisioned + sane: run_secret_checks: false diff --git a/examples/consumer/.github/workflows/implement.yml b/examples/consumer/.github/workflows/implement.yml index f8036e9..d88438c 100644 --- a/examples/consumer/.github/workflows/implement.yml +++ b/examples/consumer/.github/workflows/implement.yml @@ -13,8 +13,8 @@ permissions: jobs: implement: if: ${{ github.event.label.name == 'agent:try' }} - uses: Netis/agent-ops/.github/workflows/implement.yml@v0.2.0 + uses: Netis/olympus/.github/workflows/implement.yml@v0.2.0 with: - agent_ops_ref: v0.2.0 + olympus_ref: v0.2.0 runner_labels: '["self-hosted","my-runner"]' secrets: inherit # + AUTO_MERGE_TEAM (optional) diff --git a/examples/consumer/.github/workflows/observe.yml b/examples/consumer/.github/workflows/observe.yml index 3cc2a05..64343df 100644 --- a/examples/consumer/.github/workflows/observe.yml +++ b/examples/consumer/.github/workflows/observe.yml @@ -1,7 +1,7 @@ name: observe # Polls the deployed service every 5 min; files a deduplicated incident issue # on a sustained failure. Prefer the systemd-timer path (scripts/agent-bot/ -# mara.service/.timer on a host isolated from prod) if you want the observer to +# argus.service/.timer on a host isolated from prod) if you want the observer to # survive an outage that also takes down this runner. on: @@ -18,9 +18,9 @@ permissions: jobs: observe: - uses: Netis/agent-ops/.github/workflows/observe.yml@v0.2.0 + uses: Netis/olympus/.github/workflows/observe.yml@v0.2.0 with: - agent_ops_ref: v0.2.0 + olympus_ref: v0.2.0 runner_labels: '["self-hosted","my-runner"]' dry_run: ${{ github.event.inputs.dry_run == 'true' }} secrets: inherit diff --git a/examples/consumer/.github/workflows/pr-review.yml b/examples/consumer/.github/workflows/pr-review.yml index 70a8793..ceaf7f3 100644 --- a/examples/consumer/.github/workflows/pr-review.yml +++ b/examples/consumer/.github/workflows/pr-review.yml @@ -22,9 +22,9 @@ permissions: jobs: review: - uses: Netis/agent-ops/.github/workflows/review.yml@v0.2.0 + uses: Netis/olympus/.github/workflows/review.yml@v0.2.0 with: - agent_ops_ref: v0.2.0 + olympus_ref: v0.2.0 runner_labels: '["self-hosted","my-runner"]' # Empty string on the workflow_run path (resolved from the run event); # the PR number you typed on a manual workflow_dispatch re-review. diff --git a/examples/consumer/.github/workflows/pr-revise.yml b/examples/consumer/.github/workflows/pr-revise.yml index 8f6d033..6ee8103 100644 --- a/examples/consumer/.github/workflows/pr-revise.yml +++ b/examples/consumer/.github/workflows/pr-revise.yml @@ -1,7 +1,7 @@ name: pr-revise # Dispatched by the review workflow when the review bot requests changes on an # auto-agent PR. The file MUST be named pr-revise.yml (revise_dispatch.sh -# dispatches that workflow name) unless you set AGENT_OPS_REVISE_WORKFLOW. +# dispatches that workflow name) unless you set OLYMPUS_REVISE_WORKFLOW. on: workflow_dispatch: @@ -17,9 +17,9 @@ permissions: jobs: revise: - uses: Netis/agent-ops/.github/workflows/revise.yml@v0.2.0 + uses: Netis/olympus/.github/workflows/revise.yml@v0.2.0 with: pr_number: ${{ inputs.pr_number }} - agent_ops_ref: v0.2.0 + olympus_ref: v0.2.0 runner_labels: '["self-hosted","my-runner"]' secrets: inherit diff --git a/examples/consumer/.github/workflows/triage.yml b/examples/consumer/.github/workflows/triage.yml index 30d70ac..2fcc9b4 100644 --- a/examples/consumer/.github/workflows/triage.yml +++ b/examples/consumer/.github/workflows/triage.yml @@ -1,6 +1,6 @@ name: triage # Drop this into a consuming repo at .github/workflows/triage.yml. -# Pin @vX.Y.Z (and agent_ops_ref) to the same agent-ops release. +# Pin @vX.Y.Z (and olympus_ref) to the same olympus release. on: issues: @@ -12,8 +12,8 @@ permissions: jobs: triage: - uses: Netis/agent-ops/.github/workflows/triage.yml@v0.2.0 + uses: Netis/olympus/.github/workflows/triage.yml@v0.2.0 with: - agent_ops_ref: v0.2.0 + olympus_ref: v0.2.0 runner_labels: '["self-hosted","my-runner"]' secrets: inherit # LITELLM_BASE_URL/API_KEY/NO_PROXY + AGENT_GH_TOKEN diff --git a/examples/consumer/.agent-ops.json b/examples/consumer/.olympus.json similarity index 82% rename from examples/consumer/.agent-ops.json rename to examples/consumer/.olympus.json index 03a7a45..7a7c8f8 100644 --- a/examples/consumer/.agent-ops.json +++ b/examples/consumer/.olympus.json @@ -1,12 +1,12 @@ { - "$schema": "https://raw.githubusercontent.com/Netis/agent-ops/main/schema/agent-ops.schema.json", + "$schema": "https://raw.githubusercontent.com/Netis/olympus/main/schema/olympus.schema.json", "project": { "name": "my-service", "default_branch": "main" }, "agents": { - "review_bot_login": "vivi", - "dev_agent_name": "wiwi" + "review_bot_login": "themis", + "dev_agent_name": "hephaestus" }, "labels": { "assess": "agent:assess", diff --git a/schema/agent-ops.schema.json b/schema/olympus.schema.json similarity index 88% rename from schema/agent-ops.schema.json rename to schema/olympus.schema.json index f08883d..86baaae 100644 --- a/schema/agent-ops.schema.json +++ b/schema/olympus.schema.json @@ -1,8 +1,8 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://raw.githubusercontent.com/Netis/agent-ops/main/schema/agent-ops.schema.json", - "title": "agent-ops consumer config (.agent-ops.json)", - "description": "Per-repo policy for the agent-ops mechanism. Every field is optional; omitted values fall back to a built-in default. This file is the ONLY place a consuming repo expresses repo-specific behavior — the agent-ops scripts and workflows carry none.", + "$id": "https://raw.githubusercontent.com/Netis/olympus/main/schema/olympus.schema.json", + "title": "Olympus consumer config (.olympus.json)", + "description": "Per-repo policy for the Olympus mechanism. Every field is optional; omitted values fall back to a built-in default. This file is the ONLY place a consuming repo expresses repo-specific behavior — the Olympus scripts and workflows carry none.", "type": "object", "additionalProperties": true, "properties": { @@ -19,7 +19,7 @@ "type": "object", "additionalProperties": false, "properties": { - "review_bot_login": { "type": "string", "default": "vivi", "description": "GitHub login (or body-footer marker) of the review bot. Auto-merge / revise key off this to find its review." }, + "review_bot_login": { "type": "string", "default": "themis", "description": "GitHub login (or body-footer marker) of the review bot. Auto-merge / revise key off this to find its review." }, "dev_agent_name": { "type": "string", "default": "the dev agent", "description": "Display name used in commit author + log lines for the implementer." } } }, @@ -62,7 +62,7 @@ "additionalProperties": false, "properties": { "service_name": { "type": "string", "default": "the service", "description": "Name used in incident text." }, - "health_url": { "type": "string", "description": "Health endpoint mara polls." }, + "health_url": { "type": "string", "description": "Health endpoint argus polls." }, "repo": { "type": "string", "description": "owner/name the incident issue is filed on." }, "labels": { "type": "string", "default": "incident", "description": "Comma-separated issue labels." }, "readiness": { diff --git a/scripts/agent-bot/mara.service b/scripts/agent-bot/argus.service similarity index 80% rename from scripts/agent-bot/mara.service rename to scripts/agent-bot/argus.service index 09cacf1..48dc390 100644 --- a/scripts/agent-bot/mara.service +++ b/scripts/agent-bot/argus.service @@ -1,5 +1,5 @@ [Unit] -Description=mara — heron prod observer (one poll) +Description=argus — prod observer (one poll) After=network-online.target Wants=network-online.target @@ -8,10 +8,10 @@ Type=oneshot # Run as a host user that has: ssh access to the prod box (for log context) # and an authenticated `gh` (for filing issues). On the reference host that # is the same user the deploy runner uses. -User=mara +User=argus # Config + GH_TOKEN live here (chmod 600, NOT in the repo). The leading '-' # makes it optional so the unit loads even before the operator creates it. -EnvironmentFile=-/etc/mara/env -ExecStart=/usr/local/bin/mara.sh +EnvironmentFile=-/etc/argus/env +ExecStart=/usr/local/bin/argus.sh # A failed poll (e.g. transient network) must not wedge the timer. SuccessExitStatus=0 1 diff --git a/scripts/agent-bot/mara.sh b/scripts/agent-bot/argus.sh similarity index 70% rename from scripts/agent-bot/mara.sh rename to scripts/agent-bot/argus.sh index 4f19cc3..6f6734c 100644 --- a/scripts/agent-bot/mara.sh +++ b/scripts/agent-bot/argus.sh @@ -1,12 +1,12 @@ #!/usr/bin/env bash -# mara — prod observer. One poll of the production heron: detect failure +# argus — prod observer. One poll of the production heron: detect failure # conditions and file a deduplicated GitHub issue (with context) so the -# triage -> wiwi loop can pick it up. Closes the incident loop that a human +# triage -> hephaestus loop can pick it up. Closes the incident loop that a human # otherwise has to watch by hand. # # Runs on a host ISOLATED from prod (a separate box from the one it watches), -# driven by a systemd timer (mara.timer). Each invocation is one poll; state -# (for dedup) persists in $MARA_STATE_DIR. +# driven by a systemd timer (argus.timer). Each invocation is one poll; state +# (for dedup) persists in $ARGUS_STATE_DIR. # # Detected conditions (health-based, current-state — no stale-log ambiguity): # DOWN : /api/health unreachable / non-2xx / unparseable @@ -17,40 +17,40 @@ # lines). # # Config (env; nothing internal hardcoded — the systemd unit supplies these): -# MARA_HEALTH_URL required, e.g. http://:4500/api/health -# MARA_LOG_HOST optional ssh host for log context (e.g. user@host) -# MARA_LOG_PATH log path on MARA_LOG_HOST (default /tmp/heron.log) -# MARA_REPO GitHub repo (default Netis/heron) -# MARA_LABELS issue labels (comma-sep) (default mara,incident) -# MARA_STATE_DIR dedup state dir (default $HOME/.mara) -# MARA_DEDUP_SECS don't refile same signature within (default 21600 = 6h) -# MARA_CONFIRM_POLLS polls that must ALL fail to file (default 3, 1=off) -# MARA_CONFIRM_DELAY_SECS seconds between confirm polls (default 10) -# MARA_DRY_RUN "1" → print the issue instead of filing (needs no token) +# ARGUS_HEALTH_URL required, e.g. http://:4500/api/health +# ARGUS_LOG_HOST optional ssh host for log context (e.g. user@host) +# ARGUS_LOG_PATH log path on ARGUS_LOG_HOST (default /tmp/heron.log) +# ARGUS_REPO GitHub repo (default Netis/heron) +# ARGUS_LABELS issue labels (comma-sep) (default argus,incident) +# ARGUS_STATE_DIR dedup state dir (default $HOME/.argus) +# ARGUS_DEDUP_SECS don't refile same signature within (default 21600 = 6h) +# ARGUS_CONFIRM_POLLS polls that must ALL fail to file (default 3, 1=off) +# ARGUS_CONFIRM_DELAY_SECS seconds between confirm polls (default 10) +# ARGUS_DRY_RUN "1" → print the issue instead of filing (needs no token) # GH_TOKEN PAT for `gh` (from the unit's EnvironmentFile) unless dry-run # # A single failed poll is NOT filed on its own: a prod deploy restarts heron # (health is briefly down, and the pipeline reads running=false until capture -# resumes — observed ~10 s), and a one-off network hiccup looks identical. mara +# resumes — observed ~10 s), and a one-off network hiccup looks identical. argus # re-polls and only files when EVERY poll fails, so a deploy/restart blip never # opens a phantom incident. (For an airtight deploy guard, pair with a # maintenance-window sentinel — A reduces but can't fully eliminate the window # for an unusually long restart.) set -uo pipefail -HEALTH_URL="${MARA_HEALTH_URL:?set MARA_HEALTH_URL}" -SVC="${MARA_SERVICE_NAME:-the service}" -READY_JQ="${MARA_READY_JQ:-}" # optional jq filter for "parked" detection -READY_EXPECT="${MARA_READY_EXPECT:-true}" -LOG_HOST="${MARA_LOG_HOST:-}" -LOG_PATH="${MARA_LOG_PATH:-/tmp/service.log}" -REPO="${MARA_REPO:?set MARA_REPO (owner/name of the repo to file incidents on)}" -LABELS="${MARA_LABELS:-incident}" -STATE_DIR="${MARA_STATE_DIR:-$HOME/.mara}" -DEDUP_SECS="${MARA_DEDUP_SECS:-21600}" -CONFIRM_POLLS="${MARA_CONFIRM_POLLS:-3}" -CONFIRM_DELAY="${MARA_CONFIRM_DELAY_SECS:-10}" -DRY_RUN="${MARA_DRY_RUN:-0}" +HEALTH_URL="${ARGUS_HEALTH_URL:?set ARGUS_HEALTH_URL}" +SVC="${ARGUS_SERVICE_NAME:-the service}" +READY_JQ="${ARGUS_READY_JQ:-}" # optional jq filter for "parked" detection +READY_EXPECT="${ARGUS_READY_EXPECT:-true}" +LOG_HOST="${ARGUS_LOG_HOST:-}" +LOG_PATH="${ARGUS_LOG_PATH:-/tmp/service.log}" +REPO="${ARGUS_REPO:?set ARGUS_REPO (owner/name of the repo to file incidents on)}" +LABELS="${ARGUS_LABELS:-incident}" +STATE_DIR="${ARGUS_STATE_DIR:-$HOME/.argus}" +DEDUP_SECS="${ARGUS_DEDUP_SECS:-21600}" +CONFIRM_POLLS="${ARGUS_CONFIRM_POLLS:-3}" +CONFIRM_DELAY="${ARGUS_CONFIRM_DELAY_SECS:-10}" +DRY_RUN="${ARGUS_DRY_RUN:-0}" GH_BIN="${GH_BIN:-$(command -v gh || echo "$HOME/bin/gh")}" mkdir -p "$STATE_DIR" @@ -65,7 +65,7 @@ now=$(date +%s) # docker-bridge addresses) # 2. home-directory paths (/home/, /Users/) # 3. URL authorities (scheme://HOST:PORT → scheme://) — so an -# internal hostname in MARA_HEALTH_URL or a +# internal hostname in ARGUS_HEALTH_URL or a # logged URL never lands in the issue. IPv4 # hosts are already masked by rule 1. # 4. ssh-style user@host tokens (user@host → @) @@ -98,7 +98,7 @@ probe_once() { # Optional "parked" detection: the service answers health 200 but a deeper # readiness field says it isn't actually doing its job (the silent-failure # mode). Configured per-repo via observer.readiness.{jq,expect}; when no jq - # filter is set, mara only reports DOWN (the universal signal). + # filter is set, argus only reports DOWN (the universal signal). ready="$(printf '%s' "$json" | jq -r "$READY_JQ" 2>/dev/null || echo '__err__')" if [ "$ready" = "__err__" ] || [ "$ready" = "null" ] || [ -z "$ready" ]; then signature="prod-health-bad" @@ -112,12 +112,12 @@ probe_once() { probe_once if [ -z "$signature" ]; then - echo "mara: prod ${SVC} OK (HTTP $code) — no incident" + echo "argus: prod ${SVC} OK (HTTP $code) — no incident" exit 0 fi # ---- confirm the failure is SUSTAINED, not a transient blip ------------- -# Re-poll up to MARA_CONFIRM_POLLS times; if ANY confirmation comes back +# Re-poll up to ARGUS_CONFIRM_POLLS times; if ANY confirmation comes back # healthy, the first hit was transient (deploy/restart window, one-off network # hiccup) → do NOT file. Only a failure on EVERY poll is reported, using the # freshest snapshot. CONFIRM_POLLS=1 disables this (file on first failure). @@ -128,10 +128,10 @@ while [ "$n" -lt "$CONFIRM_POLLS" ]; do n=$((n + 1)) probe_once if [ -z "$signature" ]; then - echo "mara: '$first_sig' cleared on confirm poll $n/$CONFIRM_POLLS — transient (likely a deploy/restart blip), not filing" + echo "argus: '$first_sig' cleared on confirm poll $n/$CONFIRM_POLLS — transient (likely a deploy/restart blip), not filing" exit 0 fi - echo "mara: failure persists on confirm poll $n/$CONFIRM_POLLS (signature=$signature)" >&2 + echo "argus: failure persists on confirm poll $n/$CONFIRM_POLLS (signature=$signature)" >&2 done # Confirmed sustained. Refresh `now` so dedup/SEEN reflect confirm time, not the # first probe (the confirm polls added a few seconds). @@ -140,7 +140,7 @@ now=$(date +%s) # ---- dedup: skip if filed within the window ---------------------------- last="$(awk -F'\t' -v s="$signature" '$1==s{print $2}' "$SEEN" | tail -1)" if [ -n "$last" ] && [ $(( now - last )) -lt "$DEDUP_SECS" ]; then - echo "mara: '$signature' already reported $(( (now-last)/60 ))m ago (< dedup window) — skipping" + echo "argus: '$signature' already reported $(( (now-last)/60 ))m ago (< dedup window) — skipping" exit 0 fi @@ -153,9 +153,9 @@ if [ -n "$LOG_HOST" ]; then fi stamp="$(date -u +%Y-%m-%dT%H:%M:%SZ)" -title="[mara] prod incident: ${signature}" +title="[argus] prod incident: ${signature}" body="$(cat </dev/null || true)" if [ -n "$existing" ]; then - echo "mara: open issue #$existing already tracks '$signature' — recording + skipping" + echo "argus: open issue #$existing already tracks '$signature' — recording + skipping" printf '%s\t%s\n' "$signature" "$now" >> "$SEEN" exit 0 fi @@ -203,11 +203,11 @@ url="$("$GH_BIN" issue create --repo "$REPO" --title "$title" --label "$LABELS" if ! printf '%s' "$url" | grep -q 'github.com/'; then # gh won't auto-create a missing label and fails the whole call — a missing # label must not lose the incident, so retry without labels. - echo "mara: create with labels failed ($url); retrying without labels" >&2 + echo "argus: create with labels failed ($url); retrying without labels" >&2 url="$("$GH_BIN" issue create --repo "$REPO" --title "$title" --body "$body" 2>&1)" fi if printf '%s' "$url" | grep -q 'github.com/'; then - echo "mara: filed $url"; printf '%s\t%s\n' "$signature" "$now" >> "$SEEN" + echo "argus: filed $url"; printf '%s\t%s\n' "$signature" "$now" >> "$SEEN" else - echo "mara: gh issue create FAILED: $url" >&2; exit 1 + echo "argus: gh issue create FAILED: $url" >&2; exit 1 fi diff --git a/scripts/agent-bot/mara.timer b/scripts/agent-bot/argus.timer similarity index 78% rename from scripts/agent-bot/mara.timer rename to scripts/agent-bot/argus.timer index c079344..24050d7 100644 --- a/scripts/agent-bot/mara.timer +++ b/scripts/agent-bot/argus.timer @@ -1,5 +1,5 @@ [Unit] -Description=mara — poll prod heron every 5 minutes +Description=argus — poll prod health every 5 minutes After=network-online.target [Timer] diff --git a/scripts/agent-bot/auto_merge.sh b/scripts/agent-bot/auto_merge.sh index 4ff9be4..a7f145d 100755 --- a/scripts/agent-bot/auto_merge.sh +++ b/scripts/agent-bot/auto_merge.sh @@ -1,17 +1,17 @@ #!/usr/bin/env bash -# Called from the tail of pr-review.yml AFTER vivi posts her review. +# Called from the tail of pr-review.yml AFTER themis posts her review. # Auto-merges iff: # - PR has label `auto-agent` -# - PR is not draft (wiwi may have flipped it; or the linked issue +# - PR is not draft (hephaestus may have flipped it; or the linked issue # author was a team member and we promoted earlier — see below) -# - vivi's latest review state == APPROVED +# - themis's latest review state == APPROVED # - the linked issue's author is on the auto-merge allowlist set -euo pipefail -# Load .agent-ops.json → AGENT_OPS_* (review-bot login, labels). +# Load .olympus.json → OLYMPUS_* (review-bot login, labels). # shellcheck source=scripts/lib/config.sh source "$(cd "$(dirname "${BASH_SOURCE[0]}")/../lib" && pwd)/config.sh" -agent_ops_load_config +olympus_load_config # Auto-merge allowlist (GitHub logins). Sourced from the AUTO_MERGE_TEAM env # (CSV or whitespace-separated), injected from a repo secret by the workflow @@ -22,14 +22,14 @@ PR="${PR_NUMBER:?PR_NUMBER required}" meta=$(gh pr view "$PR" --json isDraft,labels,body) labels=$(echo "$meta" | jq -r '.labels[].name') -echo "$labels" | grep -qx "$AGENT_OPS_LABEL_AUTO_AGENT" || { echo "not an auto-agent PR; skip"; exit 0; } +echo "$labels" | grep -qx "$OLYMPUS_LABEL_AUTO_AGENT" || { echo "not an auto-agent PR; skip"; exit 0; } # Latest review state. Some reviews have a null .body (e.g. quick # APPROVE clicks without a comment); coerce to "" before contains() # or jq aborts the whole pipeline. The review bot is identified by login OR # its footer in the body (handles bot-account attribution quirks). state=$(gh pr view "$PR" --json reviews --jq ' - [.reviews[] | select(.author.login==env.AGENT_OPS_REVIEW_BOT_LOGIN or ((.body // "") | contains(env.AGENT_OPS_REVIEW_BOT_LOGIN)))] + [.reviews[] | select(.author.login==env.OLYMPUS_REVIEW_BOT_LOGIN or ((.body // "") | contains(env.OLYMPUS_REVIEW_BOT_LOGIN)))] | last | .state // empty') [ "$state" = "APPROVED" ] || { echo "review verdict=$state; skip"; exit 0; } @@ -40,7 +40,7 @@ issue=$(echo "$meta" | jq -r '.body' | grep -oE 'Closes #[0-9]+' | head -1 | tr author=$(gh issue view "$issue" --json author --jq '.author.login') for m in $TEAM; do if [ "$m" = "$author" ]; then - echo "vivi APPROVED + author=$author ∈ TEAM → admin-merge" + echo "themis APPROVED + author=$author ∈ TEAM → admin-merge" # Lift draft (if still draft) and merge. gh pr ready "$PR" >/dev/null 2>&1 || true gh pr merge "$PR" --admin --squash --delete-branch diff --git a/scripts/agent-bot/revise_dispatch.sh b/scripts/agent-bot/revise_dispatch.sh index e122d26..1257d00 100755 --- a/scripts/agent-bot/revise_dispatch.sh +++ b/scripts/agent-bot/revise_dispatch.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash -# Called from the tail of pr-review.yml AFTER vivi posts her review. -# If vivi REQUEST_CHANGES'd a wiwi PR (label `auto-agent`), dispatch the -# `pr-revise` workflow so wiwi addresses the feedback automatically — unless +# Called from the tail of pr-review.yml AFTER themis posts her review. +# If themis REQUEST_CHANGES'd a hephaestus PR (label `auto-agent`), dispatch the +# `pr-revise` workflow so hephaestus addresses the feedback automatically — unless # the round cap is reached, in which case hand off to a human. # # Idempotent + safe to call for every PR: it bails out for human PRs, for @@ -13,45 +13,45 @@ # revise run must itself be able to push + re-trigger ci/pr-review. set -euo pipefail -# Load .agent-ops.json → AGENT_OPS_* (review-bot login, labels). +# Load .olympus.json → OLYMPUS_* (review-bot login, labels). # shellcheck source=scripts/lib/config.sh source "$(cd "$(dirname "${BASH_SOURCE[0]}")/../lib" && pwd)/config.sh" -agent_ops_load_config +olympus_load_config PR="${PR_NUMBER:?PR_NUMBER required}" -# Number of times vivi may block a PR before we stop auto-revising and +# Number of times themis may block a PR before we stop auto-revising and # escalate. Counts CHANGES_REQUESTED reviews already on the PR (including the -# one just posted), so cap=3 gives wiwi two automated revision attempts. +# one just posted), so cap=3 gives hephaestus two automated revision attempts. MAX_REVISE_ROUNDS="${MAX_REVISE_ROUNDS:-3}" meta=$(gh pr view "$PR" --json labels,reviews,state) [ "$(echo "$meta" | jq -r '.state')" = "OPEN" ] || { echo "PR #$PR not open; skip"; exit 0; } -echo "$meta" | jq -r '.labels[].name' | grep -qx "$AGENT_OPS_LABEL_AUTO_AGENT" || { echo "PR #$PR not auto-agent; skip"; exit 0; } +echo "$meta" | jq -r '.labels[].name' | grep -qx "$OLYMPUS_LABEL_AUTO_AGENT" || { echo "PR #$PR not auto-agent; skip"; exit 0; } # Review states in chronological order. Identify the review bot the same way # auto_merge.sh does (login OR its footer in the body). -vivi_states=$(echo "$meta" | jq -r ' +themis_states=$(echo "$meta" | jq -r ' [ .reviews[] - | select(.author.login==env.AGENT_OPS_REVIEW_BOT_LOGIN or ((.body // "") | contains(env.AGENT_OPS_REVIEW_BOT_LOGIN))) + | select(.author.login==env.OLYMPUS_REVIEW_BOT_LOGIN or ((.body // "") | contains(env.OLYMPUS_REVIEW_BOT_LOGIN))) | .state ] | .[]') -latest=$(printf '%s\n' "$vivi_states" | grep -v '^$' | tail -1 || true) +latest=$(printf '%s\n' "$themis_states" | grep -v '^$' | tail -1 || true) if [ "$latest" != "CHANGES_REQUESTED" ]; then - echo "vivi latest verdict=${latest:-none} (not CHANGES_REQUESTED); skip" + echo "themis latest verdict=${latest:-none} (not CHANGES_REQUESTED); skip" exit 0 fi -rounds=$(printf '%s\n' "$vivi_states" | grep -c CHANGES_REQUESTED || true) +rounds=$(printf '%s\n' "$themis_states" | grep -c CHANGES_REQUESTED || true) if [ "$rounds" -ge "$MAX_REVISE_ROUNDS" ]; then echo "revise rounds=$rounds ≥ cap=$MAX_REVISE_ROUNDS — escalating PR #$PR to a human" gh label create needs-human --color B60205 \ --description "Auto-agent loop paused; needs a human" 2>/dev/null || true gh pr edit "$PR" --add-label needs-human >/dev/null 2>&1 || true - gh pr comment "$PR" --body "🤖 vivi has requested changes ${rounds}× — at the auto-revise cap (\`${MAX_REVISE_ROUNDS}\`). Pausing the wiwi ↔ vivi loop and handing off to a human. To resume an automated pass, remove the \`needs-human\` label and re-run the \`pr-revise\` workflow for this PR." + gh pr comment "$PR" --body "🤖 themis has requested changes ${rounds}× — at the auto-revise cap (\`${MAX_REVISE_ROUNDS}\`). Pausing the hephaestus ↔ themis loop and handing off to a human. To resume an automated pass, remove the \`needs-human\` label and re-run the \`pr-revise\` workflow for this PR." exit 0 fi -echo "vivi REQUEST_CHANGES on auto-agent PR #$PR (round $rounds/$MAX_REVISE_ROUNDS) → dispatching pr-revise" +echo "themis REQUEST_CHANGES on auto-agent PR #$PR (round $rounds/$MAX_REVISE_ROUNDS) → dispatching pr-revise" gh workflow run pr-revise.yml -f pr_number="$PR" diff --git a/scripts/agent-bot/run_wiwi.sh b/scripts/agent-bot/run_hephaestus.sh similarity index 65% rename from scripts/agent-bot/run_wiwi.sh rename to scripts/agent-bot/run_hephaestus.sh index 1feb81f..65817b5 100755 --- a/scripts/agent-bot/run_wiwi.sh +++ b/scripts/agent-bot/run_hephaestus.sh @@ -4,11 +4,11 @@ # gating happens downstream in the review workflow. set -euo pipefail -# Load .agent-ops.json → AGENT_OPS_* (default branch, build cmd, labels, name). +# Load .olympus.json → OLYMPUS_* (default branch, build cmd, labels, name). # shellcheck source=scripts/lib/config.sh source "$(cd "$(dirname "${BASH_SOURCE[0]}")/../lib" && pwd)/config.sh" -agent_ops_load_config -BASE="$AGENT_OPS_DEFAULT_BRANCH" +olympus_load_config +BASE="$OLYMPUS_DEFAULT_BRANCH" # Auto-merge allowlist (GitHub logins). Sourced from the AUTO_MERGE_TEAM env # (CSV or whitespace-separated), injected from a repo secret by the workflow @@ -25,23 +25,23 @@ is_team_member() { # same issue don't collide with leftover branches from prior attempts. STAMP=$(date -u +%Y%m%d-%H%M%S) BRANCH="agent/dev/issue-${ISSUE_NUMBER}-${STAMP}" -git config user.email "agent-bot@noreply.local" -git config user.name "$AGENT_OPS_DEV_AGENT_NAME" +git config user.email "olympus-bot@noreply.local" +git config user.name "$OLYMPUS_DEV_AGENT_NAME" git fetch origin "$BASE" git checkout -B "$BRANCH" "origin/$BASE" PROMPT=$(mktemp) cat > "$PROMPT" <${AGENT_OPS_MAX_LOC} LOC or cross-cutting), - STOP, leave a note in /tmp/wiwi-abort.txt explaining why, and exit non-zero. + task is larger than expected (>${OLYMPUS_MAX_LOC} LOC or cross-cutting), + STOP, leave a note in /tmp/hephaestus-abort.txt explaining why, and exit non-zero. - Add a deterministic test for the change (unit / integration / a tiny fixture). Don't claim done without one. - After edits, run the project build + test command: - \`${AGENT_OPS_BUILD_CMD:-see the repo CONTRIBUTING docs}\` — it must be green + \`${OLYMPUS_BUILD_CMD:-see the repo CONTRIBUTING docs}\` — it must be green before you stop. - Do NOT add new dependencies, new secrets, new network calls. - Do NOT modify CI workflows, branch protection, or this script. @@ -53,7 +53,7 @@ You are **wiwi**, the dev agent. Implement the change requested by issue small — but at least one commit MUST exist before you exit. - The driver script does have a defensive fallback that will auto-commit anything you left uncommitted, but DO NOT rely on it — - the fallback produces a single "wiwi: auto-commit" message that's + the fallback produces a single "hephaestus: auto-commit" message that's useless to reviewers compared to your own intentional commit messages. - **You may be a RESUMED run.** If \`git log origin/${BASE}..HEAD\` shows @@ -64,10 +64,10 @@ You are **wiwi**, the dev agent. Implement the change requested by issue verify the partial state makes sense (read the diffs, build, run tests); then continue toward the issue's acceptance criteria. If the existing state is incoherent and you cannot make sense of it, - STOP and write to /tmp/wiwi-abort.txt explaining why so a human + STOP and write to /tmp/hephaestus-abort.txt explaining why so a human can intervene. -When done, write a brief summary to /tmp/wiwi-summary.md (Markdown) for +When done, write a brief summary to /tmp/hephaestus-summary.md (Markdown) for the PR body. End it with the literal line: Closes #${ISSUE_NUMBER} @@ -76,26 +76,26 @@ Issue title: ${ISSUE_TITLE} EOF # Run the configured agent harness (default: claude) on the implement prompt, -# STREAMING its output to BOTH the workflow log (stdout) and /tmp/wiwi-run.log — +# STREAMING its output to BOTH the workflow log (stdout) and /tmp/hephaestus-run.log — # so a developer watching `gh run watch` sees each tool call land in near -# real-time (a silent `> log 2>&1` once hid whether wiwi was progressing or +# real-time (a silent `> log 2>&1` once hid whether hephaestus was progressing or # hung). agent-harness.sh sources litellm-wait.sh and owns the gateway pre-flight # wait + retry-on-gateway-down loop (it re-runs from scratch; the prompt above # tells the agent how to resume from the partial on-disk state). Which CLI runs -# is .agent-ops.json's harness.kind. +# is .olympus.json's harness.kind. # shellcheck source=../lib/agent-harness.sh source "$(cd "$(dirname "${BASH_SOURCE[0]}")/../lib" && pwd)/agent-harness.sh" claude_exit=0 -agent_run --profile implement --prompt "$PROMPT" --stream /tmp/wiwi-run.log --label wiwi || claude_exit=$? +agent_run --profile implement --prompt "$PROMPT" --stream /tmp/hephaestus-run.log --label hephaestus || claude_exit=$? if [ "$claude_exit" != "0" ]; then - echo "wiwi run failed (claude exit=$claude_exit; see /tmp/wiwi-run.log)" >&2 - gh issue comment "$ISSUE_NUMBER" --body "🤖 wiwi could not complete this task. See workflow log." + echo "hephaestus run failed (claude exit=$claude_exit; see /tmp/hephaestus-run.log)" >&2 + gh issue comment "$ISSUE_NUMBER" --body "🤖 hephaestus could not complete this task. See workflow log." exit 1 fi -if [ -f /tmp/wiwi-abort.txt ]; then - gh issue comment "$ISSUE_NUMBER" --body "🤖 wiwi aborted: $(cat /tmp/wiwi-abort.txt)" +if [ -f /tmp/hephaestus-abort.txt ]; then + gh issue comment "$ISSUE_NUMBER" --body "🤖 hephaestus aborted: $(cat /tmp/hephaestus-abort.txt)" exit 0 fi @@ -105,19 +105,19 @@ fi # rebrand because the prompt said "Commit in logical chunks" and # claude interpreted that as something the driver would handle. Auto- # commit message is intentionally terse — reviewer can rewrite the -# message after-the-fact if needed, or wiwi can re-run with the +# message after-the-fact if needed, or hephaestus can re-run with the # tightened prompt. if ! git diff --quiet || ! git diff --cached --quiet || \ [ -n "$(git ls-files --others --exclude-standard)" ]; then - echo "wiwi: detected uncommitted edits at end of run; auto-committing as fallback" >&2 + echo "hephaestus: detected uncommitted edits at end of run; auto-committing as fallback" >&2 git add -A - git commit -m "wiwi: auto-commit uncommitted edits for #${ISSUE_NUMBER}" \ - -m "Generated by run_wiwi.sh fallback. claude finished without committing; this commit captures the working-tree state so the PR isn't empty. Reviewer: consider asking wiwi to re-run with explicit commit guidance if the diff is large or unclear." + git commit -m "hephaestus: auto-commit uncommitted edits for #${ISSUE_NUMBER}" \ + -m "Generated by run_hephaestus.sh fallback. claude finished without committing; this commit captures the working-tree state so the PR isn't empty. Reviewer: consider asking hephaestus to re-run with explicit commit guidance if the diff is large or unclear." fi # Sanity: must have produced commits. if [ "$(git rev-list --count "origin/$BASE..HEAD")" = "0" ]; then - gh issue comment "$ISSUE_NUMBER" --body "🤖 wiwi finished without any commit OR uncommitted edits; nothing to PR." + gh issue comment "$ISSUE_NUMBER" --body "🤖 hephaestus finished without any commit OR uncommitted edits; nothing to PR." exit 0 fi @@ -125,12 +125,12 @@ git push -u origin "$BRANCH" BODY_FILE=$(mktemp) { - cat /tmp/wiwi-summary.md 2>/dev/null || echo "(wiwi did not write a summary)" + cat /tmp/hephaestus-summary.md 2>/dev/null || echo "(hephaestus did not write a summary)" echo echo "---" - echo "🤖 Implemented by **wiwi** • issue author: @${ISSUE_AUTHOR}" + echo "🤖 Implemented by **${OLYMPUS_DEV_AGENT_NAME}** • issue author: @${ISSUE_AUTHOR}" if is_team_member "$ISSUE_AUTHOR"; then - echo "Eligible for auto-merge on vivi APPROVE." + echo "Eligible for auto-merge on ${OLYMPUS_REVIEW_BOT_LOGIN} APPROVE." fi } > "$BODY_FILE" @@ -140,4 +140,4 @@ gh pr create \ --head "$BRANCH" \ --title "${ISSUE_TITLE}" \ --body-file "$BODY_FILE" \ - --label "$AGENT_OPS_LABEL_AUTO_AGENT" + --label "$OLYMPUS_LABEL_AUTO_AGENT" diff --git a/scripts/agent-bot/run_revise.sh b/scripts/agent-bot/run_revise.sh index c3eb275..16f3450 100755 --- a/scripts/agent-bot/run_revise.sh +++ b/scripts/agent-bot/run_revise.sh @@ -1,31 +1,31 @@ #!/usr/bin/env bash -# wiwi REVISION pass. Dispatched (via pr-revise.yml) when vivi posts a -# CHANGES_REQUESTED review on a wiwi PR (label `auto-agent`). Runs inside a -# checkout of the PR head branch, feeds vivi's blocking feedback + the diff +# hephaestus REVISION pass. Dispatched (via pr-revise.yml) when themis posts a +# CHANGES_REQUESTED review on a hephaestus PR (label `auto-agent`). Runs inside a +# checkout of the PR head branch, feeds themis's blocking feedback + the diff # to claude, ensures the build is green, then commits + pushes back — which -# re-triggers ci → pr-review (vivi re-review). Mirrors run_wiwi.sh's +# re-triggers ci → pr-review (themis re-review). Mirrors run_hephaestus.sh's # LiteLLM-wait + claude-retry plumbing so the two agents behave identically # under a flaky backend. set -euo pipefail HERE=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) -# Load .agent-ops.json → AGENT_OPS_* (review-bot login, dev-agent name). +# Load .olympus.json → OLYMPUS_* (review-bot login, dev-agent name). # shellcheck source=scripts/lib/config.sh source "$HERE/../lib/config.sh" -agent_ops_load_config +olympus_load_config PR="${PR_NUMBER:?PR_NUMBER required}" REPO="${GITHUB_REPOSITORY:?GITHUB_REPOSITORY required}" git config user.email "agent-bot@noreply.local" -git config user.name "$AGENT_OPS_DEV_AGENT_NAME" +git config user.name "$OLYMPUS_DEV_AGENT_NAME" -# --- gather vivi's feedback + the diff for the prompt --------------------- -# Latest CHANGES_REQUESTED review body. Identify vivi the same way -# auto_merge.sh does (author login OR the vivi footer), so attribution +# --- gather themis's feedback + the diff for the prompt --------------------- +# Latest CHANGES_REQUESTED review body. Identify themis the same way +# auto_merge.sh does (author login OR the themis footer), so attribution # quirks between bot accounts don't drop the review. REVIEW_BODY=$(gh api "repos/$REPO/pulls/$PR/reviews" --jq ' [ .[] - | select(.user.login==env.AGENT_OPS_REVIEW_BOT_LOGIN or ((.body // "") | contains(env.AGENT_OPS_REVIEW_BOT_LOGIN))) + | select(.user.login==env.OLYMPUS_REVIEW_BOT_LOGIN or ((.body // "") | contains(env.OLYMPUS_REVIEW_BOT_LOGIN))) | select(.state=="CHANGES_REQUESTED") ] | last | .body // ""' 2>/dev/null || true) @@ -44,11 +44,11 @@ fi PROMPT=$(mktemp) cat > "$PROMPT" <&2 - gh pr comment "$PR" --body "🤖 wiwi could not complete the revision (see workflow log). Leaving vivi's review for a human." + echo "hephaestus revise failed (claude exit=$claude_exit; see /tmp/hephaestus-revise-run.log)" >&2 + gh pr comment "$PR" --body "🤖 hephaestus could not complete the revision (see workflow log). Leaving themis's review for a human." exit 1 fi -if [ -f /tmp/wiwi-revise-abort.txt ]; then - gh pr comment "$PR" --body "🤖 wiwi paused the revision: $(cat /tmp/wiwi-revise-abort.txt)" +if [ -f /tmp/hephaestus-revise-abort.txt ]; then + gh pr comment "$PR" --body "🤖 hephaestus paused the revision: $(cat /tmp/hephaestus-revise-abort.txt)" exit 0 fi @@ -103,9 +103,9 @@ fi # run isn't lost to workspace cleanup. if ! git diff --quiet || ! git diff --cached --quiet || \ [ -n "$(git ls-files --others --exclude-standard)" ]; then - echo "wiwi revise: auto-committing leftover edits (fallback)" >&2 + echo "hephaestus revise: auto-committing leftover edits (fallback)" >&2 git add -A - git commit -m "wiwi: revision for PR #${PR} (auto-commit fallback)" \ + git commit -m "hephaestus: revision for PR #${PR} (auto-commit fallback)" \ -m "claude finished the revision without committing; this captures the working-tree state so the review feedback isn't lost. Reviewer: squash/reword as needed." fi @@ -114,10 +114,10 @@ fi # its upstream (origin/); equal ⇒ no new commit this pass. upstream=$(git rev-parse '@{u}' 2>/dev/null || echo none) if [ "$(git rev-parse HEAD)" = "$upstream" ]; then - echo "wiwi revise: no new commits; nothing to push" >&2 - gh pr comment "$PR" --body "🤖 wiwi made no changes on this revision pass — vivi's feedback may need a human. Pausing the loop." + echo "hephaestus revise: no new commits; nothing to push" >&2 + gh pr comment "$PR" --body "🤖 hephaestus made no changes on this revision pass — themis's feedback may need a human. Pausing the loop." exit 0 fi git push -gh pr comment "$PR" --body "🤖 **wiwi** pushed a revision addressing vivi's review. CI + re-review run automatically." +gh pr comment "$PR" --body "🤖 **hephaestus** pushed a revision addressing themis's review. CI + re-review run automatically." diff --git a/scripts/agent-bot/run_triage.sh b/scripts/agent-bot/run_triage.sh index 9f353e6..7850d08 100755 --- a/scripts/agent-bot/run_triage.sh +++ b/scripts/agent-bot/run_triage.sh @@ -49,9 +49,9 @@ I had a look into it, but before I pick it up I want to be sure I'd be fixing ex if [ "$verdict" = "needs_info" ] || [ "$verdict" = "skip" ]; then # Label names come from config (set in the live flow); tests run this # without config loaded, so fall back to the defaults. - local l_try="${AGENT_OPS_LABEL_TRY:-agent:try}" - local l_skip="${AGENT_OPS_LABEL_SKIP:-agent:skip}" - local l_assess="${AGENT_OPS_LABEL_ASSESS:-agent:assess}" + local l_try="${OLYMPUS_LABEL_TRY:-agent:try}" + local l_skip="${OLYMPUS_LABEL_SKIP:-agent:skip}" + local l_assess="${OLYMPUS_LABEL_ASSESS:-agent:assess}" cat <Maintainer controls @@ -63,7 +63,7 @@ FOOT # Invisible breadcrumb (no rendered output) so re-triage / tooling can tell a # triage-authored comment from a human one without polluting the voice. - printf '\n\n' "$verdict" + printf '\n\n' "$verdict" } # Sourced by tests with TRIAGE_LIB_ONLY=1: load the helpers above and stop @@ -73,27 +73,27 @@ if [ "${TRIAGE_LIB_ONLY:-}" = "1" ]; then return 0 2>/dev/null || exit 0 fi -# Load the consumer repo's .agent-ops.json → AGENT_OPS_* (gates, labels, +# Load the consumer repo's .olympus.json → OLYMPUS_* (gates, labels, # language, agent names). Defaults keep the original behavior if it's absent. # shellcheck source=scripts/lib/config.sh source "$(cd "$(dirname "${BASH_SOURCE[0]}")/../lib" && pwd)/config.sh" -agent_ops_load_config +olympus_load_config # Per-repo language directive for the reply. "auto" (default) → match the # reporter's language; a fixed code (e.g. "en", "zh") → always reply in it. -if [ "${AGENT_OPS_LANGUAGE:-auto}" = "auto" ]; then +if [ "${OLYMPUS_LANGUAGE:-auto}" = "auto" ]; then LANG_DIRECTIVE='write the reply in the SAME language the reporter used in the issue (title + body). A Chinese issue gets a Chinese reply; Japanese → Japanese; Spanish → Spanish; and so on. Match them naturally and fluently. Fall back to English only if the issue language is genuinely unclear.' else - LANG_DIRECTIVE="always write the reply in this language: ${AGENT_OPS_LANGUAGE}." + LANG_DIRECTIVE="always write the reply in this language: ${OLYMPUS_LANGUAGE}." fi # --------------------------------------------------------------------------- # Auto-path guards. On the auto path (TRIGGER_KIND=opened) we skip two classes # of issue so they never get auto-routed into the autonomous dev agent: -# - prod incidents filed by mara (incident/mara labels) — an operator routes +# - prod incidents filed by argus (incident/argus labels) — an operator routes # these in deliberately via `agent:assess`. # - issues already in the pipeline or muted (agent:try / agent:skip / # auto-agent) — avoids duplicate triage and re-trigger loops. @@ -103,12 +103,12 @@ fi if [ "${TRIGGER_KIND:-opened}" = "opened" ]; then LABELS=$(gh issue view "$ISSUE_NUMBER" --json labels --jq '[.labels[].name] | join(",")' 2>/dev/null || echo "") case ",$LABELS," in - *,incident,*|*,mara,*) + *,incident,*|*,argus,*) echo "auto-triage skipped: prod-incident issue #$ISSUE_NUMBER; add agent:assess to route it manually" exit 0 ;; esac case ",$LABELS," in - *,"$AGENT_OPS_LABEL_TRY",*|*,"$AGENT_OPS_LABEL_SKIP",*|*,"$AGENT_OPS_LABEL_AUTO_AGENT",*) + *,"$OLYMPUS_LABEL_TRY",*|*,"$OLYMPUS_LABEL_SKIP",*|*,"$OLYMPUS_LABEL_AUTO_AGENT",*) echo "auto-triage skipped: issue #$ISSUE_NUMBER is already queued/muted/has-PR" exit 0 ;; esac @@ -142,10 +142,10 @@ JOB B — DECIDE if the autonomous dev agent can implement it UNATTENDED Verdict \`do\` ONLY when ALL of these hold: 1. Concrete, actionable description AND explicit acceptance criteria (you can list 2+ objectively checkable assertions). - 2. Estimated diff < ${AGENT_OPS_MAX_LOC} LOC across < ${AGENT_OPS_MAX_FILES} files. - 3. Contained: ${AGENT_OPS_CONTAINED} — not cross-cutting architecture work. + 2. Estimated diff < ${OLYMPUS_MAX_LOC} LOC across < ${OLYMPUS_MAX_FILES} files. + 3. Contained: ${OLYMPUS_CONTAINED} — not cross-cutting architecture work. 4. No new runtime dependency, no new secret, no new external network call. - 5. Ships with a deterministic test (${AGENT_OPS_TEST_HINT}) in the same PR — + 5. Ships with a deterministic test (${OLYMPUS_TEST_HINT}) in the same PR — not "needs manual QA". If gate 1 fails → \`needs_info\`. If gates 2–5 fail → \`skip\`. When in doubt, be strict → \`needs_info\`. @@ -202,7 +202,7 @@ EOF # Run the configured agent harness (default: claude) on the triage prompt. # agent-harness.sh sources litellm-wait.sh and owns the gateway pre-flight wait -# + the retry-on-gateway-down loop; which CLI runs is .agent-ops.json's +# + the retry-on-gateway-down loop; which CLI runs is .olympus.json's # harness.kind. Triage is idempotent (input = the issue body) so a from-scratch # retry is safe. # shellcheck source=../lib/agent-harness.sh @@ -262,10 +262,10 @@ case "$VERDICT" in # still label (with GITHUB_TOKEN) but the dev agent won't auto-start; # an operator can re-toggle the label by hand. if [ -n "${AGENT_GH_TOKEN:-}" ]; then - GH_TOKEN="$AGENT_GH_TOKEN" gh issue edit "$ISSUE_NUMBER" --add-label "$AGENT_OPS_LABEL_TRY" + GH_TOKEN="$AGENT_GH_TOKEN" gh issue edit "$ISSUE_NUMBER" --add-label "$OLYMPUS_LABEL_TRY" else - echo "warning: AGENT_GH_TOKEN unset; labeling under GITHUB_TOKEN — ${AGENT_OPS_DEV_AGENT_NAME} will NOT auto-start" >&2 - gh issue edit "$ISSUE_NUMBER" --add-label "$AGENT_OPS_LABEL_TRY" + echo "warning: AGENT_GH_TOKEN unset; labeling under GITHUB_TOKEN — ${OLYMPUS_DEV_AGENT_NAME} will NOT auto-start" >&2 + gh issue edit "$ISSUE_NUMBER" --add-label "$OLYMPUS_LABEL_TRY" fi compose_comment_body "$REPLY" "do" 0 > "$TMP_BODY" gh issue comment "$ISSUE_NUMBER" --body-file "$TMP_BODY" diff --git a/scripts/agent-bot/tests/test_mara.py b/scripts/agent-bot/tests/test_argus.py similarity index 74% rename from scripts/agent-bot/tests/test_mara.py rename to scripts/agent-bot/tests/test_argus.py index f5803c2..e8cdfba 100644 --- a/scripts/agent-bot/tests/test_mara.py +++ b/scripts/agent-bot/tests/test_argus.py @@ -1,12 +1,12 @@ #!/usr/bin/env python3 -"""Integration test for mara.sh's confirm-debounce (option A) — stdlib only, so +"""Integration test for argus.sh's confirm-debounce (option A) — stdlib only, so CI can run it without installing anything: - python3 scripts/agent-bot/tests/test_mara.py + python3 scripts/agent-bot/tests/test_argus.py Spins up a stub `/api/health` server whose responses follow a per-test script (one entry per request; the last entry repeats once the script is exhausted), -then runs mara.sh in DRY_RUN with CONFIRM_DELAY=0 and asserts whether it would +then runs argus.sh in DRY_RUN with CONFIRM_DELAY=0 and asserts whether it would file. The point under test: a single failed poll that recovers on a later poll (a deploy/restart blip) must NOT open an incident; only an all-polls-failed run does. @@ -22,7 +22,7 @@ import threading HERE = os.path.dirname(os.path.abspath(__file__)) -MARA = os.path.join(os.path.dirname(HERE), "mara.sh") +MARA = os.path.join(os.path.dirname(HERE), "argus.sh") # Response script the handler walks through. Each entry is one of: # {"code": 503} → down (non-200) @@ -63,24 +63,24 @@ def _set_script(script): _reqs[0] = 0 -def _run_mara(script, **env_over): - """Set the stub script, run mara.sh (DRY_RUN), return combined output.""" +def _run_argus(script, **env_over): + """Set the stub script, run argus.sh (DRY_RUN), return combined output.""" _set_script(script) - tmp = tempfile.mkdtemp(prefix="mara-test-") + tmp = tempfile.mkdtemp(prefix="argus-test-") env = dict(os.environ) env.update( { - "MARA_HEALTH_URL": f"http://127.0.0.1:{PORT}/api/health", - "MARA_DRY_RUN": "1", - "MARA_CONFIRM_DELAY_SECS": "0", - "MARA_CONFIRM_POLLS": "3", - "MARA_STATE_DIR": tmp, - "MARA_LOG_HOST": "", # no ssh log-context fetch - "MARA_REPO": "Example/repo", # now required - "MARA_SERVICE_NAME": "heron", # keep the readable name in asserts + "ARGUS_HEALTH_URL": f"http://127.0.0.1:{PORT}/api/health", + "ARGUS_DRY_RUN": "1", + "ARGUS_CONFIRM_DELAY_SECS": "0", + "ARGUS_CONFIRM_POLLS": "3", + "ARGUS_STATE_DIR": tmp, + "ARGUS_LOG_HOST": "", # no ssh log-context fetch + "ARGUS_REPO": "Example/repo", # now required + "ARGUS_SERVICE_NAME": "heron", # keep the readable name in asserts # Drive the optional "parked" detection against the stub's shape. - "MARA_READY_JQ": ".data.pipelines[0].running", - "MARA_READY_EXPECT": "true", + "ARGUS_READY_JQ": ".data.pipelines[0].running", + "ARGUS_READY_EXPECT": "true", } ) env.update(env_over) @@ -93,21 +93,21 @@ def _run_mara(script, **env_over): # ---- tests ---------------------------------------------------------------- def test_healthy_no_file(): - out = _run_mara([{"code": 200, "running": True}]) + out = _run_argus([{"code": 200, "running": True}]) assert "prod heron OK" in out, out assert "would file" not in out, out def test_transient_down_then_up_does_not_file(): # poll1 = down (a restart blip), poll2 = healthy → transient, no incident. - out = _run_mara([{"code": 503}, {"code": 200, "running": True}]) + out = _run_argus([{"code": 503}, {"code": 200, "running": True}]) assert "transient" in out and "not filing" in out, out assert "would file" not in out, out def test_sustained_down_files(): # every poll fails → real outage → would file prod-down. - out = _run_mara([{"code": 503}]) + out = _run_argus([{"code": 503}]) assert "would file" in out, out assert "prod-down" in out, out assert "3/3 consecutive polls" in out, out @@ -115,20 +115,20 @@ def test_sustained_down_files(): def test_transient_parked_then_running_does_not_file(): # pipeline briefly running=false during deploy, then resumes → no incident. - out = _run_mara([{"code": 200, "running": False}, {"code": 200, "running": True}]) + out = _run_argus([{"code": 200, "running": False}, {"code": 200, "running": True}]) assert "transient" in out and "not filing" in out, out assert "would file" not in out, out def test_sustained_parked_files(): - out = _run_mara([{"code": 200, "running": False}]) + out = _run_argus([{"code": 200, "running": False}]) assert "would file" in out, out assert "prod-parked" in out, out def test_confirm_polls_1_files_on_first_failure(): # CONFIRM_POLLS=1 disables debounce → first failure files immediately. - out = _run_mara([{"code": 503}, {"code": 200, "running": True}], MARA_CONFIRM_POLLS="1") + out = _run_argus([{"code": 503}, {"code": 200, "running": True}], ARGUS_CONFIRM_POLLS="1") assert "would file" in out, out assert "prod-down" in out, out diff --git a/scripts/agent-bot/tests/test_config.py b/scripts/agent-bot/tests/test_config.py index fd2e025..83b4749 100644 --- a/scripts/agent-bot/tests/test_config.py +++ b/scripts/agent-bot/tests/test_config.py @@ -1,12 +1,12 @@ #!/usr/bin/env python3 -"""Unit test for config.sh — the loader that maps a consumer's .agent-ops.json -onto the AGENT_OPS_* / MARA_* env every agent script reads. Stdlib only. +"""Unit test for config.sh — the loader that maps a consumer's .olympus.json +onto the OLYMPUS_* / ARGUS_* env every agent script reads. Stdlib only. python3 scripts/agent-bot/tests/test_config.py Asserts: (1) values come through from a config file, (2) absent file → the built-in defaults (back-compat), (3) an already-exported env var overrides the -file, (4) observer.* maps onto the MARA_* names mara.sh consumes. +file, (4) observer.* maps onto the ARGUS_* names argus.sh consumes. Exit 0 = all pass, 1 = a failure. """ @@ -22,20 +22,20 @@ def load(config_obj=None, **env_over): """Source config.sh against an optional config file; return the resulting - AGENT_OPS_* / MARA_* env as a dict.""" + OLYMPUS_* / ARGUS_* env as a dict.""" env = dict(os.environ) tmp = None if config_obj is not None: fd, tmp = tempfile.mkstemp(suffix=".json") with os.fdopen(fd, "w") as f: json.dump(config_obj, f) - env["AGENT_OPS_CONFIG"] = tmp + env["OLYMPUS_CONFIG"] = tmp else: - env["AGENT_OPS_CONFIG"] = "/definitely/nonexistent.json" + env["OLYMPUS_CONFIG"] = "/definitely/nonexistent.json" env.update(env_over) script = ( - f'source "{CONFIG_SH}"; agent_ops_load_config 2>/dev/null; ' - "env | grep -E \"^(AGENT_OPS_|MARA_)\"" + f'source "{CONFIG_SH}"; olympus_load_config 2>/dev/null; ' + "env | grep -E \"^(OLYMPUS_|ARGUS_)\"" ) r = subprocess.run(["bash", "-c", script], capture_output=True, text=True, env=env, timeout=30) if tmp: @@ -52,35 +52,35 @@ def load(config_obj=None, **env_over): def test_values_from_file(): cfg = load({"triage": {"gates": {"max_loc": 150, "contained_areas": ["pkg/", "docs/"]}}}) - assert cfg["AGENT_OPS_MAX_LOC"] == "150", cfg.get("AGENT_OPS_MAX_LOC") - assert cfg["AGENT_OPS_CONTAINED"] == "pkg/, docs/", cfg.get("AGENT_OPS_CONTAINED") + assert cfg["OLYMPUS_MAX_LOC"] == "150", cfg.get("OLYMPUS_MAX_LOC") + assert cfg["OLYMPUS_CONTAINED"] == "pkg/, docs/", cfg.get("OLYMPUS_CONTAINED") def test_defaults_when_no_file(): cfg = load(None) - assert cfg["AGENT_OPS_MAX_LOC"] == "300", cfg - assert cfg["AGENT_OPS_LABEL_TRY"] == "agent:try", cfg - assert cfg["AGENT_OPS_REVIEW_BOT_LOGIN"] == "vivi", cfg + assert cfg["OLYMPUS_MAX_LOC"] == "300", cfg + assert cfg["OLYMPUS_LABEL_TRY"] == "agent:try", cfg + assert cfg["OLYMPUS_REVIEW_BOT_LOGIN"] == "themis", cfg def test_env_overrides_file(): - cfg = load({"triage": {"gates": {"max_loc": 150}}}, AGENT_OPS_MAX_LOC="999") - assert cfg["AGENT_OPS_MAX_LOC"] == "999", cfg.get("AGENT_OPS_MAX_LOC") + cfg = load({"triage": {"gates": {"max_loc": 150}}}, OLYMPUS_MAX_LOC="999") + assert cfg["OLYMPUS_MAX_LOC"] == "999", cfg.get("OLYMPUS_MAX_LOC") -def test_observer_maps_to_mara_env(): +def test_observer_maps_to_argus_env(): cfg = load({"observer": {"service_name": "svc-x", "repo": "Acme/svc", "readiness": {"jq": ".status", "expect": "ok"}}}) - assert cfg["MARA_SERVICE_NAME"] == "svc-x", cfg - assert cfg["MARA_REPO"] == "Acme/svc", cfg - assert cfg["MARA_READY_JQ"] == ".status", cfg - assert cfg["MARA_READY_EXPECT"] == "ok", cfg + assert cfg["ARGUS_SERVICE_NAME"] == "svc-x", cfg + assert cfg["ARGUS_REPO"] == "Acme/svc", cfg + assert cfg["ARGUS_READY_JQ"] == ".status", cfg + assert cfg["ARGUS_READY_EXPECT"] == "ok", cfg def test_custom_labels_and_bot(): cfg = load({"agents": {"review_bot_login": "rex"}, "labels": {"try": "bot:go"}}) - assert cfg["AGENT_OPS_REVIEW_BOT_LOGIN"] == "rex", cfg - assert cfg["AGENT_OPS_LABEL_TRY"] == "bot:go", cfg + assert cfg["OLYMPUS_REVIEW_BOT_LOGIN"] == "rex", cfg + assert cfg["OLYMPUS_LABEL_TRY"] == "bot:go", cfg def main(): diff --git a/scripts/agent-bot/tests/test_harness.py b/scripts/agent-bot/tests/test_harness.py index b3bcabd..6cebc8e 100644 --- a/scripts/agent-bot/tests/test_harness.py +++ b/scripts/agent-bot/tests/test_harness.py @@ -34,8 +34,8 @@ def dry_run(args, config=None): env["AGENT_HARNESS_DRYRUN"] = "1" pre = "" if config: - env["AGENT_OPS_CONFIG"] = config - pre = f'source "{CONFIG}"; agent_ops_load_config 2>/dev/null; ' + env["OLYMPUS_CONFIG"] = config + pre = f'source "{CONFIG}"; olympus_load_config 2>/dev/null; ' script = pre + f'source "{HARNESS}"; agent_run ' + args r = subprocess.run(["bash", "-c", script], capture_output=True, text=True, env=env) return r.stdout.strip() diff --git a/scripts/agent-bot/tests/test_triage.py b/scripts/agent-bot/tests/test_triage.py index 8320072..e67808c 100644 --- a/scripts/agent-bot/tests/test_triage.py +++ b/scripts/agent-bot/tests/test_triage.py @@ -56,7 +56,7 @@ def test_do_posts_reply_verbatim_no_robotic_header(): assert "Gate" not in out and "gate" not in out, out # `do` is auto-queued → no maintainer-controls block assert "Maintainer controls" not in out, out - assert "" in out, out + assert "" in out, out def test_needs_info_keeps_reply_and_adds_collapsed_controls(): @@ -65,7 +65,7 @@ def test_needs_info_keeps_reply_and_adds_collapsed_controls(): assert reply in out, out assert "
Maintainer controls" in out, out assert "`agent:try`" in out and "`agent:skip`" in out, out - assert "" in out, out + assert "" in out, out # still no robotic framing assert "Triage:" not in out, out @@ -75,7 +75,7 @@ def test_skip_is_not_a_brushoff_reply_preserved(): out = compose(reply, "skip") assert reply in out, out assert "Maintainer controls" in out, out - assert "" in out, out + assert "" in out, out def test_downgrade_drops_do_reply_for_honest_fallback(): @@ -85,7 +85,7 @@ def test_downgrade_drops_do_reply_for_honest_fallback(): assert do_reply not in out, "misleading do-reply must be dropped on downgrade\n" + out assert "acceptance criteria" in out, out assert "Maintainer controls" in out, out - assert "" in out, out + assert "" in out, out def test_empty_reply_falls_back(): @@ -97,7 +97,7 @@ def test_empty_reply_falls_back(): def test_marker_tracks_verdict(): for v in ("do", "needs_info", "skip"): out = compose("hi", v) - assert f"" in out, (v, out) + assert f"" in out, (v, out) def main(): diff --git a/scripts/lib/agent-harness.sh b/scripts/lib/agent-harness.sh index 47ca078..bf12395 100644 --- a/scripts/lib/agent-harness.sh +++ b/scripts/lib/agent-harness.sh @@ -4,10 +4,10 @@ # Every agent surface (triage / implement / review / revise) used to invoke the # `claude` CLI inline, each with its own copy of the "wait for the gateway, run, # retry if the gateway died mid-stream" loop. This centralises that into ONE -# function so the harness CLI becomes a `.agent-ops.json` choice: +# function so the harness CLI becomes a `.olympus.json` choice: # # harness.kind = "claude" (default — byte-compatible with the old inline calls) -# | "custom" (run AGENT_OPS_HARNESS_CMD, a command template) +# | "custom" (run OLYMPUS_HARNESS_CMD, a command template) # # Public entry point: # agent_run --profile {investigate|implement|review} --prompt \ @@ -16,12 +16,12 @@ # [--output-format F] [--permission-mode M] [--label NAME] # # Returns the harness exit code. Encapsulates: the pre-flight gateway wait + the -# retry-on-gateway-down loop (both no-ops when AGENT_OPS_HEALTH_PROBE != "true"). +# retry-on-gateway-down loop (both no-ops when OLYMPUS_HEALTH_PROBE != "true"). # # Config consumed (exported by config.sh; safe defaults if unset): -# AGENT_OPS_HARNESS claude | custom (default claude) -# AGENT_OPS_HARNESS_CMD custom command template (custom only) -# AGENT_OPS_HEALTH_PROBE true | false (default true) +# OLYMPUS_HARNESS claude | custom (default claude) +# OLYMPUS_HARNESS_CMD custom command template (custom only) +# OLYMPUS_HEALTH_PROBE true | false (default true) # ANTHROPIC_MODEL model id for the claude harness / {model} placeholder # # Dry run: AGENT_HARNESS_DRYRUN=1 prints the command that WOULD run (one line) @@ -72,7 +72,7 @@ agent_run() { local model="${ANTHROPIC_MODEL:-claude-3-5-sonnet-20241022}" local tool_set; tool_set="${tools:-$(_agent_profile_tools "$profile")}" local write; write="$(_agent_profile_write "$profile")" - local kind="${AGENT_OPS_HARNESS:-claude}" + local kind="${OLYMPUS_HARNESS:-claude}" # --- build the harness command -------------------------------------------- # For the claude harness we keep a real argv array (no eval). For custom we @@ -80,11 +80,11 @@ agent_run() { local -a claude_cmd=() local custom_cmd="" if [ "$kind" = "custom" ]; then - [ -n "${AGENT_OPS_HARNESS_CMD:-}" ] || { echo "agent_run: harness.kind=custom but AGENT_OPS_HARNESS_CMD is empty" >&2; return 78; } + [ -n "${OLYMPUS_HARNESS_CMD:-}" ] || { echo "agent_run: harness.kind=custom but OLYMPUS_HARNESS_CMD is empty" >&2; return 78; } # custom always writes the agent's output to a file we control; for stream # profiles that's a temp the loop tees to stdout afterwards. local cust_out="${out:-${streamlog:-/dev/stdout}}" - custom_cmd="$AGENT_OPS_HARNESS_CMD" + custom_cmd="$OLYMPUS_HARNESS_CMD" custom_cmd="${custom_cmd//\{model\}/$model}" custom_cmd="${custom_cmd//\{prompt_file\}/$prompt}" custom_cmd="${custom_cmd//\{out\}/$cust_out}" @@ -112,7 +112,7 @@ agent_run() { fi # --- pre-flight: gateway reachable? --------------------------------------- - if [ "${AGENT_OPS_HEALTH_PROBE:-true}" = "true" ]; then + if [ "${OLYMPUS_HEALTH_PROBE:-true}" = "true" ]; then wait_for_litellm || return $? fi @@ -141,7 +141,7 @@ agent_run() { set -e [ "$rc" -eq 0 ] && break - if [ "${AGENT_OPS_HEALTH_PROBE:-true}" != "true" ] || ! litellm_appears_down; then + if [ "${OLYMPUS_HEALTH_PROBE:-true}" != "true" ] || ! litellm_appears_down; then break # failure is not a gateway outage — don't retry fi if [ "$attempt" -ge "$retry_max" ]; then diff --git a/scripts/lib/config.sh b/scripts/lib/config.sh index d5ef9c9..59cdd5b 100644 --- a/scripts/lib/config.sh +++ b/scripts/lib/config.sh @@ -1,17 +1,17 @@ #!/usr/bin/env bash -# config.sh — load a consumer repo's .agent-ops.json into AGENT_OPS_* / agent +# config.sh — load a consumer repo's .olympus.json into OLYMPUS_* / agent # env vars, so the portable agent scripts carry NO repo-specific values. # # The single source of truth for "how this repo wants the agents to behave" -# is the consumer's .agent-ops.json (see schema/agent-ops.schema.json). This +# is the consumer's .olympus.json (see schema/olympus.schema.json). This # loader resolves it with jq (already a hard dependency of every agent script) # — no YAML parser, no new runtime dep. # # Usage (from any agent script): # source "$(dirname "${BASH_SOURCE[0]}")/../lib/config.sh" -# agent_ops_load_config # reads $AGENT_OPS_CONFIG or .agent-ops.json +# olympus_load_config # reads $OLYMPUS_CONFIG or .olympus.json # -# Every value has a default chosen so a repo with NO .agent-ops.json behaves +# Every value has a default chosen so a repo with NO .olympus.json behaves # exactly like heron did before extraction (back-compatible). A consumer # overrides only what differs. # @@ -19,77 +19,77 @@ # over the built-in default. That lets a workflow or a test pin one value # without rewriting the file. -# _aoc_get always succeeds, so the export+command-substitution pattern in -# agent_ops_load_config can't mask a meaningful return value — silence SC2155. +# _olc_get always succeeds, so the export+command-substitution pattern in +# olympus_load_config can't mask a meaningful return value — silence SC2155. # shellcheck disable=SC2155 -# _aoc_get : echo config value or default. +# _olc_get : echo config value or default. # Honors an env override named by the 3rd arg (if that env var is already set, # it wins and the file is not consulted). -_aoc_get() { +_olc_get() { local filter="$1" default="$2" envname="${3:-}" if [ -n "$envname" ]; then local cur="${!envname-__unset__}" if [ "$cur" != "__unset__" ] && [ -n "$cur" ]; then printf '%s' "$cur"; return; fi fi local v="" - if [ -n "${_AOC_FILE:-}" ] && [ -f "$_AOC_FILE" ]; then - v="$(jq -r "$filter // empty" "$_AOC_FILE" 2>/dev/null || true)" + if [ -n "${_OLC_FILE:-}" ] && [ -f "$_OLC_FILE" ]; then + v="$(jq -r "$filter // empty" "$_OLC_FILE" 2>/dev/null || true)" fi printf '%s' "${v:-$default}" } -agent_ops_load_config() { - _AOC_FILE="${AGENT_OPS_CONFIG:-.agent-ops.json}" - if [ ! -f "$_AOC_FILE" ]; then - echo "agent-ops: no ${_AOC_FILE}; using built-in defaults" >&2 - _AOC_FILE="" +olympus_load_config() { + _OLC_FILE="${OLYMPUS_CONFIG:-.olympus.json}" + if [ ! -f "$_OLC_FILE" ]; then + echo "olympus: no ${_OLC_FILE}; using built-in defaults" >&2 + _OLC_FILE="" fi # --- project / branch --- - export AGENT_OPS_DEFAULT_BRANCH="$(_aoc_get '.project.default_branch' 'main' AGENT_OPS_DEFAULT_BRANCH)" + export OLYMPUS_DEFAULT_BRANCH="$(_olc_get '.project.default_branch' 'main' OLYMPUS_DEFAULT_BRANCH)" # --- agent identities --- - export AGENT_OPS_REVIEW_BOT_LOGIN="$(_aoc_get '.agents.review_bot_login' 'vivi' AGENT_OPS_REVIEW_BOT_LOGIN)" - export AGENT_OPS_DEV_AGENT_NAME="$(_aoc_get '.agents.dev_agent_name' 'the dev agent' AGENT_OPS_DEV_AGENT_NAME)" + export OLYMPUS_REVIEW_BOT_LOGIN="$(_olc_get '.agents.review_bot_login' 'themis' OLYMPUS_REVIEW_BOT_LOGIN)" + export OLYMPUS_DEV_AGENT_NAME="$(_olc_get '.agents.dev_agent_name' 'hephaestus' OLYMPUS_DEV_AGENT_NAME)" # --- labels --- - export AGENT_OPS_LABEL_ASSESS="$(_aoc_get '.labels.assess' 'agent:assess' AGENT_OPS_LABEL_ASSESS)" - export AGENT_OPS_LABEL_TRY="$(_aoc_get '.labels.try' 'agent:try' AGENT_OPS_LABEL_TRY)" - export AGENT_OPS_LABEL_SKIP="$(_aoc_get '.labels.skip' 'agent:skip' AGENT_OPS_LABEL_SKIP)" - export AGENT_OPS_LABEL_AUTO_AGENT="$(_aoc_get '.labels.auto_agent' 'auto-agent' AGENT_OPS_LABEL_AUTO_AGENT)" + export OLYMPUS_LABEL_ASSESS="$(_olc_get '.labels.assess' 'agent:assess' OLYMPUS_LABEL_ASSESS)" + export OLYMPUS_LABEL_TRY="$(_olc_get '.labels.try' 'agent:try' OLYMPUS_LABEL_TRY)" + export OLYMPUS_LABEL_SKIP="$(_olc_get '.labels.skip' 'agent:skip' OLYMPUS_LABEL_SKIP)" + export OLYMPUS_LABEL_AUTO_AGENT="$(_olc_get '.labels.auto_agent' 'auto-agent' OLYMPUS_LABEL_AUTO_AGENT)" # --- triage gates --- - export AGENT_OPS_MAX_LOC="$(_aoc_get '.triage.gates.max_loc' '300' AGENT_OPS_MAX_LOC)" - export AGENT_OPS_MAX_FILES="$(_aoc_get '.triage.gates.max_files' '10' AGENT_OPS_MAX_FILES)" - export AGENT_OPS_CONTAINED="$(_aoc_get '(.triage.gates.contained_areas | join(", "))' 'one module, docs, or one workflow' AGENT_OPS_CONTAINED)" - export AGENT_OPS_TEST_HINT="$(_aoc_get '.triage.gates.test_hint' 'a unit / integration test' AGENT_OPS_TEST_HINT)" - export AGENT_OPS_LANGUAGE="$(_aoc_get '.triage.language' 'auto' AGENT_OPS_LANGUAGE)" + export OLYMPUS_MAX_LOC="$(_olc_get '.triage.gates.max_loc' '300' OLYMPUS_MAX_LOC)" + export OLYMPUS_MAX_FILES="$(_olc_get '.triage.gates.max_files' '10' OLYMPUS_MAX_FILES)" + export OLYMPUS_CONTAINED="$(_olc_get '(.triage.gates.contained_areas | join(", "))' 'one module, docs, or one workflow' OLYMPUS_CONTAINED)" + export OLYMPUS_TEST_HINT="$(_olc_get '.triage.gates.test_hint' 'a unit / integration test' OLYMPUS_TEST_HINT)" + export OLYMPUS_LANGUAGE="$(_olc_get '.triage.language' 'auto' OLYMPUS_LANGUAGE)" # --- implement (dev agent) --- - export AGENT_OPS_BUILD_CMD="$(_aoc_get '.implement.build_cmd' '' AGENT_OPS_BUILD_CMD)" + export OLYMPUS_BUILD_CMD="$(_olc_get '.implement.build_cmd' '' OLYMPUS_BUILD_CMD)" # --- model (harness.model wins over top-level .model) --- - export ANTHROPIC_MODEL="$(_aoc_get '(.harness.model // .model)' "${ANTHROPIC_MODEL:-claude-3-5-sonnet-20241022}" ANTHROPIC_MODEL)" + export ANTHROPIC_MODEL="$(_olc_get '(.harness.model // .model)' "${ANTHROPIC_MODEL:-claude-3-5-sonnet-20241022}" ANTHROPIC_MODEL)" # --- harness (which agent CLI drives the surfaces; default claude) --- # Read by scripts/lib/agent-harness.sh's agent_run. kind=custom runs the - # command template AGENT_OPS_HARNESS_CMD ({model}/{prompt_file}/{out}/{tools}/ + # command template OLYMPUS_HARNESS_CMD ({model}/{prompt_file}/{out}/{tools}/ # {write}/{max_turns} placeholders). health_probe=false skips the gateway # /v1/models wait (for harnesses whose endpoint isn't OpenAI-compatible). - export AGENT_OPS_HARNESS="$(_aoc_get '.harness.kind' 'claude' AGENT_OPS_HARNESS)" - export AGENT_OPS_HARNESS_CMD="$(_aoc_get '.harness.command' '' AGENT_OPS_HARNESS_CMD)" - # jq's `// empty` (in _aoc_get) treats boolean false as empty, so a literal + export OLYMPUS_HARNESS="$(_olc_get '.harness.kind' 'claude' OLYMPUS_HARNESS)" + export OLYMPUS_HARNESS_CMD="$(_olc_get '.harness.command' '' OLYMPUS_HARNESS_CMD)" + # jq's `// empty` (in _olc_get) treats boolean false as empty, so a literal # `health_probe: false` would be lost — normalise to the string "false"/"true" # inside the filter so only an explicit false disables the probe. - export AGENT_OPS_HEALTH_PROBE="$(_aoc_get '(.harness.health_probe | if . == false then "false" else "true" end)' 'true' AGENT_OPS_HEALTH_PROBE)" + export OLYMPUS_HEALTH_PROBE="$(_olc_get '(.harness.health_probe | if . == false then "false" else "true" end)' 'true' OLYMPUS_HEALTH_PROBE)" - # --- observer (mara): map .observer.* onto the MARA_* env mara.sh reads, + # --- observer (argus): map .observer.* onto the ARGUS_* env argus.sh reads, # unless the env already carries them (workflow/unit override wins). --- - export MARA_SERVICE_NAME="$(_aoc_get '.observer.service_name' 'the service' MARA_SERVICE_NAME)" - export MARA_HEALTH_URL="$(_aoc_get '.observer.health_url' "${MARA_HEALTH_URL:-}" MARA_HEALTH_URL)" - export MARA_REPO="$(_aoc_get '.observer.repo' "${MARA_REPO:-}" MARA_REPO)" - export MARA_LABELS="$(_aoc_get '.observer.labels' 'incident' MARA_LABELS)" - export MARA_READY_JQ="$(_aoc_get '.observer.readiness.jq' '' MARA_READY_JQ)" - export MARA_READY_EXPECT="$(_aoc_get '.observer.readiness.expect' 'true' MARA_READY_EXPECT)" + export ARGUS_SERVICE_NAME="$(_olc_get '.observer.service_name' 'the service' ARGUS_SERVICE_NAME)" + export ARGUS_HEALTH_URL="$(_olc_get '.observer.health_url' "${ARGUS_HEALTH_URL:-}" ARGUS_HEALTH_URL)" + export ARGUS_REPO="$(_olc_get '.observer.repo' "${ARGUS_REPO:-}" ARGUS_REPO)" + export ARGUS_LABELS="$(_olc_get '.observer.labels' 'incident' ARGUS_LABELS)" + export ARGUS_READY_JQ="$(_olc_get '.observer.readiness.jq' '' ARGUS_READY_JQ)" + export ARGUS_READY_EXPECT="$(_olc_get '.observer.readiness.expect' 'true' ARGUS_READY_EXPECT)" } diff --git a/scripts/lib/litellm-wait.sh b/scripts/lib/litellm-wait.sh index 1a344fa..85f3b72 100644 --- a/scripts/lib/litellm-wait.sh +++ b/scripts/lib/litellm-wait.sh @@ -128,7 +128,7 @@ wait_for_litellm() { # # CLAUDE_RETRY_MAX is per-caller; default 2 in the agent scripts # (giving 3 attempts total). The retry restarts claude from scratch -# — for stateful agents (wiwi) any work already committed to the +# — for stateful agents (hephaestus) any work already committed to the # branch is preserved by virtue of being on disk; in-flight, not-yet # -committed work is lost. That's an acceptable trade because the # alternative (no retry) loses the whole run anyway. diff --git a/scripts/lint/check-legacy-naming.sh b/scripts/lint/check-legacy-naming.sh new file mode 100755 index 0000000..3b825bc --- /dev/null +++ b/scripts/lint/check-legacy-naming.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash +# check-legacy-naming.sh — flag pre-rename "agent-ops" naming. +# +# This mechanism was renamed agent-ops → Olympus (see docs/migration.md). This +# linter has two users: +# +# 1. A CONSUMER repo upgrading to an Olympus-named release. Run it in the repo +# root to see exactly what to change before you re-pin: +# bash <(curl -fsSL \ +# https://raw.githubusercontent.com/Netis/olympus/main/scripts/lint/check-legacy-naming.sh) +# +# 2. THIS repo's own CI — a regression guard so the rename never silently +# leaks back in (the new label/bot/config/env names stay the only ones). +# +# IMPORTANT for consumers: a wrapper pinned to a PRE-rename tag (e.g. @v0.2.0) +# keeps working *unchanged*. That tag's content is frozen and GitHub redirects +# the old `uses:` path. You only need these edits when you re-pin to an +# Olympus-named tag — this is an upgrade pre-flight, not a "you're broken" alarm. +# +# scripts/lint/check-legacy-naming.sh [DIR] # default: current repo +# +# Exit 0 = no legacy naming found · 1 = items found · 2 = bad usage. +set -euo pipefail + +case "${1:-}" in + -h|--help) + sed -n '2,22p' "$0" | sed 's/^# \{0,1\}//' + exit 0 ;; +esac + +root="${1:-.}" +cd "$root" 2>/dev/null || { echo "check-legacy-naming: no such directory: $root" >&2; exit 2; } +git rev-parse --is-inside-work-tree >/dev/null 2>&1 || { + echo "check-legacy-naming: run inside a git repository (or pass its path)" >&2; exit 2; } + +# Files that legitimately retain the old names: this linter (it carries the +# patterns), the rename-record docs, and the migration guide. Harmless no-ops +# in a consumer repo, which won't have these paths. +ex=( + ':!scripts/lint/check-legacy-naming.sh' + ':!docs/pantheon.md' ':!docs/roadmap.md' ':!docs/improvement-plan.md' + ':!docs/migration.md' +) + +issues=0 +scan() { # <ere> <fix> + local sev="$1" title="$2" pat="$3" fix="$4" hits + hits="$(git grep -nIE "$pat" -- . "${ex[@]}" 2>/dev/null || true)" + [ -n "$hits" ] || return 0 + issues=$((issues + 1)) + printf '\n [%s] %s\n → %s\n' "$sev" "$title" "$fix" + printf '%s\n' "$hits" | sed 's/^/ /' +} + +echo "olympus migration check — scanning for legacy agent-ops naming" + +# --- MUST fix before re-pinning to an Olympus-named tag -------------------- +scan MUST "Mechanism repo reference (uses:)" \ + 'Netis/agent-ops' \ + 'Netis/agent-ops → Netis/olympus (GitHub redirects for now; update + re-pin to an Olympus tag)' +scan MUST "Version-pin workflow input" \ + 'agent_ops_ref' \ + 'agent_ops_ref: → olympus_ref:' +scan MUST "Policy config filename reference" \ + '\.agent-ops\.json' \ + '.agent-ops.json → .olympus.json' +scan MUST "Config schema reference" \ + 'agent-ops\.schema\.json' \ + 'agent-ops.schema.json → olympus.schema.json' +scan MUST "Env-var overrides" \ + 'AGENT_OPS_[A-Z_]+' \ + 'AGENT_OPS_* → OLYMPUS_*' +scan MUST "Observer (systemd) env" \ + 'MARA_[A-Z_]+' \ + 'MARA_* → ARGUS_*' +scan MUST "Self-hosted runner label" \ + '(self-hosted,[[:space:]]*agent-ops|"agent-ops")' \ + 'agent-ops runner label → olympus (keep workflow runs-on + runner registration in sync)' + +# --- cosmetic: brand + retired agent personas ------------------------------ +# Substring match (git grep's regex engine has no \b; -w would miss the +# underscore-embedded forms like run_wiwi / test_mara). Broad on purpose — +# COSMETIC severity, and 'mara' as a stray substring is a tolerable false hit. +scan COSMETIC "Brand / retired agent names" \ + '(agent-ops|wiwi|vivi|mara)' \ + 'rebrand: agent-ops→Olympus · wiwi→hephaestus · vivi→themis · mara→argus (overlaps the structural items above)' + +# --- the policy file itself ------------------------------------------------ +if [ -f .agent-ops.json ]; then + issues=$((issues + 1)) + printf '\n [MUST] Policy file is named .agent-ops.json\n → git mv .agent-ops.json .olympus.json\n' +fi + +echo +if [ "$issues" -eq 0 ]; then + echo "check-legacy-naming: ✓ no legacy agent-ops naming found" +else + cat <<'EOF' +check-legacy-naming: ✗ legacy naming found (see above). + + Also outside these files (this linter cannot see them): + • the review bot's GitHub account — the login in agents.review_bot_login is + a real account; rename or re-provision it, then update AUTO_MERGE_TEAM and + any allowlists that reference it. + + Full mapping + impact-by-pin-strategy: docs/migration.md +EOF +fi +exit $(( issues > 0 ? 1 : 0 )) diff --git a/scripts/lint/secrets.allowlist b/scripts/lint/secrets.allowlist index 8dc6a84..aedc074 100644 --- a/scripts/lint/secrets.allowlist +++ b/scripts/lint/secrets.allowlist @@ -22,7 +22,7 @@ LITELLM_BASE_URL LITELLM_NO_PROXY # AUTO_MERGE_TEAM: repo-level secret holding the CSV of GitHub logins -# allowed to auto-merge wiwi-spawned (issue-linked) PRs. Provisioned at +# allowed to auto-merge hephaestus-spawned (issue-linked) PRs. Provisioned at # the repo level; listed here because CI's GITHUB_TOKEN can't enumerate -# repo secrets. See auto_merge.sh / run_wiwi.sh. +# repo secrets. See auto_merge.sh / run_hephaestus.sh. AUTO_MERGE_TEAM diff --git a/scripts/pr-review/post_review.py b/scripts/pr-review/post_review.py index 3643fd4..d1ee534 100755 --- a/scripts/pr-review/post_review.py +++ b/scripts/pr-review/post_review.py @@ -192,6 +192,19 @@ def auto_merge(number: str) -> None: print(f"auto-merged PR #{number}") +def build_footer(run_url, review_bot=None): + """Attribution footer appended to every posted review. The bot name is + config-driven (OLYMPUS_REVIEW_BOT_LOGIN, exported by config.sh) — never + hardcoded — so a consumer's renamed review bot is attributed correctly.""" + if review_bot is None: + review_bot = os.environ.get("OLYMPUS_REVIEW_BOT_LOGIN", "the review bot") + return ( + "\n\n---\n" + f"🤖 Reviewed by **{review_bot}** • " + f"[workflow run]({run_url})" + ) + + def main() -> int: # Agent-failure path: keep failure details out of the PR entirely. # Operators read workflow logs for diagnostics; PR readers should @@ -213,12 +226,7 @@ def main() -> int: print("agent reported pre-flight error — not posting to PR") return 0 - footer = ( - "\n\n---\n" - "🤖 Reviewed by **vivi** • " - f"[workflow run]({RUN_URL})" - ) - full = body + footer + full = body + build_footer(RUN_URL) event = pick_event(body) print(f"posting review event={event} ({len(full)} bytes)") @@ -236,7 +244,7 @@ def main() -> int: # changes by the project maintainer, but anyone else's PR # still gets human review. # - # PRs labelled `auto-agent` are wiwi-spawned; their auto-merge + # PRs labelled `auto-agent` are hephaestus-spawned; their auto-merge # decision is owned by scripts/agent-bot/auto_merge.sh (different # gates: linked-issue author, not PR author). Skip here so the # two paths never race on the same PR. diff --git a/scripts/pr-review/run_review.sh b/scripts/pr-review/run_review.sh index 0b4370e..9fd151f 100755 --- a/scripts/pr-review/run_review.sh +++ b/scripts/pr-review/run_review.sh @@ -15,11 +15,11 @@ set -euo pipefail PR_NUMBER="${1:?usage: $0 <pr_number>}" WORKDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# Load .agent-ops.json so the configured harness (harness.kind) + model land in +# Load .olympus.json so the configured harness (harness.kind) + model land in # the env before the agent runs. No config ⇒ claude (back-compatible). # shellcheck source=../lib/config.sh source "$(cd "$WORKDIR/../lib" && pwd)/config.sh" -agent_ops_load_config +olympus_load_config OUT="/tmp/pr-review-${PR_NUMBER}-out.md" LOG="/tmp/pr-review-${PR_NUMBER}-agent.log" @@ -56,7 +56,7 @@ envsubst < "$WORKDIR/prompt.md" > "$PROMPT" # Source the harness adapter — it sources litellm-wait.sh and owns the gateway # pre-flight wait + the retry-on-gateway-down loop. The CLI is the consumer's -# .agent-ops.json harness.kind (default: claude). +# .olympus.json harness.kind (default: claude). # shellcheck source=../lib/agent-harness.sh source "$(cd "$(dirname "${BASH_SOURCE[0]}")/../lib" && pwd)/agent-harness.sh" @@ -76,7 +76,7 @@ ALLOWED_TOOLS="$(grep -v '^#' "$WORKDIR/allowed_tools.txt" \ claude_rc=0 agent_run --profile review --prompt "$PROMPT" --out "$OUT" --errlog "$LOG" \ --tools "$ALLOWED_TOOLS" --max-turns 60 --timeout 7200 \ - --output-format text --permission-mode acceptEdits --label vivi || claude_rc=$? + --output-format text --permission-mode acceptEdits --label themis || claude_rc=$? if [ "$claude_rc" -ne 0 ]; then echo "ERROR: agent exited with code $claude_rc" >> "$LOG" diff --git a/scripts/pr-review/test_post_review.py b/scripts/pr-review/test_post_review.py index 30b9bf0..93e2faf 100644 --- a/scripts/pr-review/test_post_review.py +++ b/scripts/pr-review/test_post_review.py @@ -169,6 +169,32 @@ def test_pick_event_comment_explicit(): assert post_review.pick_event(body) == "COMMENT" +def test_build_footer_uses_env_review_bot(): + """Footer attribution is config-driven, never hardcoded.""" + prev = os.environ.get("OLYMPUS_REVIEW_BOT_LOGIN") + os.environ["OLYMPUS_REVIEW_BOT_LOGIN"] = "athena" + try: + footer = post_review.build_footer("http://run/1") + assert "Reviewed by **athena**" in footer, footer + assert "http://run/1" in footer, footer + finally: + if prev is None: + del os.environ["OLYMPUS_REVIEW_BOT_LOGIN"] + else: + os.environ["OLYMPUS_REVIEW_BOT_LOGIN"] = prev + + +def test_build_footer_falls_back_without_env(): + """Absent config → a neutral label, never a stale hardcoded name.""" + prev = os.environ.pop("OLYMPUS_REVIEW_BOT_LOGIN", None) + try: + footer = post_review.build_footer("http://run/2") + assert "Reviewed by **the review bot**" in footer, footer + finally: + if prev is not None: + os.environ["OLYMPUS_REVIEW_BOT_LOGIN"] = prev + + if __name__ == "__main__": import traceback failed = 0