Skip to content

sync(stella): Gemini/Vertex/Bedrock providers, custom tools, skills from monorepo - #4

Merged
macanderson merged 7 commits into
mainfrom
sync/latest-providers-tools-skills
Jul 12, 2026
Merged

sync(stella): Gemini/Vertex/Bedrock providers, custom tools, skills from monorepo#4
macanderson merged 7 commits into
mainfrom
sync/latest-providers-tools-skills

Conversation

@macanderson

@macanderson macanderson commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Brings the cleanly-portable delta from the monorepo (origin/main, oxagen-* crates) across to stella-cli, fully renamed oxagen_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-model

  • 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 + project/location-scoped addressing.
  • bedrock.rs — Amazon Bedrock Converse with a pure-Rust SigV4 signer, pinned by botocore golden vectors.
  • Declared in stella-model/src/lib.rs; hmac + sha2 added to stella-model/Cargo.toml and the root [workspace.dependencies] (SigV4 uses hex, not base64 — no base64/time deps).
  • Each adapter's own unit tests ported from source (renamed).

2. Custom tools → stella-tools

  • custom.rs — developer-defined TOML tool manifests under .stella/tools/ and ~/.config/stella/tools/ that shell out to a command; CustomToolSet composes over an inner ToolExecutor. Declared in stella-tools/src/lib.rs; toml added to deps, tempfile to dev-deps + root workspace deps. The two ToolSchema literals gained read_only: false (stella's ToolSchema carries a read_only field the source predated; custom tools shell out, so they are always mutating).

3. Skills → stella-core

  • skills.rs — pure, I/O-free skill discovery/selection/mining mirroring the rules module (paths renamed .oxagen/skills.stella/skills, ~/.config/oxagen~/.config/stella). Reuses crate::rules::parse_frontmatter. Declared in stella-core/src/lib.rs with the full public re-export block. No new dependencies.

4. Vertex + Bedrock made selectable → stella-cli

  • catalog.rs: ToolDialect::GeminiFunctions + BedrockConverse; a gemini-3-pro@vertex row and the Bedrock inference-profile row (both with Pricing); provider-scoped resolve_for; (provider, id) dedup (gemini-3-pro now spans gemini + vertex); test coverage.
  • 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 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. std::env + cfg fields + stella_model only — no oxagen-mcp/oxagen-context, no agent-loop/memory/recall changes.

Deliberately unchanged

  • credential.rs — stella's version is already a strict superset of the source (atomic 0600 writes, redacted_preview, STELLA_-renamed tests). Porting the older source would regress it. The new providers' env-var resolution lives in agent.rs/config.rs, not the provider-agnostic credential module.
  • Gemini stays on its OpenAI-compat shim. The GeminiProvider adapter ships and is fully unit-tested, but flipping the gemini provider from shim → native is a behavior change to a working provider that can't be verified here without a live GEMINI_API_KEY — so it is deferred to Phase 2, exactly as upstream defers it.
  • No oxagen-mcp / oxagen-context (or stella-mcp/stella-context). stella-cli deliberately excludes them; none of the ported code requires them. The heavier monorepo features (MCP, domains/interactive, unioning FileChange into 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-model provider unit tests exercise wire translation, error mapping, and (bedrock) SigV4 against wiremock + golden vectors.
  • New agent.rs selection-routing tests (construction only, no network): assert vertex → VertexProvider and bedrock → BedrockProvider (no fall-through to the shim/anthropic), that a vertex selection missing VERTEX_PROJECT_ID is a named error, and a regression that all 7 existing providers still route to the same adapter they did before the resolve_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-cli is corrupted by a parallel session — its root Cargo.toml is clobbered with an oxagen-tui package manifest and it has untracked stella-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 clean origin/main git worktree so none of that corruption is included; that clone needs cleanup separately.

macanderson and others added 6 commits July 11, 2026 17:38
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
macanderson marked this pull request as ready for review July 12, 2026 01:25
@macanderson
macanderson merged commit b894256 into main Jul 12, 2026
0 of 2 checks passed
@macanderson
macanderson deleted the sync/latest-providers-tools-skills branch July 12, 2026 01:26
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>
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
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