Skip to content

docs(readme): trim README and split details into docs/#402

Merged
captainsafia merged 1 commit intooz-agent/control-plane-migrationfrom
oz-agent/readme-cleanup
Apr 29, 2026
Merged

docs(readme): trim README and split details into docs/#402
captainsafia merged 1 commit intooz-agent/control-plane-migrationfrom
oz-agent/readme-cleanup

Conversation

@captainsafia
Copy link
Copy Markdown
Collaborator

Cleans up the README per Slack feedback on PR #400: keep it short and precise with minimal headings, and move the longer reference material into docs/.

Changes

  • README.md is now a short intro plus a documentation index (~12 lines, down from ~260).
  • New docs/architecture.md captures the repository layout, the webhook-driven flow, and the GitHub Actions workflow table that used to live in the README.
  • New docs/onboarding.md captures the GitHub App / Vercel project / GitHub Actions secrets walkthrough that used to live under the README's "How to use these workflows in your own repo" section.
  • The README's old "Local development" section is dropped because CONTRIBUTING.md already documents env setup, the test suites, and vercel dev — the README now links there instead.

Targeting

This PR targets oz-agent/control-plane-migration (the branch behind PR #400) so the README cleanup can be reviewed independently and folded into that PR.

Conversation: https://staging.warp.dev/conversation/69d7450e-0974-42dc-9a91-26bdb498f667
Run: https://oz.staging.warp.dev/runs/019dd7c4-b4e7-7bd8-b0b6-37b274351909

This PR was generated with Oz.

The README has grown to ~260 lines mixing repository layout, webhook
flow, GitHub Actions plumbing, onboarding instructions, and local
development notes. Cut it back to a short intro plus a documentation
index, and move the moved content into:

- docs/architecture.md — repo layout, webhook flow, GHA workflows table
- docs/onboarding.md   — GitHub App + Vercel + GHA secrets walkthrough

Local development notes already live in CONTRIBUTING.md, so the README
now links there directly instead of duplicating them.

Co-Authored-By: Oz <oz-agent@warp.dev>
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
oz-for-oss Ready Ready Preview, Comment Apr 29, 2026 5:52am

Request Review

@captainsafia captainsafia marked this pull request as ready for review April 29, 2026 13:33
@captainsafia captainsafia merged commit 7361557 into oz-agent/control-plane-migration Apr 29, 2026
10 checks passed
@oz-for-oss
Copy link
Copy Markdown
Contributor

oz-for-oss Bot commented Apr 29, 2026

@captainsafia

I'm starting a first review of this pull request.

You can view the conversation on Warp.

I completed the review and posted feedback on this pull request.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

Copy link
Copy Markdown
Contributor

@oz-for-oss oz-for-oss Bot left a comment

Choose a reason for hiding this comment

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

Overview

This PR trims the README to a short introduction and documentation index, moving the longer architecture and onboarding material into new docs pages.

Concerns

  • No blocking correctness, security, or documentation concerns found in the changed lines.

Verdict

Found: 0 critical, 0 important, 0 suggestions

Approve

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

captainsafia added a commit that referenced this pull request Apr 29, 2026
The README has grown to ~260 lines mixing repository layout, webhook
flow, GitHub Actions plumbing, onboarding instructions, and local
development notes. Cut it back to a short intro plus a documentation
index, and move the moved content into:

- docs/architecture.md — repo layout, webhook flow, GHA workflows table
- docs/onboarding.md   — GitHub App + Vercel + GHA secrets walkthrough

Local development notes already live in CONTRIBUTING.md, so the README
now links there directly instead of duplicating them.

Co-authored-by: Oz <oz-agent@warp.dev>
captainsafia added a commit that referenced this pull request Apr 30, 2026
…on cloud runs (#400)

* feat(control-plane): scaffold vercel webhook and cron poller

Add a greenfield Python project at control-plane/ that hosts the
GitHub webhook endpoint and the cron poller for in-flight cloud
agent runs. The webhook handler verifies HMAC-SHA256 signatures,
routes events to a workflow handler, and returns 202 immediately;
the cron poller drains run state from Vercel KV and applies
terminal results back to GitHub through workflow-specific handlers.

The lib/ helpers (signatures, routing, trust, dispatch, state,
poll_runs, github_app) keep the runtime stdlib-friendly so the
test suite runs without extra dependencies. vercel.json declares
the Python 3.12 runtime, the webhook function, the cron function,
and a 1-minute schedule. requirements.txt mirrors the legacy
.github/scripts/requirements.txt pins.

Co-Authored-By: Oz <oz-agent@warp.dev>

* feat(oz_workflows): add role parameter and review-triage env-var resolution

build_agent_config() now accepts a 'role' keyword. When the caller
passes role='review-triage' the resolver consults
WARP_REVIEW_TRIAGE_ENVIRONMENT_ID first, then falls back to
WARP_ENVIRONMENT_ID. Default role behavior is unchanged. Unknown
roles emit a ::warning:: and use the default lookup so a future
workflow addition does not have to coordinate a lockstep change to
the role registry before it ships.

Co-Authored-By: Oz <oz-agent@warp.dev>

* feat(oz_workflows): generalize artifact loaders for cloud-mode workflows

Add load_run_artifact(run_id, filename) as the workflow-agnostic
entry point and named wrappers load_triage_artifact (triage_result.json),
load_issue_response_artifact (issue_response.json), and
load_review_artifact (review.json). Rewrite load_pr_metadata_artifact
as a thin wrapper around load_run_artifact that keeps its original
schema enforcement.

The named filenames are exported as module-level constants so the
cloud-mode prompts and the cron poller stay in lockstep with the
filename the agent uploads via 'oz artifact upload <name>.json'.

Co-Authored-By: Oz <oz-agent@warp.dev>

* feat: switch triage and review workflows to cloud-mode run_agent

The triage, respond-to-triaged-issue-comment, and review-pull-request
workflows now dispatch cloud agent runs via run_agent + build_agent_config
instead of run_agent_in_docker. The agent uploads the workflow result
artifact via 'oz artifact upload <name>.json' and the host loads it
through the corresponding load_*_artifact helper.

The cloud-mode prompts replace the Docker mount references
(/mnt/repo, /mnt/output) with explicit artifact-upload instructions.
Security rules, output-schema requirements, and skill references are
preserved verbatim across the rewrite. Each caller passes
role='review-triage' to build_agent_config so the operator can route
those workloads onto WARP_REVIEW_TRIAGE_ENVIRONMENT_ID when configured.

Tests now mock run_agent + load_*_artifact instead of
run_agent_in_docker; the obsolete _container_companion_path test
classes are removed alongside the helper.

Co-Authored-By: Oz <oz-agent@warp.dev>

* chore: delete docker assets after cloud-mode migration

The triage and review workflows now run on Warp-hosted cloud agent
runs, so the Docker images, build composite actions, host-side
docker_agent.py helper, and its test_docker_agent.py suite are no
longer reachable from any active call site. Removing them keeps the
cloud workflow path the only path; the workflow YAMLs in
.github/workflows/ continue to reference run-oz-python-script as
before and need no Docker context.

Co-Authored-By: Oz <oz-agent@warp.dev>

* chore(workflows): drop docker build steps and forward review-triage env vars

The three workflows that previously dispatched Dockerized agents
(triage-new-issues, respond-to-triaged-issue-comment,
review-pull-request) no longer need the Build … agent container step
or the TRIAGE_IMAGE / REVIEW_IMAGE env passthrough. Each workflow now
forwards WARP_ENVIRONMENT_ID and the optional
WARP_REVIEW_TRIAGE_ENVIRONMENT_ID to the Python entrypoint so
build_agent_config(role='review-triage') can route those workflows
onto a dedicated cloud environment when the operator configures one.

The workflow files themselves are intentionally retained so the
existing GitHub Actions delivery path keeps working until the Vercel
control plane is provisioned and the GitHub App webhook URL flips
over. A follow-up PR will delete .github/workflows/* per plan §5a.

Co-Authored-By: Oz <oz-agent@warp.dev>

* docs(skills): rewrite docker workflow mode sections to cloud workflow mode

The triage-issue, dedupe-issue, review-pr, and review-spec skills
now run as Warp-hosted cloud agent runs rather than inside a Docker
container, so the Docker mount-based handoff is no longer the right
contract. Each skill's workflow-mode section now describes the cloud
flow: the agent writes its result file in the run's working
directory, validates it with jq, and uploads it via 'oz artifact
upload <name>.json' so the host workflow can fetch it after the run
reaches a terminal state.

Repository-specific overrides, output schemas, severity labels,
suggestion-block constraints, and other cross-repo contracts are
preserved verbatim across the rewrite.

Co-Authored-By: Oz <oz-agent@warp.dev>

* docs: describe vercel control plane and review-triage env var

README.md now explains both delivery surfaces (GitHub Actions + Vercel
control plane) and points at control-plane/README.md for the cutover
runbook. The new WARP_REVIEW_TRIAGE_ENVIRONMENT_ID variable is
documented as the optional override for routing triage and review
runs onto a dedicated cloud environment.

CONTRIBUTING.md replaces the outdated Docker setup guidance with a
'Local Vercel dev' section that shows how to run 'vercel dev', sign a
synthetic webhook, and replay it against the local handler.

docs/platform.md acknowledges the Vercel webhook + cron flow in the
'How a workflow uses an agent' walkthrough so readers see both
delivery surfaces in the platform overview.

Co-Authored-By: Oz <oz-agent@warp.dev>

* fix(control-plane): use upstash-redis for KV access and drop invalid python runtime

Two fixes required to make the Vercel deploy succeed end-to-end:
- vercel.json: remove the "runtime": "python3.12" entries; Vercel
  rejected them as invalid runtime identifiers. The Python runtime is
  auto-detected from .py files in api/ and defaults to 3.12.
- requirements.txt + api/cron.py: replace the vercel-kv package (whose
  KV() constructor does not accept the documented kwargs and rejects
  the Vercel-injected KV_* env vars) with upstash-redis, the official
  Upstash Python SDK. Vercel KV is Upstash Redis under the hood, and
  upstash-redis consumes KV_REST_API_URL / KV_REST_API_TOKEN directly.

Smoke tests against the preview deploy now pass:
- GET /api/webhook -> 200 {"status": "ok"}
- GET /api/cron without auth -> 401 invalid cron secret
- GET /api/cron with bearer -> 200 empty drain

Co-Authored-By: Oz <oz-agent@warp.dev>

* feat(oz_workflows): add dispatch_run for fire-and-forget agent dispatch

Splits the dispatch step out of run_agent into a standalone function
so callers that cannot afford to block on the polling loop (most
notably the Vercel webhook handler, which must respond within
GitHub's ~10s delivery window) can dispatch a run and persist its
run_id to KV for the cron poller to drain asynchronously.

run_agent is preserved as the blocking convenience wrapper used by
GitHub Actions entrypoints, refactored to call dispatch_run
internally so the request-construction path stays single-source.

Co-Authored-By: Oz <oz-agent@warp.dev>

* feat(oz_workflows): add fire-and-forget dispatch_run helper

The Vercel webhook handler dispatches cloud agent runs in fire-and-forget
mode: it persists ``RunState`` keyed by the returned ``run_id`` and returns
202 immediately. The cron poller drains the run on the next tick.

Splitting the SDK call out of ``run_agent`` keeps the legacy GitHub
Actions entrypoints synchronous (``run_agent`` now wraps ``dispatch_run``
plus the existing polling loop) while giving the control-plane webhook
handler a primitive that does not block on a poll.

Co-Authored-By: Oz <oz-agent@warp.dev>

* chore(control-plane): mirror oz_workflows + entrypoints into the function

Vercel's project root is `control-plane/`, but the four PR-flow
entrypoints (`review_pr.py`, `respond_to_pr_comment.py`,
`verify_pr_comment.py`, `enforce_pr_issue_state.py`) and the
`oz_workflows` package they depend on live under `.github/scripts/`.

Add a Vercel `installCommand` (`scripts/vercel_install.sh`) that mirrors
those into `control-plane/lib/oz_workflows/` and
`control-plane/lib/scripts/` before each Vercel build. Extend the
function-runtime `PYTHONPATH` to include `lib/` so the control-plane
code can `from oz_workflows.oz_client import ...` and
`from scripts.review_pr import ...` directly.

The mirrored directories are git-ignored so the repository keeps a
single source of truth at `.github/scripts/`.

Co-Authored-By: Oz <oz-agent@warp.dev>

* refactor(workflows): expose gather/build/apply helpers on PR entrypoints

Refactors the four PR-flow entrypoints to surface three importable
helpers each so the Vercel control plane can reuse the same logic:

* `verify_pr_comment.py`: `gather_verify_context`, the existing
  `build_verification_prompt`, and `apply_verification_result`.
* `enforce_pr_issue_state.py`: `enforce_pr_state_synchronously` (returns
  an `EnforceDecision` of allow / close / need-cloud-match),
  `gather_enforce_context`, the existing
  `build_issue_association_prompt`, and `apply_issue_association_result`.
* `review_pr.py`: `gather_review_context`,
  `build_review_prompt_for_dispatch` (cloud-mode prompt with PR
  description / annotated diff / spec context inlined since the cloud
  agent has no host-prepared workspace), and `apply_review_result`.
  The existing `build_review_prompt` keeps the file-based variant for
  the GitHub Actions path.
* `respond_to_pr_comment.py`: `gather_pr_comment_context`,
  `build_pr_comment_prompt`, and `apply_pr_comment_result`. All three
  event paths (review_comment / issue_comment / review_body) now funnel
  through the shared helpers.

Each entrypoint's `main()` was updated to call the same helpers so the
GitHub Actions path and the Vercel control plane path stay byte-for-byte
equivalent. The full `.github/scripts/tests` suite still passes (494/494).

Co-Authored-By: Oz <oz-agent@warp.dev>

* feat(control-plane): wire builders, handlers, webhook, and cron registry

Adds the production wiring that lets the Vercel control plane drive PR
workflows end-to-end:

* `lib/builders.py` exposes one `PromptBuilder` per cloud-agent
  workflow (`review-pull-request`, `respond-to-pr-comment`,
  `verify-pr-comment`, `enforce-pr-issue-state`) plus
  `build_builder_registry` for the webhook handler.
* `lib/handlers.py` exposes the corresponding `WorkflowHandlers`
  registry (artifact_loader / result_applier / failure_handler) for the
  cron poller, with each handler minting a fresh GitHub client per
  call.
* `api/webhook.py:process_webhook_request` now verifies the signature,
  routes the event, runs the synchronous `enforce-pr-issue-state` path
  in-process when applicable, evaluates the route through the builder
  registry, dispatches the cloud agent run via `dispatch_run`, and
  persists the run state to KV. Errors at any stage surface as a
  structured 500 instead of hanging the request.
* `api/cron.py:build_workflow_handlers` returns the concrete handler
  registry instead of an empty `{}`.

The dispatch path is gated behind optional kwargs on
`process_webhook_request` so the existing unit tests continue to pass
without GitHub or oz-agent SDKs on PYTHONPATH.

Co-Authored-By: Oz <oz-agent@warp.dev>

* test(control-plane): add builders + handlers + webhook dispatch unit tests

Adds three new test modules covering the wiring introduced in the
previous commit:

* `tests/test_builders.py` (7 tests) — one test class per builder.
  Stubs `scripts.<workflow>` so the assertions stay focused on payload
  parsing, repo handle resolution, and DispatchRequest shape.
* `tests/test_handlers.py` (10 tests) — one test class per
  `WorkflowHandlers`. Stubs `scripts.<workflow>` and `oz_workflows.*`
  so the assertions verify which apply helper / artifact loader gets
  called with the right arguments. Includes a failure_handler test
  that confirms `WorkflowProgressComment.report_error` is invoked.
* `tests/test_webhook_dispatch.py` (6 tests) — exercises the new
  dispatch path in `process_webhook_request`: success, no-builder,
  builder-raises, dispatch-raises, plus the synchronous
  enforce-pr-issue-state short-circuit and need-cloud-match
  fallthrough.

Also updates `control-plane/README.md` to drop the "currently no-op"
caveat, list which PR-triggered workflows are live vs. still pending,
and document the new install hook + GitHub App env vars.

Total control-plane test count: 110 (up from 87). The legacy
`.github/scripts/tests` suite still passes (494/494).

Co-Authored-By: Oz <oz-agent@warp.dev>

* docs(control-plane): refresh entrypoint runtime docs

Co-Authored-By: Safia Abdalla <captainsafia@users.noreply.github.com>

Co-Authored-By: Oz <oz-agent@warp.dev>

* fix(control-plane): vendor oz_workflows + entrypoints so vercel ships them

The previous setup relied on a vercel.json installCommand that copied
.github/scripts/oz_workflows and the four PR-flow entrypoints into
control-plane/lib/ at build time. That approach assumed Vercel would
clone the entire repo onto the build runner, but Vercel's project
root is set to control-plane/ and Vercel does not pull files outside
the project root by default. The install script silently couldn't
locate the source on the build runner, so the function bundle was
deployed without oz_workflows, producing:

  {"error": "webhook runtime not ready: No module named 'oz_workflows'"}

This commit checks the mirrored copies into the branch directly so
they ship with the function bundle regardless of Vercel's source
inclusion settings. The vendored copies live at
control-plane/lib/oz_workflows/ and control-plane/lib/scripts/ and
are no longer in .gitignore.

The vercel.json installCommand is dropped so Vercel uses its default
'pip install -r requirements.txt' for runtime deps.

scripts/vercel_install.sh is retained as a local refresh helper that
contributors run manually after editing .github/scripts/oz_workflows
or any of the four PR-flow entrypoints. CI does not run it; Vercel
does not run it.

A future improvement worth tracking: a pre-commit hook (or CI job)
that fails if control-plane/lib/oz_workflows is out of sync with
.github/scripts/oz_workflows, so contributors cannot merge without
running the refresh.

Co-Authored-By: Oz <oz-agent@warp.dev>

* fix(control-plane): resolve bare skill names into <repo>:<path> spec before dispatch

The Vercel webhook handler dispatched cloud agent runs with bare skill
names like 'review-pr', 'implement-issue', and 'verify-pr', which the
Oz API rejects with:

  invalid skill_spec format: missing ':' separator.
  Expected format: 'repo:skill' or 'org/repo:skill'

The legacy oz_workflows.oz_client.skill_spec resolves these by
checking the local filesystem, but the Vercel function has no
filesystem access to the skill files so it cannot use that path.

This change adds cloud_skill_spec(skill_name) in
control-plane/lib/dispatch.py that formats a bare skill name into

  <workflow_code_repo>:.agents/skills/<name>/SKILL.md

defaulting <workflow_code_repo> to 'warpdotdev/oz-for-oss' (the repo
that hosts the bundled core skills) and allowing forks to override
via the WORKFLOW_CODE_REPOSITORY env var. dispatch_run now calls
cloud_skill_spec on request.skill_name before passing it to the Oz
SDK runner, so the skill arrives at the API in the required form.

Repo-local override skills (e.g. 'review-pr-local' in the consuming
repo) remain referenced inside the prompt body via the existing
oz_workflows.repo_local.format_repo_local_prompt_section helper; this
change only fixes the dispatch-side core skill spec.

Adds 7 tests for cloud_skill_spec covering the default repo, the
env-var override, the kwarg override, the already-qualified
pass-through, the SKILL.md suffix path, the empty-string case, and
the None-skill dispatch path.

Co-Authored-By: Oz <oz-agent@warp.dev>

* feat(workflows): allow WorkflowProgressComment + apply helpers to be reconstructed by callers

The Vercel control plane needs to drive the WorkflowProgressComment
lifecycle across the webhook → cron seam: the webhook posts the
'starting...' comment at dispatch time and the cron poller updates the
same comment as the run progresses. To support that without forking the
helper, two changes are needed:

- WorkflowProgressComment now accepts optional comment_id, run_id,
  oz_run_id, and session_link kwargs. Callers (the cron's result_applier
  and failure_handler) construct an instance bound to the comment posted
  at dispatch time so progress.complete / progress.replace_body /
  progress.report_error edit that exact comment instead of falling back
  to the workflow-prefix lookup. Defaults preserve the legacy GitHub
  Actions runtime contract.
- apply_review_result / apply_pr_comment_result / apply_verification_result /
  apply_issue_association_result now accept an optional progress kwarg.
  When supplied, the helper reuses the caller's WorkflowProgressComment
  instead of constructing a fresh one. Production GHA callers that omit
  progress see no behavior change.

Co-Authored-By: Oz <oz-agent@warp.dev>

* feat(control-plane): post WorkflowProgressComment start before dispatch

Builders now drive the user-facing progress comment lifecycle that the
legacy GitHub Actions `main()` entrypoints used to: each builder
constructs a WorkflowProgressComment for the originating issue/PR,
posts the workflow-specific 'starting...' status line, and stashes
progress_comment_id + progress_run_id onto DispatchRequest.payload_subset
so the cron poller can reconstruct the same instance when the run
terminates.

The review-pr / respond-to-pr-comment / verify-pr-comment builders post
the start comment directly via _start_progress_comment. The
enforce-pr-issue-state builder hands its progress instance to the
synchronous helper, which already drives the start line for the
need-cloud-match branch.

Without this, real PR webhook deliveries dispatched a cloud agent run
but never produced any user-visible 'Oz is starting to review...' /
'/oz-verify is running...' comments, and operators had no way to
follow run progress.

Co-Authored-By: Oz <oz-agent@warp.dev>

* feat(control-plane): drive WorkflowProgressComment lifecycle from cron

The cron poller now keeps the originating progress comment in sync
with the run's lifecycle:

- A new non_terminal_handler protocol on WorkflowHandlers fires on every
  pending poll. Each PR-flow handler reconstructs the
  WorkflowProgressComment from the persisted payload_subset
  (progress_comment_id / progress_run_id) and calls
  record_run_session_link so the session-share link surfaces in the
  user-visible comment as soon as Oz reports it. Failures are absorbed.
- Each result_applier reconstructs the same progress instance and passes
  it through to the workflow-specific apply_*_result helper so the final
  progress.complete / progress.replace_body call edits the comment
  posted at dispatch time instead of creating a sibling comment.
- Each failure_handler also reconstructs the progress instance and
  invokes progress.report_error, producing an in-place error message
  rather than orphaning the in-flight progress comment.

Together with the builder-side start, this restores the user-facing
'Oz is starting to review...' → '...session link...' → 'Oz completed
the review' progression on the originating PR / issue when runs are
dispatched through the Vercel control plane.

Co-Authored-By: Oz <oz-agent@warp.dev>

* chore(control-plane): refresh vendored oz_workflows + entrypoints

Mirrors the helpers + apply-result signature changes from .github/scripts/
into control-plane/lib/. The vendored copies are the canonical
Vercel-side source — Vercel does not run scripts/vercel_install.sh,
so any edit to .github/scripts/ only reaches the function bundle
through this committed mirror.

Co-Authored-By: Oz <oz-agent@warp.dev>

* refactor(repo): host webhook control plane at root and prune PR-only GH Actions

Move the webhook + cron control plane out of `control-plane/` into the
repository root so the Vercel function bundle reads `api/`, `lib/`,
`tests/`, `vercel.json`, and `requirements.txt` directly without a
mirror step. The legacy `control-plane/scripts/vercel_install.sh`
mirror is no longer needed: `lib/oz_workflows/` and `lib/scripts/` are
now the only canonical copies.

PR-driven bot behavior (`review-pull-request`, `enforce-pr-issue-state`,
`respond-to-pr-comment`, `verify-pr-comment`) now flows exclusively
through the webhook. The matching GitHub Actions wrappers and Python
entrypoints have been deleted, along with the `pr-hooks.yml` umbrella
workflow that used to fan them out:

- .github/workflows/{enforce-pr-issue-state, pr-hooks,
  respond-to-pr-comment*, review-pull-request, verify-pr-comment*}.yml
- .github/scripts/{enforce_pr_issue_state, resolve_review_context,
  respond_to_pr_comment, review_pr, verify_pr_comment}.py
- The accompanying tests under .github/scripts/tests/

Issue-triggered (`triage`, `create-spec-from-issue`,
`create-implementation-from-issue`, `respond-to-triaged-issue-comment`,
`comment-on-unready-assigned-issue`, `comment-on-ready-to-{spec,
implement}`) and plan-approval (`comment-on-plan-approved`,
`trigger-implementation-on-plan-approved`,
`remove-stale-issue-labels-on-plan-approved`) workflows stay on the
GitHub Actions delivery path because they clone the repo, push
branches, and open PRs from inside the runner. The router in
`lib/routing.py` deliberately drops `issues` events and `issue_comment`
events on plain issues so the webhook does not double-fire alongside
the workflow.

Other supporting changes:

- Convert `run-tests.yml` from a `workflow_call` shim into a standalone
  PR-triggered workflow that runs both the webhook tests
  (`python -m pytest tests`) and the helper unit tests
  (`PYTHONPATH=lib:.github/scripts python -m unittest discover -s
  .github/scripts/tests`).
- Update `.github/actions/run-oz-python-script` to set
  `PYTHONPATH=lib:.github/scripts` and install dependencies from the
  top-level `requirements.txt` so the issue-triggered + self-improvement
  workflows can still resolve `oz_workflows` after the move.
- Update `.github/actions/setup-oz-python` to default to the top-level
  `requirements.txt`.
- Refresh `README.md`, `CONTRIBUTING.md`, and `docs/platform.md` to
  describe the two delivery surfaces, the routing split, and the new
  paths.

Validation:
- python -m pytest tests \u2192 110 passed, 14 subtests passed
- PYTHONPATH=lib:.github/scripts python -m unittest discover \\
    -s .github/scripts/tests \u2192 362 passed

Co-Authored-By: Oz <oz-agent@warp.dev>

* fix(ci): install pytest + pytest-subtests for control-plane tests

The new `run-tests.yml` workflow runs `python -m pytest tests` against
the webhook control-plane test suite, but pytest is intentionally not
listed in `requirements.txt` (those are runtime dependencies for the
Vercel function bundle). The CI job therefore failed with
`No module named pytest`.

Install the test-only dependencies (`pytest` + `pytest-subtests`) in
the workflow before invoking pytest, and document the same install in
`CONTRIBUTING.md` so local setups stay aligned with CI.

Validation: 110 passed, 14 subtests passed (python -m pytest tests)

Co-Authored-By: Oz <oz-agent@warp.dev>

* feat(control-plane): route issue triage through the Vercel webhook

Migrates the triage-new-issues workflow off the GitHub Actions delivery
surface and onto the Vercel webhook + cron control plane that already
serves the PR-driven workflows in this branch.

- Routing: issues.opened on a non-triaged issue routes to
  triage-new-issues; plain issue_comment events route to triage when
  they carry @oz-agent on a non-triaged issue or are a needs-info
  reply from the original reporter. @oz-agent mentions on triaged
  issues stay on the GitHub Actions respond-to-triaged path.
- Builder: build_triage_request gathers issue + dedupe + triage-config
  context via PyGithub, posts the WorkflowProgressComment start line,
  and stuffs progress_comment_id / progress_run_id onto payload_subset
  so the cron poller can edit the same comment.
- Handler: build_triage_handlers wires up load_triage_artifact, the
  cloud-mode applier, the failure handler, and the non-terminal
  session-link refresh.
- Cloud-mode helpers: lib/scripts/triage_new_issues.py now exposes
  TriageContext, gather_triage_context, build_triage_prompt_for_dispatch,
  and apply_triage_result_for_dispatch alongside the legacy main()
  entrypoint and the pure helpers consumed by the unit tests.
- Cleanup: deletes .github/workflows/triage-new-issues{,-local}.yml,
  the .github/scripts/triage_new_issues.py shim, and moves the triage
  unit tests under tests/.
- Docs: README, api/cron.py, and lib/routing.py docstrings now describe
  the triage delivery path.
- Tests: tests/test_routing.py covers the new issues + plain-issue
  routing; tests/test_builders.py and tests/test_handlers.py cover the
  new builder and handler; the relocated tests/test_triage.py preserves
  the existing pure-helper coverage.

Co-Authored-By: Oz <oz-agent@warp.dev>

* chore: drop uv version pin and ignore local-only build artifacts

The pinned uv version (0.11.7 / 0.11.8) in uv.toml was too strict for
Vercel's bundled uv (0.10.11) and caused production deploys to fail
with 'Required uv version does not match the running version'.
Removing uv.toml lets each environment use whatever uv it ships with;
GitHub Actions can pin via the action's own input if needed.

Also extend .gitignore with the local-only artifacts that have been
showing up in working trees: .vercel CLI output (no trailing slash so
it matches both file and dir), .env*.local for the per-environment
secret files, and .DS_Store from macOS.

Co-Authored-By: Oz <oz-agent@warp.dev>

* feat(routing): triage every issue regardless of lifecycle labels

Previously the webhook routing skipped issues that already carried the
'triaged' label: 'issues.opened' deliveries returned None, and
'@oz-agent' mentions on triaged issues were left to the legacy
'respond-to-triaged-issue-comment' GitHub Actions workflow. That
workflow in turn excludes issues with 'ready-to-spec' or
'ready-to-implement' (see .github/workflows/respond-to-triaged-issue-comment.yml:30),
so issues that progressed past triage fell into a routing gap and
never re-triaged on new comments.

Drop both gates: every 'issues.opened' delivery now triggers triage
(useful when an issue is reopened or imported with prior labels), and
every '@oz-agent' mention on a non-PR issue routes to triage so the
bot picks up new context from the conversation regardless of the
issue's lifecycle stage.

Operators can remove the 'respond-to-triaged-issue-comment' GitHub
Actions workflow once they're comfortable letting triage handle every
mention; for now leaving it in place will simply cause a duplicate
inline-response comment alongside the triage progress comment for
'triaged'-but-not-'ready' issues.

Updates two existing tests to assert the new behavior and adds two
new tests covering the 'ready-to-implement' label path.

Co-Authored-By: Oz <oz-agent@warp.dev>

* feat(review): assign exactly one randomly-selected human reviewer per PR

Resolves #399. The PR review workflow used to request review from
every matching stakeholder (capped at 3) by iterating the agent's
recommended_reviewers list in order. That over-notified maintainers
and gave the assignment no randomness, so the same person tended to
get pinged on every PR that touched their files.

This change reworks _normalize_reviewer_logins to:

- Build the full eligible candidate pool (preserving the existing
  filtering: dedup, strip @-prefix, drop the PR author, require
  membership in .github/STAKEHOLDERS when allowed_logins is set).
- Uniformly sample sample_size logins from that pool using
  random.sample (without replacement).

The default sample_size is the new _REVIEWER_SAMPLE_SIZE = 1
constant. Callers can pass a different size and an injected
random.Random instance for deterministic tests.

Also updates the agent-facing prompt (in both gather_review_context
and the legacy main() path) to clarify that the workflow uniformly
samples exactly one reviewer from the candidates the agent returns,
so the agent should provide a meaningful pool rather than picking
the single 'best' candidate itself.

Adds 16 tests (NormalizeReviewerLoginsTest +
ResolveNonMemberReviewActionTest) covering: default sample size of
1, single-pick semantics, uniform distribution across the pool,
PR-author exclusion (case-insensitive), dedup, @-prefix stripping,
allowed_logins gating, empty-pool / non-list / sample-size=0 short
circuits, sample-size > pool fallback, larger explicit sample sizes,
and the APPROVE / REQUEST_CHANGES branches in
_resolve_non_member_review_action.

Co-Authored-By: Oz <oz-agent@warp.dev>

* feat(review): downgrade APPROVE verdict to COMMENT review on PRs

The agent now only ever takes a REQUEST_CHANGES action against a PR.
Positive verdicts are posted as plain COMMENT reviews so a human still
has to actually approve; the reviewer-request still pings a randomly
sampled stakeholder so the PR is handed off to a maintainer.

- _resolve_non_member_review_action maps verdict=APPROVE -> event=COMMENT
  while preserving recommended_reviewers; verdict=REQUEST_CHANGES is left
  as event=REQUEST_CHANGES with no reviewer request.
- apply_review_result and the legacy main() now post the GitHub review
  only when there is real content (or the event is REQUEST_CHANGES) and
  always issue the reviewer request when recommended_reviewers is set.
- The empty-feedback short-circuit now also requires no reviewers so an
  APPROVE verdict with no inline comments still pings a human.
- _format_review_completion_message no longer claims the bot approved
  the PR; the COMMENT-with-reviewers branch says I left feedback as a
  comment so a maintainer can approve.
- Updated the non-member-review prompt in both gather_review_context and
  legacy main() to explain that APPROVE will be downgraded to COMMENT
  and that a human reviewer is the one who actually approves.
- Tests in tests/test_review_pr_reviewer_sampling.py updated to assert
  the new mapping and the rewritten completion message.

Co-Authored-By: Oz <oz-agent@warp.dev>

* feat(triage): add comment_type discriminator for issue-thread responses

The triage workflow now accepts a `comment_type` discriminator on
`triage_result.json` so the agent can return either a structured
triage comment or a lighter issue-thread response when answering a
follow-up question on an already-triaged issue.

- `comment_type="triage"` (default; backwards compatible) drives the
  existing structured comment with statements, follow-up questions,
  duplicate detection, and a maintainer-details expando, plus the
  label mutations applied by `apply_triage_result`.
- `comment_type="response"` drives a brief user-facing reply
  (`response_body`) with a maintainer-only Reasoning expando
  (`details`). The workflow does NOT change any labels in this mode
  so the issue's lifecycle state stays as the maintainer left it.

Routing was already correct: `@oz-agent` mentions on plain issues
route to `WORKFLOW_TRIAGE_NEW_ISSUES` regardless of triaged /
needs-info / ready-to-implement state. The routing docstrings were
updated to call out that the workflow itself picks the comment shape
from the discriminator on its result payload.

- Added `COMMENT_TYPE_TRIAGE`, `COMMENT_TYPE_RESPONSE`,
  `ALLOWED_COMMENT_TYPES`, `RESPONSE_DETAILS_SUMMARY`, and
  `RESPONSE_FALLBACK_BODY` constants.
- Added `extract_comment_type`, `extract_response_body`,
  `extract_response_details`, and `build_response_comment_body`
  helpers. Unknown / non-string `comment_type` values fall back to
  `triage` so a malformed payload never produces a half-rendered
  response.
- `apply_triage_result_for_dispatch` (cloud path) and the legacy
  `process_issue` GHA path branch on `comment_type` before calling
  `apply_triage_result`. Response mode replaces the progress comment
  with the response shape and skips all label mutations.
- The triage prompt now documents both shapes and tells the agent
  when to pick each one.
- Tests cover the discriminator extraction, response field
  normalization, response comment markdown layout, and the
  apply-result dispatch (response mode skips labels; unknown
  `comment_type` falls back to triage and applies labels).

Co-Authored-By: Oz <oz-agent@warp.dev>

* docs(readme): trim README and split details into docs/ (#402)

The README has grown to ~260 lines mixing repository layout, webhook
flow, GitHub Actions plumbing, onboarding instructions, and local
development notes. Cut it back to a short intro plus a documentation
index, and move the moved content into:

- docs/architecture.md — repo layout, webhook flow, GHA workflows table
- docs/onboarding.md   — GitHub App + Vercel + GHA secrets walkthrough

Local development notes already live in CONTRIBUTING.md, so the README
now links there directly instead of duplicating them.

Co-authored-by: Oz <oz-agent@warp.dev>

* feat(comments): respond to every human-authored comment, not just org members

The control plane previously refused to dispatch a respond-to-pr-comment
or verify-pr-comment run unless the triggering comment's author was an
organization member, owner, or collaborator (or passed an
`/orgs/{org}/members/{login}` probe). The bot now responds to every
human-authored comment; the only authors filtered out are automation
accounts, which the existing `is_automation_user` check already drops.

- `lib/scripts/respond_to_pr_comment.py:main()` no longer calls
  `is_trusted_commenter`. The bot-author short-circuit at the top of
  `main()` is the only filter that remains.
- `lib/scripts/verify_pr_comment.py:main()` does the same for the
  `/oz-verify` command.
- The respond-to-pr-comment prompt no longer claims the workflow has
  pre-screened the commenter as a trusted org member. The new copy
  reminds the agent to treat all fetched PR / comment content as
  untrusted (per the security rules) and to weigh maintainer comments
  more heavily than drive-by replies based on the
  `author_association` labels the fetch script returns.
- Removed the now-dead trust surface: `is_trusted_commenter` from
  `lib/oz_workflows/helpers.py`, the `urllib.parse` import that only
  existed for the org-membership probe, the standalone `lib/trust.py`
  module, and the `tests/test_trust.py` suite that tested it.

Out of scope by design (different policies, not the comment-response
gate): `enforce_pr_issue_state._is_pr_author_org_member` (auto-close
PRs without a ready issue), `review_pr._is_non_member_pr` (non-member
PRs still get the APPROVE/REQUEST_CHANGES review-action gate), and the
prompt-context filtering helpers `org_member_comments_text` /
`review_thread_comments_text` / `all_review_comments_text` (consumed
by skill scripts rather than the webhook).

Co-Authored-By: Oz <oz-agent@warp.dev>

* feat(routing): migrate create-spec/create-implementation triggers to webhook

The Vercel control plane now owns every dispatch path that lands a
`create-spec-from-issue` or `create-implementation-from-issue` run.
`lib.routing._route_issues` was extended to handle:

- `issues.assigned` when oz-agent is the new assignee and the issue
  carries `ready-to-spec` or `ready-to-implement`.
- `issues.labeled` when the added label is one of those lifecycle
  labels and oz-agent is already in the assignees.
- `issue_comment` `@oz-agent` mentions on `ready-to-spec` /
  `ready-to-implement` issues (already migrated; cloud-mode helpers
  in `lib/scripts/create_*_from_issue.py` plus builders/handlers).

`ready-to-implement` wins over `ready-to-spec` so an issue carrying
both labels lands on the implementation workflow rather than
regenerating a spec.

The legacy GitHub Actions adapters that used to fan these triggers
out into the runtime are gone:

- .github/workflows/create-spec-from-issue-local.yml
- .github/workflows/create-spec-from-issue.yml
- .github/workflows/create-implementation-from-issue-local.yml
- .github/workflows/create-implementation-from-issue.yml
- .github/scripts/create_spec_from_issue.py (no remaining consumers)
- .github/scripts/tests/test_issue_workflow_dispatch_inputs.py

`.github/scripts/create_implementation_from_issue.py` is kept on
purpose because `trigger_implementation_on_plan_approved.py` still
imports it for the plan-approved trigger; that flow is a separate
migration.

Webhook test suite: 224 passing (9 new routing cases for the
`issues.assigned` / `issues.labeled` paths).

Co-Authored-By: Oz <oz-agent@warp.dev>

* feat(routing): migrate plan-approved handlers to the webhook

Move the three GitHub Actions workflows that fired on `pull_request_target.labeled`
for the `plan-approved` label onto the Vercel webhook:

- `comment-on-plan-approved.yml` (posts the spec-approved comment on
  the linked issue).
- `remove-stale-issue-labels-on-plan-approved.yml` (strips
  `ready-to-spec` from the linked issue).
- `trigger-implementation-on-plan-approved.yml` (dispatches a
  create-implementation cloud agent run when the linked issue carries
  `ready-to-implement` and `oz-agent` is assigned).

The router exposes a single `WORKFLOW_PLAN_APPROVED` workflow keyed
on `pull_request.labeled` with the `plan-approved` label. The
webhook handler runs `apply_plan_approved_sync` inline (analogous
to `enforce-pr-issue-state`'s sync path) which:

1. Verifies the PR is open and is a spec PR (head branch matches
   `oz-agent/spec-issue-{N}` or every changed file lives under
   `specs/`).
2. Resolves the linked issue via `resolve_issue_number_for_pr`.
3. Posts the spec-approved comment on the linked issue with a
   workflow-prefix metadata marker so retried webhook deliveries
   do not double-post.
4. Removes `ready-to-spec` from the linked issue if present.
5. Returns a structured outcome unless the issue is ready for
   implementation, in which case it returns `None` and the webhook
   falls through to `build_plan_approved_request`. That builder
   reuses `gather_create_implementation_context` and dispatches a
   cloud agent run keyed on `WORKFLOW_PLAN_APPROVED`. The cron-side
   handler is a thin alias around the existing
   `build_create_implementation_handlers` so the apply path
   (`apply_create_implementation_result`) is unchanged.

Behavior tightening: the synchronous helper gates all three side
effects on the spec-only check, so a non-spec PR labeled
`plan-approved` no longer strips `ready-to-spec` off an unrelated
issue. The legacy GHA `remove-stale-issue-labels-on-plan-approved`
workflow stripped the label regardless of spec-only status.

Webhook test suite: 241 passing (+17 new cases for plan-approved
across routing, builder, handler, sync helper, and dispatch
fallthrough).

Deletions:
- .github/workflows/comment-on-plan-approved.yml
- .github/workflows/trigger-implementation-on-plan-approved-local.yml
- .github/workflows/trigger-implementation-on-plan-approved.yml
- .github/workflows/remove-stale-issue-labels-on-plan-approved-local.yml
- .github/workflows/remove-stale-issue-labels-on-plan-approved.yml
- .github/scripts/trigger_implementation_on_plan_approved.py
- .github/scripts/remove_stale_issue_labels_on_plan_approved.py
- .github/scripts/tests/test_trigger_implementation_on_plan_approved.py
- .github/scripts/create_implementation_from_issue.py (last consumer
  was `trigger_implementation_on_plan_approved.py`).

Co-Authored-By: Oz <oz-agent@warp.dev>

* fix(review): load .github/STAKEHOLDERS via the GitHub API on the consuming repo

The cloud-mode `gather_review_context` was loading
`.github/STAKEHOLDERS` from `workspace_path / '.github' / 'STAKEHOLDERS'`.
The Vercel webhook hands in `Path('/tmp')` because the consuming
repository is not checked out on the function's filesystem, so the
file never resolved and non-member-PR review requests silently lost
stakeholder enforcement: no `allowed_logins` filter on the agent's
`recommended_reviewers` list, and no `Stakeholders (from
`.github/STAKEHOLDERS`):` block in the prompt.

The triage cloud path already loaded STAKEHOLDERS via the GitHub API
(`_load_stakeholders_from_repo` in `triage_new_issues.py`); review's
cloud path missed that migration.

Promote the API-backed helpers into `oz_workflows.triage`:
- `STAKEHOLDERS_REPO_PATH = '.github/STAKEHOLDERS'` constant.
- `decode_repo_text_file(repo_handle, path)` — generic text loader
  that wraps `Repository.get_contents` and tolerates missing files,
  directory paths, and GithubException.
- `load_stakeholders_from_repo(repo_handle)` — drop-in API-backed
  counterpart to the workspace-backed `load_stakeholders`.
- Both share `_parse_stakeholders_lines` so the workspace and API
  surfaces produce byte-for-byte identical entries.

Update `gather_review_context` to call the API-backed loader so the
file is read from the repository that triggered the webhook. The
existing private `_load_stakeholders_from_repo` and
`_decode_repo_text_file` in `triage_new_issues.py` become thin
aliases over the moved-out helpers so test fixtures patching those
private names keep working.

Webhook test suite: 249 passing (+8 cases for the API-backed loader).

Co-Authored-By: Oz <oz-agent@warp.dev>

* fix(cloud): resolve repo-local skills and spec context via the GitHub API

The Vercel webhook does not check out the consuming repository, so
the cloud-mode `gather_*_context` helpers that resolved the
repo-local companion skill or the directory-spec context through
`workspace_path = Path('/tmp')` always degraded to empty results:
the prompt lost the repo-local override section, and any issue
without an approved spec PR lost its `specs/GH<N>/` content.

Add API-backed counterparts and wire them into the cloud paths:

- `oz_workflows/repo_local.py`:
  - `repo_local_skill_path_for_dispatch(repo_handle, name)` reads
    `.agents/skills/<name>-local/SKILL.md` via
    `decode_repo_text_file` and returns the repo-relative path
    string when the body is non-empty.
  - `format_repo_local_prompt_section` accepts `Path | str` so
    cloud and legacy callers share one formatter.
- `oz_workflows/helpers.py`:
  - `read_repo_spec_files(repo_handle, issue_number)` mirrors
    `read_local_spec_files` via the API.
  - `resolve_spec_context_for_issue_via_api` /
    `resolve_spec_context_for_pr_via_api` walk both branches
    (approved spec PR + `specs/GH<N>/{product,tech}.md`) without
    touching the local filesystem.

Wire the helpers into the cloud paths:

- `gather_review_context` uses
  `repo_local_skill_path_for_dispatch` for the companion section
  and `resolve_spec_context_for_pr_via_api` for the spec text.
  The legacy subprocess-based `_resolve_spec_context_text_for_pr`
  is replaced with a `_format_spec_context_text` helper that
  renders the resolved dict.
- `gather_pr_comment_context` and
  `gather_create_implementation_context` switch to the API spec
  resolver.
- `build_triage_prompt_for_dispatch` accepts `repo_handle` and
  resolves the `triage-issue-local` and `dedupe-issue-local`
  companions through the API.
- `lib.builders.build_triage_request` passes `repo_handle`
  through.

Webhook test suite: 265 passing (+16 cases for the API-backed
repo-local skill and spec-context helpers).

Co-Authored-By: Oz <oz-agent@warp.dev>

* feat(triage): drop SME identification from triage prompt and skill

The triage agent's SME (subject-matter expert) suggestions were never consumed downstream: there's no `extract_sme_candidates` helper, no surfacing in comments/labels/reviewers, and the only production reference was the schema example inside the prompt.

Remove the goal line, JSON schema field (`sme_candidates`), `Repository Stakeholders` prompt section, and the `stakeholders_text` parameter that fed it from `build_triage_prompt`. Drop `stakeholders_text` from the cloud-mode `TriageContext`, the `gather_triage_context` / `build_triage_prompt_for_dispatch` flow, and the legacy `process_issue` / `main` chain. Update `.agents/skills/triage-issue/SKILL.md`: drop the SME workflow step (and renumber subsequent steps), the STAKEHOLDERS input line, and the `SMEs` reference in the output expectations.

The `oz_workflows.triage` helpers (`load_stakeholders_from_repo`, `format_stakeholders_for_prompt`, `STAKEHOLDERS_REPO_PATH`) and their tests stay in place because `scripts/review_pr.py` still uses them for non-member PR reviewer enforcement.

Co-Authored-By: Oz <oz-agent@warp.dev>

* feat(webhook): add announce-ready-issue handler for unassigned ready-to-X labels

Move the `comment-on-ready-to-implement` / `comment-on-ready-to-spec` GitHub Actions workflows into the Vercel webhook control plane. When a maintainer adds `ready-to-spec` or `ready-to-implement` to an issue without enlisting `oz-agent` as an assignee, the webhook now posts a one-shot announcement comment letting contributors know the issue is open for the matching kind of contribution and that maintainers can tag `@oz-agent` to have the bot pick up the work automatically.

- Add `WORKFLOW_ANNOUNCE_READY_ISSUE` to `lib/routing.py` and split the `issues.labeled` route: oz-agent assigned still routes to create-spec / create-implementation, oz-agent NOT assigned now routes to the new workflow instead of being dropped.

- Add `lib/scripts/announce_ready_issue.py` with `apply_announce_ready_issue_sync`. The handler is fully synchronous (no cloud-agent dispatch fallback), idempotent via the `oz-agent-metadata` marker, and emits label-specific announcement bodies that link back to `@oz-agent` and the `specs/` tree as relevant.

- Wire `sync_announce_ready_issue` into `api/webhook.py` alongside the existing `sync_enforcer` / `sync_plan_approved` paths, special-cased to short-circuit so the workflow never reaches the dispatch code path.

- Add 13 tests covering routing, sync-handler outcomes (announced / noop / skipped), and webhook short-circuit behavior.

- Delete the legacy `.github/workflows/comment-on-ready-to-{implement,spec}.yml` adapters now that the webhook owns this behavior.

Co-Authored-By: Oz <oz-agent@warp.dev>

* fix(review-pr): ensure approved non-member PRs request review

Co-Authored-By: Oz <oz-agent@warp.dev>

* fix(webhook): update ready issue announcement copy

Co-Authored-By: Oz <oz-agent@warp.dev>

* fix(review-pr): approve after dismissing stale app review

Co-Authored-By: Oz <oz-agent@warp.dev>

* fix: make PR reviewer selection deterministic

Co-Authored-By: Oz <oz-agent@warp.dev>

* Add verdict to review.json, request changes, only request review on approve

* refactor: add agent workflow lifecycle abstraction

Co-Authored-By: Oz <oz-agent@warp.dev>

* chore: prune legacy GitHub Actions runtime

Co-Authored-By: Oz <oz-agent@warp.dev>

* chore: remove PR state enforcement checks

Co-Authored-By: Oz <oz-agent@warp.dev>

* fix: address control plane migration regressions

Co-Authored-By: Oz <oz-agent@warp.dev>

* fix: remove synchronous agent entrypoints

Co-Authored-By: Oz <oz-agent@warp.dev>

* refactor: rename workflow packages

Rename the shared oz_workflows package to oz and move executable workflow modules from lib/scripts into lib/workflows. Update imports, documentation, specs, and tests to use the new package layout.

Co-Authored-By: Oz <oz-agent@warp.dev>

* refactor: rename lib package to core

Move the control-plane package from lib to core and update runtime imports, PYTHONPATH configuration, docs, and tests to match the new package layout.

Co-Authored-By: Oz <oz-agent@warp.dev>

* fix: harden workflow apply branch checks

Constrain implementation branch overrides to the expected branch or a delimiter-bounded suffix and restore the one-minute timestamp cushion when checking agent-pushed spec and implementation branches.

Co-Authored-By: Oz <oz-agent@warp.dev>

* fix: remove dedupe issue recency bias

Co-Authored-By: Oz <oz-agent@warp.dev>

---------

Co-authored-by: Oz <oz-agent@warp.dev>
Co-authored-by: Roland Huang <roland@warp.dev>
@captainsafia captainsafia deleted the oz-agent/readme-cleanup branch May 1, 2026 01:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants