Skip to content

ci(harness-bundle): single-dispatch fan-out release for harness + deps - #110

Merged
ytallo merged 4 commits into
mainfrom
feat/harness-bundle-release
May 11, 2026
Merged

ci(harness-bundle): single-dispatch fan-out release for harness + deps#110
ytallo merged 4 commits into
mainfrom
feat/harness-bundle-release

Conversation

@ytallo

@ytallo ytallo commented May 9, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds release-harness-bundle.yml: one harness/v* tag fans out builds + publishes for harness and every in-repo dep whose local manifest version is ahead of the registry. Reuses _rust-binary.yml and _publish-registry.yml exactly as the per-worker release.yml does.
  • Adds create-tag-harness-bundle.yml: one workflow_dispatch bumps harness, pre-bumps deps with source changes since their last tag, and pushes the umbrella harness/v<X.Y.Z> annotated tag.
  • release.yml drops harness/v* from its tag patterns so the bundle workflow owns harness; everything else still uses the per-worker path.
  • Pre-bumps approval-gate, provider-anthropic, provider-openai to 0.2.0 (substantive src changes since 0.1.0).
  • Widens pr-checks metadata_globs to include Cargo.toml so description-only edits don't force a version bump (still strict on src/, build.rs, tests/, etc.).
  • Drops harness.default_config.workers from registry/index.json — the harness binary's WorkerConfig only deserializes engine_url, so the array was dead config.
  • Tracks the new approval-gate sources (build.rs, src/{config,manifest}.rs, skill.md, skills/, tests/{manifest,skill}.rs) and the new harness/crates/harness-types subcrate that the prior commit missed.
  • .gitignore: ignore harness/data/ (sled state).

Test plan

  • CI green: pr-checks matrix passes for every changed worker (the Cargo.toml allowlist relaxation should keep cosmetic-only deps out of strict mode).
  • Dry-run Create harness bundle tag from the Actions tab on this branch with bump_harness=patch and confirm the script computes a sensible publish set without pushing.
  • After merge, run Create harness bundle tag with bump_harness=minor, registry_tag=latest. Confirm the resulting release-harness-bundle.yml run builds 4 workers (harness + the 3 pre-bumped) and POSTs each to https://api.workers.iii.dev/publish with 2xx.
  • iii worker add harness in a clean dir resolves harness 0.3.0, fetches all deps at registered versions, and iii start boots cleanly.

Out-of-scope this PR (stashed locally for a follow-up): acp, iii-database, iii-lsp, image-resize, mcp, storage, skills.

Summary by CodeRabbit

  • New Features

    • Harness meta-worker: unified bundle composing 13+ specialized workers for streamlined install and publishing.
    • Approval-gate: runtime service for pausing and resolving function-call approvals.
    • Public harness types: new shared data models for agent messages, events, functions, and thinking.
  • Chores

    • Standardized Rust runtime lifecycle (build/start) across many workers.
    • Registry updated: added/reshuffled workers; removed subagent.
    • CI/workflows: new harness tag/release automation and tag-creation tooling.
  • Documentation

    • README and harness docs updated to describe harness and registry behavior.

ytallo added 3 commits May 9, 2026 16:46
Companion to 49b5726, which committed the tracked-file portion of the
iii-native restructure but missed these new untracked files. Also picks up
shell/iii.worker.yaml's runtime/scripts metadata to match the other deps.
Adds release-harness-bundle.yml (fires on harness/v* tags, computes the
publish set from harness/iii.worker.yaml deps that have a local version
greater than what is currently registered, fans out builds and publishes
through the existing _rust-binary.yml + _publish-registry.yml reusables)
and create-tag-harness-bundle.yml (one workflow_dispatch that bumps
harness, pre-bumps deps with source changes since their last tag, and
pushes the umbrella harness/v<X.Y.Z> annotated tag).

release.yml drops harness/v* from its tag patterns to avoid
double-publishing — the bundle workflow owns it now. The original
release.yml still serves single-worker releases for everything else.

Pre-bumps approval-gate, provider-anthropic, provider-openai to 0.2.0
since each has substantive src changes since v0.1.0.

ci.yml widens the metadata_globs allowlist with Cargo.toml so
description-only Cargo.toml edits no longer flip pr-checks into strict
mode (still strict on src/, build.rs, tests/, etc.).

registry/index.json drops harness.default_config.workers — the harness
binary's WorkerConfig only deserializes engine_url, so the workers
array was dead config that "iii worker add harness" would persist and
the harness would silently ignore.

.gitignore adds harness/data/ for runtime sled state.
@coderabbitai

coderabbitai Bot commented May 9, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 44bdd604-512b-4130-b0ba-24173c024288

📥 Commits

Reviewing files that changed from the base of the PR and between d862236 and 0087c70.

📒 Files selected for processing (2)
  • harness/crates/harness-types/src/thinking.rs
  • harness/tests/phase_a.rs
💤 Files with no reviewable changes (1)
  • harness/tests/phase_a.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • harness/crates/harness-types/src/thinking.rs

📝 Walkthrough

Walkthrough

