sync(stella): Gemini/Vertex/Bedrock providers, custom tools, skills from monorepo - #4
Merged
Merged
Conversation
Ports three new Provider adapters from the monorepo (oxagen-model), fully renamed oxagen -> stella: - gemini.rs: Google native generateContent (gemini-functions dialect, thinking level, thought-signature round-trips, cached-token accounting). - vertex.rs: Vertex AI enterprise surface sharing gemini's wire shape behind OAuth bearer auth and project/location-scoped addressing. - bedrock.rs: Amazon Bedrock Converse with a pure-Rust SigV4 signer, pinned by botocore golden vectors. Wiring: declared in stella-model/src/lib.rs; added hmac + sha2 to stella-model/Cargo.toml and the root [workspace.dependencies] (SigV4 uses hex, not base64 — no base64/time deps needed). credential.rs and the existing catalog rows are untouched; stella-protocol already exports every type the adapters need (ReasoningEffort included). The adapters are catalog-independent library primitives; CLI provider-selection wiring is a separate follow-up. cargo check -p stella-model: clean. cargo test -p stella-model: 99 passed, 0 failed (incl. 10 gemini, 14 bedrock with 7 SigV4 golden-vector tests). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ports oxagen-tools/custom.rs -> stella-tools/custom.rs (renamed oxagen -> stella). Custom tools are TOML manifests discovered under .stella/tools/ (workspace) and ~/.config/stella/tools/ (user-global) that shell out to a command, giving developers a low-ceremony floor beneath the MCP tool set. CustomToolSet composes over an inner ToolExecutor. Wiring: declared in stella-tools/src/lib.rs; added toml to stella-tools [dependencies] (used by manifest parsing) and tempfile to [dev-dependencies] + the root [workspace.dependencies]. The two ToolSchema literals gained read_only: false to match stella-protocol's ToolSchema, which carries a read_only field the source predated (custom tools shell out, so they are always mutating). cargo test -p stella-tools: 82 passed, 0 failed (29 custom::). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ports oxagen-core/skills.rs -> stella-core/skills.rs (renamed oxagen -> stella, paths .oxagen/skills -> .stella/skills and ~/.config/oxagen -> ~/.config/stella). Pure, I/O-free logic mirroring the rules module: skill discovery via the injectable SkillSource port, frontmatter parsing (reuses crate::rules::parse_frontmatter), selection against a token budget, auto-creation decisions, and skill-candidate mining from observations. Wiring: declared in stella-core/src/lib.rs with the full public re-export block (Skill, SkillSource, LoadSkillsOptions, load_skills, select_skills, mine_skill_candidates, render_skills_section, decide_auto_creation, etc.). No new dependencies — only std plus the existing crate::rules seam. cargo test -p stella-core: 199 passed, 0 failed (35 skills::). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Additively wires the new native adapters into provider selection (no existing provider's behavior changes): - catalog.rs: adds ToolDialect::GeminiFunctions + BedrockConverse; a gemini-3-pro row on provider "vertex" and the Bedrock inference-profile row (us.anthropic.claude-sonnet-4-5), both with Pricing; a provider-scoped resolve_for(); dedup keyed on (provider, id) since gemini-3-pro now spans gemini + vertex; and test coverage for all of it. - config.rs: appends vertex + bedrock ProviderConfig rows at the END of PROVIDERS so credential auto-detection never prefers them over an explicitly-configured provider (AWS_ACCESS_KEY_ID is commonly present for unrelated reasons). Both are openai_compatible = false. - agent.rs build_provider: switches the catalog check to resolve_for (provider-scoped) and adds vertex + bedrock construction arms that read their extra addressing/credentials from std::env (VERTEX_PROJECT_ID/ LOCATION, AWS_SECRET_ACCESS_KEY/SESSION_TOKEN/REGION) with named errors. No oxagen-mcp / oxagen-context involved. Gemini deliberately stays on its OpenAI-compat shim (unchanged) — the shim->native flip is a behavior change to a working provider that can't be verified here without a live GEMINI_API_KEY, so it is deferred. cargo clippy --workspace --all-targets -- -D warnings: clean. cargo test: stella-model 100 passed, stella-cli config 7 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fixes a stray pre-existing oxagen-rust-cli spec-path reference in the catalog module doc, encountered while adding the Vertex/Bedrock rows. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds two build_provider routing tests keyed on Provider::id() (construction only — no network call): - existing_providers_still_route_to_their_current_adapter: regression guard that the resolve_for switch + (provider, id) dedup + inserted vertex/ bedrock arms did not change selection for the existing providers — openai keeps its Responses-API adapter ahead of the openai_compatible flag, every OpenAI-compatible provider (zai/xai/deepseek/gemini/openrouter) still routes to the ZaiProvider shim, anthropic keeps the Messages adapter. - vertex_and_bedrock_route_to_their_native_adapters_not_a_fallthrough: asserts vertex -> VertexProvider and bedrock -> BedrockProvider (not the shim/anthropic fall-through), and that a vertex selection missing VERTEX_PROJECT_ID is a named error rather than a silent fall-through. cargo test -p stella-cli: 9 passed. clippy -p stella-cli --all-targets -D warnings: clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
macanderson
marked this pull request as ready for review
July 12, 2026 01:25
macanderson
added a commit
that referenced
this pull request
Jul 23, 2026
…ads as "no changes" (#352) * test(pipeline): loop-invariant chaos harness — and the 7th death it found The harness enumerates the provider x isolation x class x model x witness x budget x bypass cross-product (288 scenarios) and asserts two invariants on EVERY one: (1) named termination — a run ends only by Ok(outcome) or a typed Err, an Ok(Aborted) carries a non-empty reason, and a terminal Complete/Error reaches the stream; (2) never choose nothing — a run that executed zero worker turns and did not complete may only be a RECOGNIZED cause (budget, scope-user-abort, unresolvable provider, or a deliberate witness artifact-integrity fail-closed), never a silent setup death. Deterministic, milliseconds, and a break names the exact scenario. Building it earned its keep immediately. My first cut was vacuous — it inferred "worker executed" from transcript length, but run() appends the user message before execution, so the check never fired. Fixed to key on the worker's StepUsage event, the harness went red on a 7th zero-work death that #345 had missed: witness AUTHORING failures (no TEST_COMMAND, an unusable command, a test that proves nothing, the author engine getting stuck) aborted the whole task. The task needs no witness to proceed — so these now degrade to a bare worker run. Only witness artifact-INTEGRITY violations (the author modified tracked files, produced a non-single-file/symlink artifact, or a runner/identity mismatch) stay fail-closed, surfacing the problem instead of silently completing unverified. witness_stage now returns a typed WitnessAbort { reason, degradable } to draw that line. Tests reconciled: the still-passes and metering cases degrade (author+repair still individually metered); the production-edit security case is scripted to reach the tracked-mutation check and stays fail-closed. Signed-off-by: macanderson <mac@oxagen.sh> * feat(pipeline): verification cannot lie — a blind empty diff never reads as "no changes" Principle #4 of the loop-hardening thread, and the scariest failure this codebase hit: a wrong verification signal does not merely fail, it MISDIRECTS. On Terminal-Bench the judge was handed an empty diff and concluded "no changes were made to the repository" while the agent's files sat on disk — the agent's rational response was to reinitialize git to make the signal non-empty. An empty diff is ambiguous: the agent genuinely changed nothing, OR the diff machinery is blind (work committed, a baseline miss, an uncaptured file). The pipeline already carries the signal that resolves it — file_changes, the count of FileChange events the turn emitted. When that is positive but the gathered diff is empty, verification_honest_diff replaces the bare empty string with an explicit "the tree changed but the diff could not be captured — this is NOT evidence that nothing changed" marker. Applied at both diff-gather points (initial verify and post-revise), so every downstream consumer — the model judge, the distress-guidance call, the recorded evidence — sees a "couldn't verify", never a false "verified nothing". The judge's evidence summary now also states the file-change count explicitly. This is the confidence notion the principle calls for: distinguish "verified false" from "couldn't verify" so a blind signal can never be read as a negative one. The deterministic paths were already honest here (the zero-diff guard keyed on file_changes, not the diff). Tests pin the pure guard: a blind empty diff with file changes reports "uncaptured" and forecloses the "no changes" reading; a truly empty diff with zero file changes stays empty (no invented changes); a real diff passes through untouched. Signed-off-by: macanderson <mac@oxagen.sh> --------- Signed-off-by: macanderson <mac@oxagen.sh>
This was referenced Aug 3, 2026
macanderson
added a commit
that referenced
this pull request
Aug 7, 2026
… stacked behind one clippy error (#2000) ## What & why `main` was red at `e0fbbe02` on **five distinct breaks stacked behind one another**. CI's log showed only the first, because both compile-tier gates report one unit at a time: clippy stops at the first *hard* error in a crate, and `cargo doc --workspace` stops at the first crate that fails to document. That is why this is the sixth consecutive unbreak PR — each one can only reveal the next layer. Root cause tracked in #1986 (`ci.yml` does not run on a push to `main`); evidence from this session added there. ### 1. `pipeline/scope_stage.rs:34` — dead `spend` local ``` error: variable does not need to be mutable error: unused variable: `spend` ``` `plan_with_review` binds `let mut spend = Spend { budget, total };` and never reads it — the re-planning loop builds a fresh `Spend` by reborrowing on each iteration, which is the only construction the code uses. Dead since #1971, unmasked when #1985 cleared the `plan_stage` arg-count error above it. ### 2. `management_prompt/tests.rs` — `ModelCallRole::Research` listed twice `unreachable_patterns`. Kept the documented placement beside `Unknown`, whose comment explains why `Research` never reaches the chokepoint; dropped the copy appended after `Summarization`. ### 3. `verification_hardening.rs` — three items nothing constructs `dead_code` ×3 on `SHELL_TOOL`, `shell_call_result`, `PassingShell`. The child `flip_halt_arming` module defines its own, which shadow the parent's through `use super::*` — a glob import loses to a local definition silently, so this was never a name clash, just quietly unreachable code. The child's are the live pair *and* the newer one: a per-command `call_id: format!("call-shell-{command}")` that `FlipHalt` correlates on, versus the parent's fixed `"call-shell"` which cannot distinguish two shell calls. So the parent's stale copies go. Its `mod` doc claimed the child existed in order to reach the parent's fakes — the pre-split rationale, now false — and is rewritten to point at the child's own doc, where the anti-clobber reason for colocating them lives (#1997). **#2 and #3 are the same shape**: a merge landed the same addition twice. Neither side conflicts textually, so review saw nothing. ### 4. `stella-protocol/src/event.rs` — unresolved intra-doc link `AgentEvent::Compaction::rewrites` documents itself with `[`CompactionRewrite`]`, but `event.rs` never imports the type (the field spells it `crate::CompactionRewrite` inline), so `broken_intra_doc_links` failed `doc-warnings`. A *different gate step* from #1–#3, invisible while clippy was red. Fourth recurrence of the shape #1986 tracks. ### 5. `file-size` — two ceilings exceeded on `main` `driver.rs` at 2572/2571 and `pipeline/tests.rs` at 2537/2536. **Neither file is touched by this branch**; both were grown on `main` by merges that did not regenerate the baseline. That mechanism is #2004. Also regenerated `docs/wire/*` — the protocol types' doc comments *are* that contract, so break #4's fix mechanically changed the emitted `description`. ## About the two raised ceilings A raised ceiling is normally a defect, so this is stated plainly rather than buried: `make file-size-update` moved `driver.rs` and `pipeline/tests.rs` up by one line each, for growth **this branch did not author**, because the growth has already landed on `main` and reverting another PR's line is outside this task. The alternative was leaving the gate red. The same regeneration also **tightens** `pipeline.rs` from 3451 to 3181 — a 270-line shrink the baseline had not captured. This branch adds no lines to any god file. A maintainer who would rather see those two lines pushed into submodules should say so; that is their call, not mine. ## The witness - [x] No witness test. Four of the five are dead code, a duplicate match arm, and a doc link — no runtime behavior exists to witness, and the compiler is the oracle. The fifth is a generated baseline. Per CONTRIBUTING's carve-out for changes with no behavior delta, here is how it was verified instead: - `cargo clippy --workspace --all-targets -- -D warnings` — fails on `main` at break #1, exits 0 here. - `RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps` — fails on `main` at break #4, exits 0 here. - `cargo fmt --all --check` — exits 0. - `cargo test -p stella-pipeline` — 618 pass, 0 fail. That suite **did not compile at all** on `main` (break #3), so these tests were not running. - `scripts/check-file-size.sh` and `make wire-schema` — both exit 0. Exit codes were read from cargo directly, not through a pipe: `cargo … | tail` reports *tail's* status, which is always 0, and cargo colorizes `error` so a plain `^error` grep matches nothing. Both produced a false green in this session before being corrected. ## The gate - [x] `file-size` and `god-files` pass; baseline regenerated, never hand-edited. - [x] `wire-schema` passes; the diff is comment-only — no field added, removed, renamed or re-tagged, and no optional field made required, so the additive-only contract holds. - [x] No behavior change, no new flags, no new dependencies. ## Nothing left behind - **#2013** (filed) — sharing `CARGO_TARGET_DIR` between worktrees produces compile errors naming symbols that do not exist. Hit during this work: a phantom `E0004` for `AgentEvent::TurnParked`/`TurnWoken`, variants present in neither checkout, because a parallel job's build was linked in. Nearly caused a wrong "fix". - **#1986** — commented with the full five-layer breakdown as evidence for fixing the trigger rather than the instances. - **#2004** — owns the file-size baseline skew behind break #5. Refs #1972, #1986, #1997, #2004, #2013
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings the cleanly-portable delta from the monorepo (
origin/main,oxagen-*crates) across to stella-cli, fully renamedoxagen_→stella_. Additive throughout — no existing behavior changed, no crate removed. Gemini/Vertex/Bedrock ship wired and selectable, not as inert library primitives.What was ported
1. Provider adapters →
stella-modelgemini.rs— Google nativegenerateContent(gemini-functions dialect, thinking level, thought-signature round-trips, cached-token accounting).vertex.rs— Vertex AI enterprise surface sharing gemini's wire shape behind OAuth bearer auth + project/location-scoped addressing.bedrock.rs— Amazon Bedrock Converse with a pure-Rust SigV4 signer, pinned by botocore golden vectors.stella-model/src/lib.rs;hmac+sha2added tostella-model/Cargo.tomland the root[workspace.dependencies](SigV4 uses hex, not base64 — no base64/time deps).2. Custom tools →
stella-toolscustom.rs— developer-defined TOML tool manifests under.stella/tools/and~/.config/stella/tools/that shell out to a command;CustomToolSetcomposes over an innerToolExecutor. Declared instella-tools/src/lib.rs;tomladded to deps,tempfileto dev-deps + root workspace deps. The twoToolSchemaliterals gainedread_only: false(stella'sToolSchemacarries aread_onlyfield the source predated; custom tools shell out, so they are always mutating).3. Skills →
stella-coreskills.rs— pure, I/O-free skill discovery/selection/mining mirroring the rules module (paths renamed.oxagen/skills→.stella/skills,~/.config/oxagen→~/.config/stella). Reusescrate::rules::parse_frontmatter. Declared instella-core/src/lib.rswith the full public re-export block. No new dependencies.4. Vertex + Bedrock made selectable →
stella-clicatalog.rs:ToolDialect::GeminiFunctions+BedrockConverse; agemini-3-pro@vertexrow and the Bedrock inference-profile row (both withPricing); provider-scopedresolve_for;(provider, id)dedup (gemini-3-pro now spans gemini + vertex); test coverage.config.rs: appendsvertex+bedrockProviderConfigrows at the END ofPROVIDERSso credential auto-detection never prefers them over an explicitly-configured provider (AWS_ACCESS_KEY_IDis commonly present for unrelated reasons). Bothopenai_compatible = false.agent.rsbuild_provider: switches the catalog check toresolve_for(provider-scoped) and addsvertex+bedrockconstruction arms that read their extra addressing/credentials fromstd::env(VERTEX_PROJECT_ID/LOCATION,AWS_SECRET_ACCESS_KEY/SESSION_TOKEN/REGION) with named errors.std::env+cfgfields +stella_modelonly — nooxagen-mcp/oxagen-context, no agent-loop/memory/recall changes.Deliberately unchanged
credential.rs— stella's version is already a strict superset of the source (atomic0600writes,redacted_preview,STELLA_-renamed tests). Porting the older source would regress it. The new providers' env-var resolution lives inagent.rs/config.rs, not the provider-agnostic credential module.GeminiProvideradapter ships and is fully unit-tested, but flipping thegeminiprovider from shim → native is a behavior change to a working provider that can't be verified here without a liveGEMINI_API_KEY— so it is deferred to Phase 2, exactly as upstream defers it.oxagen-mcp/oxagen-context(orstella-mcp/stella-context). stella-cli deliberately excludes them; none of the ported code requires them. The heavier monorepo features (MCP, domains/interactive, unioningFileChangeinto stella's event enum, broader agent-loop reconciliation) are separate Phase 2 work.Testing posture
Vertex, Bedrock, and the Gemini adapter are unit- and routing-tested, but NOT live-credential-verified (matching upstream, which also only unit-tests them). A real end-to-end call against Google/AWS is the follow-up once live credentials are available in an environment. Specifically:
stella-modelprovider unit tests exercise wire translation, error mapping, and (bedrock) SigV4 againstwiremock+ golden vectors.agent.rsselection-routing tests (construction only, no network): assertvertex → VertexProviderandbedrock → BedrockProvider(no fall-through to the shim/anthropic), that a vertex selection missingVERTEX_PROJECT_IDis a named error, and a regression that all 7 existing providers still route to the same adapter they did before theresolve_for/dedup change.Verification
cargo check --workspace: clean (Finished).cargo clippy --workspace --all-targets -- -D warnings: clean (Finished, no warnings).cargo test --workspace: 424 passed, 0 failed across all crates.stella-model: 100 (incl. 10 gemini, 14 bedrock with 7 SigV4 golden-vector tests, updated catalog/resolve_for tests).stella-core: 199 (incl. 35 skills).stella-tools: 82 (incl. 29 custom).stella-cli: 9 (7 config incl. the 9-provider catalog cross-check + 2 selection-routing tests).Build environment note
The main working checkout
/Users/macanderson/Workspaces/stella-cliis corrupted by a parallel session — its rootCargo.tomlis clobbered with anoxagen-tuipackage manifest and it has untrackedstella-mcp/,stella-context/,stella-graph/,stella-fleet/,stella-media/,stella-pipeline/,stella-tui/dirs (crates stella-cli excludes). This PR was built entirely from a cleanorigin/maingit worktree so none of that corruption is included; that clone needs cleanup separately.