Adds harness bundle/release workflows, a harness-types crate with Serde data contracts, refactors approval-gate to use YAML-backed WorkerConfig and a clap/tracing CLI, standardizes worker runtimes/scripts across many workers with III_URL CLI env wiring, updates registry entries and CI release triggers, and removes the subagent worker and its documentation/tests.

Changes

Harness bundle, approval-gate, and standardization

Layer / File(s) Summary
Data Contracts
harness/crates/harness-types/Cargo.toml, harness/crates/harness-types/src/*
Introduces harness-types crate with typed Serde schemas for agent events/messages, content blocks, function lifecycle, streaming events, approval decisions, and thinking budgets.
Approval Gate Refactor
approval-gate/src/config.rs, approval-gate/src/lib.rs, approval-gate/src/main.rs, approval-gate/Cargo.toml, approval-gate/README.md, approval-gate/skills/*, approval-gate/tests/*
Refactors approval-gate to use YAML-backed WorkerConfig; threads state_scope through handlers; adds clap/tracing-based CLI and manifest mode; adds build script; expands docs/skills; updates tests; bumps version to 0.2.0.
Harness Composition
harness/Cargo.toml, harness/build.rs, harness/src/lib.rs, harness/tests/integration.rs, harness/iii.worker.yaml
Adds crates/harness-types to workspace; build.rs parses iii.worker.yaml to generate EXPECTED_WORKERS at compile time; removes integration tests that enforced manual sync; updates Makefile/demo and skills version.
Worker Runtime & CLI Standardization
*/iii.worker.yaml, many */Cargo.toml, many */src/main.rs
Adds runtime.kind: rust and cargo install/start scripts to numerous worker configs; wires III_URL env into CLI args via clap; updates crate descriptions and clap features; bumps some crate versions.
Harness Bundle Workflows
.github/workflows/create-tag-harness-bundle.yml, .github/workflows/release-harness-bundle.yml
Adds create-tag-harness-bundle.yml (manual bump and conditional dependency patch-bumps) and release-harness-bundle.yml (tag-triggered publish set computation, build, and publish jobs).
CI & Release Triggers
.github/workflows/ci.yml, .github/workflows/create-tag.yml, .github/workflows/release.yml
Treats Cargo.toml as metadata in CI worker change bucketing; updates create-tag worker choices and expands release push.tags patterns to include many workers.
Registry & Docs
registry/index.json, README.md, harness/ARCHITECTURE.md, .gitignore
Adds/updates registry entries (acp, harness, iii-database, proof, skills, consolidated shell, etc.), removes subagent/todo-worker variants, documents harness as a meta-worker, and adds harness/data/ and .omx/ to .gitignore.
Subagent Removal
subagent/*
Removes the entire subagent crate (manifests, build script, src, tests), its iii.worker.yaml, and documentation/skill pages.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • iii-hq/workers#63: Both PRs modify the same GitHub Actions workflows (updating .github/workflows/create-tag.yml worker options and release tag patterns to include the "acp" worker).
  • iii-hq/workers#106: The main PR implements the same "tools → functions" and native-harness surface changes (harness types, function_call/event renames, approval-gate switch to function_call, generated EXPECTED_WORKERS, etc.).
  • iii-hq/workers#31: Overlaps shell worker additions and harness integration changes touching the same worker files.

Suggested reviewers

  • andersonleal

Poem

A rabbit drums its paws with care,
Harness bundles float through air;
Types hum steady, approvals wait,
Scripts spin builds at every gate;
Subagent bows — new workflows sing. 🐇✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/harness-bundle-release

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
harness/ARCHITECTURE.md (1)

101-109: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a language to the fenced code block.

The block should declare a language (likely bash) to satisfy markdown lint and improve rendering.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@harness/ARCHITECTURE.md` around lines 101 - 109, The fenced code block
listing demo.sh commands needs a language specifier for markdown linting; update
the opening fence from ``` to ```bash so the block reads as a bash snippet
(affecting the block containing the demo.sh
build/engine/start/verify/web/stop/all lines), ensuring syntax highlighting and
markdown lint compliance.
.github/workflows/ci.yml (1)

74-81: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Cargo.toml is now over-classified as metadata-only.

This downgrades strict PR checks for all manifest-only changes, including dependency/feature/runtime changes that should still require strict version-bump enforcement. Consider key-level handling (e.g., only description, readme, authors/license metadata as soft) instead of file-level classification.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 74 - 81, The current metadata_globs
list treats "Cargo.toml" as a metadata-only change which is too coarse; remove
"Cargo.toml" from metadata_globs and instead add a targeted check in the PR
classification logic that parses Cargo.toml and treats only safe metadata keys
(e.g., description, readme, authors, license) as metadata-only while marking
dependency, version, features, or build/runtime key changes as non-metadata;
update the code that references metadata_globs and the PR gating step to call
this new Cargo.toml key-level validator so manifest edits that affect
dependencies/features still trigger strict checks.
🧹 Nitpick comments (4)
policy-denylist/iii.worker.yaml (1)

9-14: 💤 Low value

LGTM: Standard Rust runtime configuration added.

The runtime and scripts configuration follows a consistent pattern across workers. The cargo build and cargo run commands are appropriate for local development.

If these scripts will be used for production builds, consider using cargo build --release for optimized binaries.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@policy-denylist/iii.worker.yaml` around lines 9 - 14, The runtime config uses
development commands; update the scripts entries 'install' and 'start' to use
release builds for production by replacing 'cargo build' with 'cargo build
--release' and 'cargo run' with 'cargo run --release' (or keep dev commands for
local only), ensuring the 'runtime.kind' remains 'rust' and the YAML keys
'scripts.install' and 'scripts.start' are the ones you modify.
provider-anthropic/crates/auth-credentials/src/lib.rs (1)

18-21: ⚡ Quick win

provider_extra always serializes as null when unset.

Unlike the sibling Option fields, provider_extra: serde_json::Value has no skip_serializing_if, so a serialized OAuth credential always carries "provider_extra": null. Trivial to make consistent.

♻️ Proposed change
-        #[serde(default)]
-        provider_extra: serde_json::Value,
+        #[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
+        provider_extra: serde_json::Value,

The same change applies to provider-openai/crates/auth-credentials/src/lib.rs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@provider-anthropic/crates/auth-credentials/src/lib.rs` around lines 18 - 21,
The provider_extra field currently serializes as null because it is typed as
serde_json::Value with #[serde(default)]; change the field declaration for
provider_extra to Option<serde_json::Value> and add #[serde(default,
skip_serializing_if = "Option::is_none")] so it is omitted when unset; update
the struct where provider_extra is defined (the provider_extra field) in both
auth-credentials modules (provider_extra) to this Option type and serde
attributes to make serialization consistent with the other optional fields.
approval-gate/src/manifest.rs (1)

18-19: 💤 Low value

Good refactor to use WorkerConfig::default() serialization.

This approach is cleaner than hardcoding JSON and ensures the manifest stays in sync with the actual config struct.

Consider logging serialization failures instead of silently falling back to {}, as this could hide configuration issues during development:

📊 Optional improvement for debugging
-        default_config: serde_json::to_value(crate::config::WorkerConfig::default())
-            .unwrap_or_else(|_| serde_json::json!({})),
+        default_config: serde_json::to_value(crate::config::WorkerConfig::default())
+            .unwrap_or_else(|e| {
+                eprintln!("WARN: WorkerConfig serialization failed: {e}");
+                serde_json::json!({})
+            }),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@approval-gate/src/manifest.rs` around lines 18 - 19, The current use of
serde_json::to_value(crate::config::WorkerConfig::default()).unwrap_or_else(|_|
serde_json::json!({})) silently swallows serialization errors; change the
unwrap_or_else closure to capture the error and log it (e.g., using log::error
or tracing::error) including the error details and context about serializing
WorkerConfig::default() before returning the empty object fallback so failures
are visible during development; keep the fallback serde_json::json!({}) to
preserve behavior if desired.
approval-gate/tests/manifest.rs (1)

5-11: ⚖️ Poor tradeoff

Consider a more robust fallback path.

The hardcoded target/debug path may fail for release builds or custom target directories. Consider using CARGO_TARGET_DIR or workspace metadata:

🔧 Suggested improvement
 fn binary_path() -> String {
     if let Some(path) = option_env!("CARGO_BIN_EXE_iii_approval_gate") {
         return path.to_string();
     }
-    let manifest_dir = env!("CARGO_MANIFEST_DIR");
-    format!("{manifest_dir}/target/debug/iii-approval-gate")
+    // Try CARGO_TARGET_DIR, fall back to workspace-relative target/
+    let target_dir = std::env::var("CARGO_TARGET_DIR")
+        .or_else(|_| std::env::var("OUT_DIR").map(|p| {
+            std::path::PathBuf::from(p)
+                .ancestors()
+                .nth(3)
+                .unwrap()
+                .join("debug")
+                .display()
+                .to_string()
+        }))
+        .unwrap_or_else(|_| format!("{}/target/debug", env!("CARGO_MANIFEST_DIR")));
+    format!("{target_dir}/iii-approval-gate")
 }

Alternatively, document that the test requires cargo test to be run from the workspace root, and the fallback is a known limitation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@approval-gate/tests/manifest.rs` around lines 5 - 11, The fallback in
binary_path() is brittle because it assumes "target/debug"; update binary_path
to first try the existing option_env!("CARGO_BIN_EXE_iii_approval_gate"), then
derive the target directory and profile instead of hardcoding "target/debug":
read option_env!("CARGO_TARGET_DIR") (fallback to "target"), read
env!("PROFILE") or option_env!("PROFILE") for "debug"/"release", and format the
path as "{target_dir}/{profile}/iii-approval-gate"; keep the original
CARGO_BIN_EXE check and, if you prefer, add a short comment documenting the
remaining limitation or alternative to require running tests from workspace
root.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/create-tag-harness-bundle.yml:
- Around line 213-245: The workflow currently commits and pushes the bump in the
"Commit bundle bumps" step before verifying tag availability in the "Tag and
push" step; change the order so the tag existence check (using the same
TAG="harness/v${HARNESS_VERSION}" and git rev-parse logic) runs before creating
or pushing the commit, and if the tag already exists exit with an error without
making the commit/push; update/remove the duplicate tag check in "Tag and push"
or keep it as a safety net but ensure the initial pre-commit check prevents
creating the orphaned bump commit when TAG (harness/v${HARNESS_VERSION}) already
exists.

In @.github/workflows/create-tag.yml:
- Around line 12-15: The generic tag workflow currently includes the list item
'harness' which must be removed so harness tags are handled only by
release-harness-bundle.yml; open the step that lists items including
'approval-gate', 'auth-credentials', 'harness', 'hook-fanout' and delete the
'harness' entry so create-tag-harness-bundle.yml and release-harness-bundle.yml
can enforce the dependency pre-bump logic correctly.

In @.github/workflows/release-harness-bundle.yml:
- Around line 44-55: The checkout step is not pinned to the intended tag,
causing the workspace to potentially be on a different commit than the tag
parsed later; update the initial actions/checkout@v4 step to include a ref that
uses the same tag expression as RAW_TAG (e.g. ref: ${{ inputs.tag ||
github.ref_name }}) so the checked-out commit matches the tag, keep fetch-depth:
0 and fetch-tags: true, and remove or keep the subsequent "Refetch annotated
tag" step as optional; ensure the RAW_TAG expression used in the git fetch
matches the ref used for actions/checkout to avoid metadata/code mismatches.

In `@approval-gate/README.md`:
- Line 88: Update the approval::resolve documentation row to use the consistent
identifier name: replace the tuple description "(session_id, tool_call)" with
"(session_id, function_call_id)" (keeping a note that tool_call_id is a legacy
alias only if needed); ensure any adjacent mentions in the same table row or
nearby text reference session_id and function_call_id so the README uses
function_call_id everywhere consistent with the rest of the docs.

In `@harness/ARCHITECTURE.md`:
- Around line 78-80: The document header "The 14 expected workers" and any
sample payload counts referencing 14 must be reconciled with the intro that says
13; update the heading and all sample payload counts to match the actual
EXPECTED_WORKERS value generated from iii.worker.yaml (and included into lib.rs
via build.rs) so the document consistently reflects the real worker count (13) —
search for "EXPECTED_WORKERS", the heading text, and any sample payload blocks
and change "14" to the correct number.

In `@harness/build.rs`:
- Around line 31-37: The worker extraction is too permissive (current chain
using skip_while/take_while/filter/filter_map/map) and treats nested YAML keys
like "    version:" as worker names; tighten the filter that currently checks
starts_with("  ") && contains(':') to require an exact top-level dependency key
pattern — either replace that closure with a regex match like r"^ 
[A-Za-z0-9_-]+:" or explicitly check that the line starts with exactly two
spaces followed immediately by a valid key (alphanumeric, dash or underscore)
and a colon (e.g., ensure line.len() > 2, &line[0..2] == "  ", then validate the
key portion before the colon), so only true top-level dependency entries under
"dependencies:" are captured (adjust the .filter and/or .filter_map around the
current filter_map(|l| l.split(':').next()) accordingly).

In `@harness/crates/harness-types/src/stream_event.rs`:
- Around line 123-131: The test named done_is_terminal is misnamed and only
asserts that AssistantMessageEvent::Stop { stop_reason: StopReason::End, ... }
returns false from is_terminal(), leaving the true path untested; update the
tests to (1) rename the existing test to reflect that Stop is non-terminal
(e.g., stop_is_non_terminal) and (2) add a new test that constructs a terminal
event (for example using AssistantMessageEvent::Done or an
AssistantMessage::default() converted into the terminal variant, or constructing
the variant that represents Done/Error) and asserts is_terminal() returns true;
reference AssistantMessageEvent::Stop, StopReason::End, is_terminal and the
terminal variant (Done/Error/AssistantMessage::default) when making the changes.

---

Outside diff comments:
In @.github/workflows/ci.yml:
- Around line 74-81: The current metadata_globs list treats "Cargo.toml" as a
metadata-only change which is too coarse; remove "Cargo.toml" from
metadata_globs and instead add a targeted check in the PR classification logic
that parses Cargo.toml and treats only safe metadata keys (e.g., description,
readme, authors, license) as metadata-only while marking dependency, version,
features, or build/runtime key changes as non-metadata; update the code that
references metadata_globs and the PR gating step to call this new Cargo.toml
key-level validator so manifest edits that affect dependencies/features still
trigger strict checks.

In `@harness/ARCHITECTURE.md`:
- Around line 101-109: The fenced code block listing demo.sh commands needs a
language specifier for markdown linting; update the opening fence from ``` to
```bash so the block reads as a bash snippet (affecting the block containing the
demo.sh build/engine/start/verify/web/stop/all lines), ensuring syntax
highlighting and markdown lint compliance.

---

Nitpick comments:
In `@approval-gate/src/manifest.rs`:
- Around line 18-19: The current use of
serde_json::to_value(crate::config::WorkerConfig::default()).unwrap_or_else(|_|
serde_json::json!({})) silently swallows serialization errors; change the
unwrap_or_else closure to capture the error and log it (e.g., using log::error
or tracing::error) including the error details and context about serializing
WorkerConfig::default() before returning the empty object fallback so failures
are visible during development; keep the fallback serde_json::json!({}) to
preserve behavior if desired.

In `@approval-gate/tests/manifest.rs`:
- Around line 5-11: The fallback in binary_path() is brittle because it assumes
"target/debug"; update binary_path to first try the existing
option_env!("CARGO_BIN_EXE_iii_approval_gate"), then derive the target directory
and profile instead of hardcoding "target/debug": read
option_env!("CARGO_TARGET_DIR") (fallback to "target"), read env!("PROFILE") or
option_env!("PROFILE") for "debug"/"release", and format the path as
"{target_dir}/{profile}/iii-approval-gate"; keep the original CARGO_BIN_EXE
check and, if you prefer, add a short comment documenting the remaining
limitation or alternative to require running tests from workspace root.

In `@policy-denylist/iii.worker.yaml`:
- Around line 9-14: The runtime config uses development commands; update the
scripts entries 'install' and 'start' to use release builds for production by
replacing 'cargo build' with 'cargo build --release' and 'cargo run' with 'cargo
run --release' (or keep dev commands for local only), ensuring the
'runtime.kind' remains 'rust' and the YAML keys 'scripts.install' and
'scripts.start' are the ones you modify.

In `@provider-anthropic/crates/auth-credentials/src/lib.rs`:
- Around line 18-21: The provider_extra field currently serializes as null
because it is typed as serde_json::Value with #[serde(default)]; change the
field declaration for provider_extra to Option<serde_json::Value> and add
#[serde(default, skip_serializing_if = "Option::is_none")] so it is omitted when
unset; update the struct where provider_extra is defined (the provider_extra
field) in both auth-credentials modules (provider_extra) to this Option type and
serde attributes to make serialization consistent with the other optional
fields.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 58b290da-8713-4797-aa96-11172c044654

📥 Commits

Reviewing files that changed from the base of the PR and between 755da99 and d862236.

⛔ Files ignored due to path filters (5)
  • approval-gate/Cargo.lock is excluded by !**/*.lock
  • harness/Cargo.lock is excluded by !**/*.lock
  • provider-anthropic/Cargo.lock is excluded by !**/*.lock
  • provider-openai/Cargo.lock is excluded by !**/*.lock
  • subagent/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (94)
  • .github/workflows/ci.yml
  • .github/workflows/create-tag-harness-bundle.yml
  • .github/workflows/create-tag.yml
  • .github/workflows/release-harness-bundle.yml
  • .github/workflows/release.yml
  • .gitignore
  • README.md
  • approval-gate/Cargo.toml
  • approval-gate/README.md
  • approval-gate/build.rs
  • approval-gate/iii.worker.yaml
  • approval-gate/skill.md
  • approval-gate/skills/list_pending.md
  • approval-gate/skills/policy_approval_gate.md
  • approval-gate/skills/resolve.md
  • approval-gate/src/config.rs
  • approval-gate/src/lib.rs
  • approval-gate/src/main.rs
  • approval-gate/src/manifest.rs
  • approval-gate/tests/integration.rs
  • approval-gate/tests/manifest.rs
  • approval-gate/tests/skill.rs
  • auth-credentials/Cargo.toml
  • auth-credentials/iii.worker.yaml
  • harness/ARCHITECTURE.md
  • harness/Cargo.toml
  • harness/Makefile
  • harness/README.md
  • harness/build.rs
  • harness/crates/harness-types/Cargo.toml
  • harness/crates/harness-types/src/agent_event.rs
  • harness/crates/harness-types/src/agent_message.rs
  • harness/crates/harness-types/src/content.rs
  • harness/crates/harness-types/src/function.rs
  • harness/crates/harness-types/src/lib.rs
  • harness/crates/harness-types/src/stream_event.rs
  • harness/crates/harness-types/src/thinking.rs
  • harness/iii.worker.yaml
  • harness/scripts/demo.sh
  • harness/src/lib.rs
  • harness/tests/integration.rs
  • hook-fanout/Cargo.toml
  • hook-fanout/iii.worker.yaml
  • hook-fanout/src/main.rs
  • llm-budget/Cargo.toml
  • llm-budget/iii.worker.yaml
  • llm-budget/src/main.rs
  • models-catalog/Cargo.toml
  • models-catalog/iii.worker.yaml
  • oauth-anthropic/Cargo.toml
  • oauth-anthropic/iii.worker.yaml
  • oauth-openai-codex/Cargo.toml
  • oauth-openai-codex/iii.worker.yaml
  • policy-denylist/Cargo.toml
  • policy-denylist/iii.worker.yaml
  • provider-anthropic/Cargo.toml
  • provider-anthropic/crates/auth-credentials/Cargo.toml
  • provider-anthropic/crates/auth-credentials/src/lib.rs
  • provider-anthropic/crates/overflow-classify/src/lib.rs
  • provider-anthropic/iii.worker.yaml
  • provider-anthropic/src/main.rs
  • provider-openai/Cargo.toml
  • provider-openai/crates/auth-credentials/Cargo.toml
  • provider-openai/crates/auth-credentials/src/lib.rs
  • provider-openai/crates/overflow-classify/src/lib.rs
  • provider-openai/iii.worker.yaml
  • provider-openai/src/main.rs
  • provider-router/Cargo.toml
  • provider-router/iii.worker.yaml
  • provider-router/src/register.rs
  • registry/index.json
  • session-inbox/Cargo.toml
  • session-inbox/iii.worker.yaml
  • session-tree/Cargo.toml
  • session-tree/iii.worker.yaml
  • session-tree/src/main.rs
  • shell/Cargo.toml
  • shell/iii.worker.yaml
  • shell/src/main.rs
  • subagent/Cargo.toml
  • subagent/README.md
  • subagent/build.rs
  • subagent/iii.worker.yaml
  • subagent/skill.md
  • subagent/skills/start.md
  • subagent/src/config.rs
  • subagent/src/lib.rs
  • subagent/src/main.rs
  • subagent/src/register.rs
  • subagent/src/start.rs
  • subagent/tests/integration.rs
  • subagent/tests/manifest.rs
  • turn-orchestrator/Cargo.toml
  • turn-orchestrator/iii.worker.yaml
💤 Files with no reviewable changes (14)
  • subagent/skill.md
  • subagent/build.rs
  • subagent/iii.worker.yaml
  • subagent/src/lib.rs
  • subagent/src/register.rs
  • subagent/src/start.rs
  • subagent/skills/start.md
  • subagent/tests/integration.rs
  • subagent/Cargo.toml
  • subagent/src/main.rs
  • subagent/src/config.rs
  • harness/tests/integration.rs
  • subagent/README.md
  • subagent/tests/manifest.rs

Comment on lines +213 to +245
- name: Commit bundle bumps
env:
HARNESS_VERSION: ${{ steps.compute.outputs.harness_version }}
BUMPED_COUNT: ${{ steps.compute.outputs.bumped_count }}
run: |
git add -A
if git diff --cached --quiet; then
echo "::error::no changes to commit"
exit 1
fi
git commit -m "chore(harness-bundle): release harness v${HARNESS_VERSION} (+${BUMPED_COUNT} deps)"
git push origin main

- name: Tag and push
env:
HARNESS_VERSION: ${{ steps.compute.outputs.harness_version }}
REGISTRY_TAG: ${{ inputs.registry_tag }}
BUMPED: ${{ steps.compute.outputs.bumped }}
run: |
TAG="harness/v${HARNESS_VERSION}"
if git rev-parse "$TAG" >/dev/null 2>&1; then
echo "::error::tag $TAG already exists"
exit 1
fi
git tag -a "$TAG" -m "Release harness bundle ${TAG}

worker: harness
version: ${HARNESS_VERSION}
registry-tag: ${REGISTRY_TAG}
bumped-deps: ${BUMPED}
"
git push origin "$TAG"
echo "::notice::pushed $TAG (registry-tag=$REGISTRY_TAG)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Check tag availability before pushing the bump commit.

Right now the workflow writes to main first and only then discovers that harness/v${HARNESS_VERSION} already exists. That leaves a partial release commit behind with no matching umbrella tag.

Suggested ordering change
+      - name: Check tag does not exist
+        env:
+          HARNESS_VERSION: ${{ steps.compute.outputs.harness_version }}
+        run: |
+          TAG="harness/v${HARNESS_VERSION}"
+          if git rev-parse "$TAG" >/dev/null 2>&1; then
+            echo "::error::tag $TAG already exists"
+            exit 1
+          fi
+
       - name: Commit bundle bumps
         env:
           HARNESS_VERSION: ${{ steps.compute.outputs.harness_version }}
           BUMPED_COUNT: ${{ steps.compute.outputs.bumped_count }}
         run: |
@@
-      - name: Tag and push
+      - name: Tag and push
         env:
           HARNESS_VERSION: ${{ steps.compute.outputs.harness_version }}
           REGISTRY_TAG: ${{ inputs.registry_tag }}
           BUMPED: ${{ steps.compute.outputs.bumped }}
         run: |
           TAG="harness/v${HARNESS_VERSION}"
-          if git rev-parse "$TAG" >/dev/null 2>&1; then
-            echo "::error::tag $TAG already exists"
-            exit 1
-          fi
           git tag -a "$TAG" -m "Release harness bundle ${TAG}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/create-tag-harness-bundle.yml around lines 213 - 245, The
workflow currently commits and pushes the bump in the "Commit bundle bumps" step
before verifying tag availability in the "Tag and push" step; change the order
so the tag existence check (using the same TAG="harness/v${HARNESS_VERSION}" and
git rev-parse logic) runs before creating or pushing the commit, and if the tag
already exists exit with an error without making the commit/push; update/remove
the duplicate tag check in "Tag and push" or keep it as a safety net but ensure
the initial pre-commit check prevents creating the orphaned bump commit when TAG
(harness/v${HARNESS_VERSION}) already exists.

Comment on lines +12 to +15
- approval-gate
- auth-credentials
- harness
- hook-fanout

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove harness from the generic tag workflow.

Selecting harness here only bumps the harness manifest, but harness/v* now fans out through release-harness-bundle.yml. That bypasses the new dependency pre-bump logic in create-tag-harness-bundle.yml, so changed in-repo deps can be silently skipped from the bundle publish.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/create-tag.yml around lines 12 - 15, The generic tag
workflow currently includes the list item 'harness' which must be removed so
harness tags are handled only by release-harness-bundle.yml; open the step that
lists items including 'approval-gate', 'auth-credentials', 'harness',
'hook-fanout' and delete the 'harness' entry so create-tag-harness-bundle.yml
and release-harness-bundle.yml can enforce the dependency pre-bump logic
correctly.

Comment on lines +44 to +55
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true

- name: Refetch annotated tag
env:
RAW_TAG: ${{ inputs.tag || github.ref_name }}
run: |
set -euo pipefail
git fetch origin "+refs/tags/${RAW_TAG}:refs/tags/${RAW_TAG}" || true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/release-harness-bundle.yml

Repository: iii-hq/workers

Length of output: 9957


Pin checkout to requested harness tag to prevent code-metadata mismatch.

The initial checkout step doesn't use the inputs.tag input, so the workspace may be on a different commit than the tag whose metadata is being parsed. When this workflow is triggered by push to a tag, the checkout uses the default branch instead of the tag. When triggered manually with a specific tag, checkout again defaults to the branch. The later metadata parsing and build steps then operate on mismatched code, potentially building and publishing a different revision than intended.

Suggested fix
       - uses: actions/checkout@v4
         with:
           fetch-depth: 0
           fetch-tags: true
+          ref: ${{ inputs.tag || github.ref_name }}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release-harness-bundle.yml around lines 44 - 55, The
checkout step is not pinned to the intended tag, causing the workspace to
potentially be on a different commit than the tag parsed later; update the
initial actions/checkout@v4 step to include a ref that uses the same tag
expression as RAW_TAG (e.g. ref: ${{ inputs.tag || github.ref_name }}) so the
checked-out commit matches the tag, keep fetch-depth: 0 and fetch-tags: true,
and remove or keep the subsequent "Refetch annotated tag" step as optional;
ensure the RAW_TAG expression used in the git fetch matches the ref used for
actions/checkout to avoid metadata/code mismatches.

Comment thread approval-gate/README.md
| Function | Role |
|---|---|
| `policy::approval_gate` | Subscriber body + `durable:subscriber` trigger on `topic`. |
| `approval::resolve` | Operator decision (`allow` / `deny`) for one pending `(session_id, tool_call)`. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Align identifier terminology with function_call_id to avoid API confusion.

At Line 88, (session_id, tool_call) is inconsistent with the rest of the docs that use function_call_id (with tool_call_id only as a legacy alias). Recommend changing this row to (session_id, function_call_id) for consistency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@approval-gate/README.md` at line 88, Update the approval::resolve
documentation row to use the consistent identifier name: replace the tuple
description "(session_id, tool_call)" with "(session_id, function_call_id)"
(keeping a note that tool_call_id is a legacy alias only if needed); ensure any
adjacent mentions in the same table row or nearby text reference session_id and
function_call_id so the README uses function_call_id everywhere consistent with
the rest of the docs.

Comment thread harness/ARCHITECTURE.md
Comment on lines +78 to +80
### 3. The 14 expected workers

`EXPECTED_WORKERS` (`lib.rs:18`) is the source of truth for what the harness assumes is on the bus. Grouped by role:
`EXPECTED_WORKERS` (generated by `build.rs` from `iii.worker.yaml`, included into `lib.rs`) is the source of truth for what the harness assumes is on the bus. Grouped by role:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Worker count is inconsistent within the document.

This section still references 14 workers, while the intro states 13. Please align the heading and the sample payload count so readers don’t get conflicting bundle expectations.

Also applies to: 160-160

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@harness/ARCHITECTURE.md` around lines 78 - 80, The document header "The 14
expected workers" and any sample payload counts referencing 14 must be
reconciled with the intro that says 13; update the heading and all sample
payload counts to match the actual EXPECTED_WORKERS value generated from
iii.worker.yaml (and included into lib.rs via build.rs) so the document
consistently reflects the real worker count (13) — search for
"EXPECTED_WORKERS", the heading text, and any sample payload blocks and change
"14" to the correct number.

Comment thread harness/build.rs
Comment on lines +31 to +37
.skip_while(|l| !l.starts_with("dependencies:"))
.skip(1)
.take_while(|l| l.is_empty() || l.starts_with(' ') || l.starts_with('\t'))
.filter(|l| l.starts_with(" ") && l.contains(':'))
.filter_map(|l| l.split(':').next())
.map(|name| name.trim().to_string())
.filter(|name| !name.is_empty())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Worker extraction is too permissive and can parse nested YAML keys as worker names.

With the current filters, nested lines like version: can be interpreted as workers. Restrict parsing to exactly top-level dependency entries under dependencies: (e.g., exact two-space indentation + valid worker-name key pattern).

Suggested hardening
-        .filter(|l| l.starts_with("  ") && l.contains(':'))
-        .filter_map(|l| l.split(':').next())
-        .map(|name| name.trim().to_string())
+        .filter_map(|l| {
+            let rest = l.strip_prefix("  ")?;
+            if rest.starts_with(' ') || rest.starts_with('\t') {
+                return None; // nested key, not a dependency name
+            }
+            let (name, _) = rest.split_once(':')?;
+            let name = name.trim();
+            if name.is_empty() {
+                return None;
+            }
+            if name
+                .chars()
+                .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
+            {
+                Some(name.to_string())
+            } else {
+                None
+            }
+        })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.skip_while(|l| !l.starts_with("dependencies:"))
.skip(1)
.take_while(|l| l.is_empty() || l.starts_with(' ') || l.starts_with('\t'))
.filter(|l| l.starts_with(" ") && l.contains(':'))
.filter_map(|l| l.split(':').next())
.map(|name| name.trim().to_string())
.filter(|name| !name.is_empty())
.skip_while(|l| !l.starts_with("dependencies:"))
.skip(1)
.take_while(|l| l.is_empty() || l.starts_with(' ') || l.starts_with('\t'))
.filter_map(|l| {
let rest = l.strip_prefix(" ")?;
if rest.starts_with(' ') || rest.starts_with('\t') {
return None; // nested key, not a dependency name
}
let (name, _) = rest.split_once(':')?;
let name = name.trim();
if name.is_empty() {
return None;
}
if name
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
{
Some(name.to_string())
} else {
None
}
})
.filter(|name| !name.is_empty())
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@harness/build.rs` around lines 31 - 37, The worker extraction is too
permissive (current chain using skip_while/take_while/filter/filter_map/map) and
treats nested YAML keys like "    version:" as worker names; tighten the filter
that currently checks starts_with("  ") && contains(':') to require an exact
top-level dependency key pattern — either replace that closure with a regex
match like r"^  [A-Za-z0-9_-]+:" or explicitly check that the line starts with
exactly two spaces followed immediately by a valid key (alphanumeric, dash or
underscore) and a colon (e.g., ensure line.len() > 2, &line[0..2] == "  ", then
validate the key portion before the colon), so only true top-level dependency
entries under "dependencies:" are captured (adjust the .filter and/or
.filter_map around the current filter_map(|l| l.split(':').next()) accordingly).

Comment on lines +123 to +131
#[test]
fn done_is_terminal() {
let ev = AssistantMessageEvent::Stop {
stop_reason: StopReason::End,
error_message: None,
error_kind: None,
};
assert!(!ev.is_terminal());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Misnamed test; no positive coverage for is_terminal true case.

done_is_terminal actually constructs a Stop variant and asserts !is_terminal(), i.e. it verifies "Stop is non-terminal". The Done/Error → true path is never exercised.

🧪 Proposed fix
-    #[test]
-    fn done_is_terminal() {
-        let ev = AssistantMessageEvent::Stop {
-            stop_reason: StopReason::End,
-            error_message: None,
-            error_kind: None,
-        };
-        assert!(!ev.is_terminal());
-    }
+    #[test]
+    fn stop_is_not_terminal() {
+        let ev = AssistantMessageEvent::Stop {
+            stop_reason: StopReason::End,
+            error_message: None,
+            error_kind: None,
+        };
+        assert!(!ev.is_terminal());
+    }
+
+    #[test]
+    fn done_and_error_are_terminal() {
+        let msg = AssistantMessage::default();
+        assert!(AssistantMessageEvent::Done { message: msg.clone() }.is_terminal());
+        assert!(AssistantMessageEvent::Error { error: msg }.is_terminal());
+    }

(Adjust AssistantMessage::default() to whatever construction is available in this crate.)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@harness/crates/harness-types/src/stream_event.rs` around lines 123 - 131, The
test named done_is_terminal is misnamed and only asserts that
AssistantMessageEvent::Stop { stop_reason: StopReason::End, ... } returns false
from is_terminal(), leaving the true path untested; update the tests to (1)
rename the existing test to reflect that Stop is non-terminal (e.g.,
stop_is_non_terminal) and (2) add a new test that constructs a terminal event
(for example using AssistantMessageEvent::Done or an AssistantMessage::default()
converted into the terminal variant, or constructing the variant that represents
Done/Error) and asserts is_terminal() returns true; reference
AssistantMessageEvent::Stop, StopReason::End, is_terminal and the terminal
variant (Done/Error/AssistantMessage::default) when making the changes.

clippy --all-targets caught:
  - harness-types::ThinkingLevel had a manual Default impl that clippy::derivable_impls flags; switched to derive(Default) + #[default] on Off.
  - tests/phase_a.rs imported turn_orchestrator::persistence directly, but the prior commit removed turn-orchestrator from harness dev-dependencies. Delete the test (the workflow it covers no longer matches the trimmed dev-dep set; resurrect via a process-level test if needed).
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.

1 participant