Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 125 additions & 48 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,46 @@ name: CI

on:
push:
# Pull requests are covered by the `pull_request` trigger below. Building
# every branch push as well duplicated the entire suite for each PR commit
# (two identical ~23 minute runs), so pushes only build the mainline, which
# is also what keeps a shared, warm cache for PR branches to restore from.
branches: [main]
pull_request:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
# Keyed by pull-request *identity*, not by branch name: two PRs from
# different forks can share a head branch name (`main`, `feature`), and
# keying on `github.head_ref` would put them in one group where
# `cancel-in-progress` lets either one cancel the other's required checks.
# `github.event.pull_request.number` is unset for pushes, which fall back to
# the ref.
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

permissions:
contents: read

env:
CARGO_TERM_COLOR: always
# Incremental compilation only pays off across edits on one machine; in CI it
# costs codegen time and inflates the cached target directory.
CARGO_INCREMENTAL: 0
# Debug info dominates link time and target-directory size. Dropping it
# roughly halves both, and nothing in CI reads line numbers out of a
# backtrace.
CARGO_PROFILE_DEV_DEBUG: 0
CARGO_PROFILE_TEST_DEBUG: 0
RUST_BACKTRACE: 1

jobs:
rust-sdk:
name: Rust SDK
# Static analysis only: `cargo clippy` and `cargo doc` stop at metadata, so
# this job never links a binary and finishes long before the test matrix.
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# This job executes repo code (cargo build/test); don't persist the
# token in git config.
Expand All @@ -27,11 +52,12 @@ jobs:
# vendor/tinytools/crates/tinytools/Cargo.toml".
submodules: recursive

- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable
with:
toolchain: stable
components: rustfmt, clippy

- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
workspaces: .

Expand All @@ -44,57 +70,108 @@ jobs:
- name: Clippy all features
run: cargo clippy --workspace --all-targets --all-features -- -D warnings

- name: Build
run: cargo build --workspace --all-targets
# `--all-features` so links into feature-gated items resolve; without
# it rustdoc cannot see the very modules the docs point at.
- name: Doc lints

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

priority high security confident

Do not make known rustdoc warnings gate CI

The previous workflow explicitly marked this check as non-blocking because the workspace has roughly 160 existing broken intra-doc links. Moving it into the new lint job without continue-on-error makes those known warnings fail the required CI status and blocks every merge until unrelated documentation is fixed. Restore non-blocking behavior or fix all existing warnings before enabling the gate.


Additional tests observation

priority high confident

Do not make broken intra-doc links block merges without fixing them first

[RULE] ci-regression

The old CI kept this step with continue-on-error: true because there were ~160 existing broken intra-doc link warnings across the workspace. This revision removes the soft-fail and makes it a hard gate, but only fixes links in the files it touches. The remaining broken links elsewhere will cause the lint job to fail, blocking all merges until they are all cleaned up. Either fix every broken link across the workspace in this PR, or restore continue-on-error: true with the TODO comment until a dedicated cleanup covers the rest.

[RULE] nonblocking-known-ci-failure ·

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

priority high tests confident

Do not make known rustdoc warnings gate CI

The workspace has approximately 160 existing broken intra-doc link warnings (as noted in earlier review context). This change removes the continue-on-error: true from the doc lints step without fixing those links, so the step will now fail the lint job and block merges on pre-existing issues. Either fix the broken links in this PR or restore continue-on-error: true until they are resolved.

[RULE] known-issues-gate-ci ·

env:
RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

priority high security confident

Do not make broken intra-doc links block merges without fixing them first

This change removes the previous non-blocking behavior for the known broken intra-doc-link set and places the check in the required lint job. Existing broken links therefore prevent the aggregate ci job from succeeding even when the code and tests pass. Repair the links before enforcing this gate, or preserve the non-blocking behavior.

[RULE] broken-intra-doc-links-gate ·

run: cargo doc --workspace --no-deps --all-features
Comment thread
senamakel marked this conversation as resolved.

# Scoped to `crates`, this repo's own workspace: an unscoped run also
# walks `vendor/`, where it reports findings against the vendored
# submodules that have to be fixed in their own repositories.
- name: Unused dependencies

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

priority high security confident

Keep known unused-dependency findings non-blocking

The old step used continue-on-error because the workspace already has known unused-dependency findings. This replacement runs the pinned action as a required lint without that exemption, so the new lint job will fail until unrelated existing findings are cleaned up, blocking all merges.

[RULE] nonblocking-known-ci-failure ·

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

priority high critique confident

Keep known unused-dependency findings non-blocking

The previous step was explicitly non-blocking because the workspace has existing unused-dependency findings. This revision removes continue-on-error, so cargo-machete will make the lint job fail on that pre-existing debt and block every merge. Restore non-blocking behavior until those findings are cleaned up, or remove the exception only together with the dependency fixes.


Additional tests observation

priority high confident

Keep known unused-dependency findings non-blocking

[RULE] known-issues-gate-ci

The workspace previously had continue-on-error: true on this step because of existing unused-dependency findings across the workspace. This change removes that guard without fixing the findings. If any such findings remain, the lint job will fail and block merges. Either fix all unused-dependency issues in this PR or add back continue-on-error: true.

[RULE] known-warning-gate ·

uses: bnjbvr/cargo-machete@ac30a525c0a8d163a92d727b3ff079ee3f6ecb08 # v0.9.2
with:
args: crates

# Every feature selection links a distinct workspace graph, so they cannot
# share a target directory. Previously they ran back to back with a
# `cargo clean` between each, which serialised five cold builds into one job
# and left the cache holding whichever graph happened to be built last. As
# separate jobs they build concurrently and each keeps its own warm cache.
test:
name: Test (${{ matrix.name }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- name: default
features: ""
- name: all-features
features: "--all-features"
- name: sqlite
features: "--no-default-features --features sqlite"
- name: tools
features: "--no-default-features --features tools"
- name: multimodal
features: "--no-default-features --features multimodal"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
submodules: recursive

- uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable
with:
toolchain: stable

- name: Build all features
run: cargo build --workspace --all-targets --all-features
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
workspaces: .
# Without a distinct key every matrix leg would race to overwrite one
# cache entry with its own feature graph.
key: ${{ matrix.name }}

- name: Build
run: cargo build --workspace --all-targets ${{ matrix.features }}

- name: Test
run: cargo test --workspace
run: cargo test --workspace ${{ matrix.features }}

coverage:
name: Coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
submodules: recursive

- name: Test all features
run: cargo test --workspace --all-features
- uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable
with:
toolchain: stable
components: llvm-tools-preview

- name: Test optional features independently
run: |
# Each feature set links a distinct workspace graph. Clearing the
# preceding graph keeps the runner's linker working set bounded and
# makes these genuinely independent builds.
cargo clean
cargo test --workspace --no-default-features --features sqlite
cargo clean
cargo test --workspace --no-default-features --features tools
cargo clean
cargo test --workspace --no-default-features --features multimodal

- name: Free build artifacts before coverage
run: cargo clean

- name: Coverage gate
uses: taiki-e/install-action@cargo-llvm-cov
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
workspaces: .
# Instrumented objects are not interchangeable with the test matrix's,
# so coverage keeps its own cache rather than invalidating theirs.
key: coverage

- uses: taiki-e/install-action@7623a79cdfecb99d681017af368ca353d9f49bb5 # v2.87.19
with:
tool: cargo-llvm-cov

- name: Verify line coverage
env:
CARGO_PROFILE_DEV_DEBUG: 0
CARGO_PROFILE_TEST_DEBUG: 0
run: >-
cargo llvm-cov --all-features --workspace
--ignore-filename-regex '(^|/)(tests?|examples)/|/test(_.*)?\.rs$'
--fail-under-lines 80

# TODO: drop `continue-on-error` once the ~160 existing broken
# intra-doc link warnings across the workspace are fixed and this can
# block merges instead of only reporting.
- name: Doc lints
env:
RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links
run: cargo doc --workspace --no-deps
continue-on-error: true

# TODO: drop `continue-on-error` once the existing unused-dependency
# findings across the workspace are cleaned up and this can block
# merges instead of only reporting.
- name: Unused dependencies
uses: bnjbvr/cargo-machete@main
continue-on-error: true
# Single required status check: branch protection can depend on this one job
# instead of being updated every time the matrix gains or loses a leg.
ci:
name: CI
runs-on: ubuntu-latest
if: always()
needs: [lint, test, coverage]
steps:
- name: Check results
run: |
echo "lint: ${{ needs.lint.result }}"
echo "test: ${{ needs.test.result }}"
echo "coverage: ${{ needs.coverage.result }}"
[ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" = "false" ]
4 changes: 2 additions & 2 deletions crates/tinyagents-graph/src/agent_loop/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
//! `structured_strategy_override`) or `EndStrategy::Early`/`Exhaustive` (A6);
//! `resolve_structured_plan` also resolves the profile-driven `Auto` choice
//! against the *default* model binding rather than the turn's actually-
//! resolved model (see that function's docs). [`RunPolicy::execution`]
//! resolved model (see that function's docs). [`tinyagents_harness::runtime::RunPolicy::execution`]
//! defaults to [`tinyagents_harness::runtime::LoopExecution::Direct`], so
//! every existing caller is unaffected unless it opts in.
//!
Expand Down Expand Up @@ -93,7 +93,7 @@
//! make installing a driver on a not-yet-`Arc`'d harness impossible — a
//! real usability regression) or unsafely extending the borrow's lifetime
//! (which this workspace denies via `unsafe_code = "deny"`). So
//! [`GraphLoopDriver::drive`] instead calls the exact same node bodies
//! [`GraphLoopDriver`]'s `drive` instead calls the exact same node bodies
//! directly, in a hand-rolled loop, against the real borrowed `&mut`
//! state — no `Arc`, no `Mutex`, no `CompiledGraph` involved — which is
//! sound with zero unsafe code precisely because a borrowed async call
Expand Down
5 changes: 3 additions & 2 deletions crates/tinyagents-graph/src/agent_loop/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ pub struct LoopState {
pub tool_calls: usize,
/// Names of calls that reached a tool executor, in execution order.
pub executed_tools: Vec<String>,
/// Set once the loop has produced a terminal outcome (finished, not
/// necessarily successfully — see [`Self::final_error`]).
/// Set once the loop has produced a terminal outcome — finished, not
/// necessarily successfully. A run that ended in an error reports it
/// through the driver's `Result`, not through this struct.
pub finished: bool,
/// The final assistant text, once [`Self::finished`] is set by a normal
/// completion, [`MiddlewareControl::StopWithFinal`][mc], or
Expand Down
2 changes: 1 addition & 1 deletion crates/tinyagents-graph/src/channel/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ impl ReducerRegistry {

/// The `{"reducer": name}` config payload for a named `BinaryAggregate`
/// channel, or `{"reducer": null}` for an unnamed one (which
/// [`crate::channel::channel_from_config`] then rejects on decode).
/// `channel_from_config` then rejects on decode).
pub(crate) fn config_for(name: Option<&str>) -> Value {
json!({ "reducer": name })
}
Expand Down
2 changes: 1 addition & 1 deletion crates/tinyagents-graph/src/channel/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ pub trait Channel: Send + Sync {
/// The channel's construction config, serialized so [`ChannelSet`] can
/// round-trip `{ kind, config, value }` through a durable checkpointer
/// without knowing the concrete channel type. Paired with
/// [`channel_from_config`] on decode. Channels with no configuration
/// `channel_from_config` on decode. Channels with no configuration
/// (the default) serialize `Value::Null`; [`Barrier`]/[`NamedBarrier`]
/// carry their `expected` set, and [`BinaryAggregate`] carries the
/// registered reducer name (see [`BinaryAggregate::named`] and
Expand Down
2 changes: 1 addition & 1 deletion crates/tinyagents-graph/src/checkpoint/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,7 @@ impl<State> Checkpoint<State> {
/// re-serializes as clean v2), and stamps [`Checkpoint::version`] to
/// [`CHECKPOINT_FORMAT_VERSION`].
///
/// A no-op on an already-v2 record. Every bundled [`Checkpointer`]
/// A no-op on an already-v2 record. Every bundled [`crate::checkpoint::Checkpointer`]
/// backend calls this on every decode path (`get`/`get_scoped`/`list`/
/// `state_history`/`get_thread`), so callers outside this module never
/// observe a v1 record — see `docs/modules/graph/checkpointing.md`.
Expand Down
2 changes: 1 addition & 1 deletion crates/tinyagents-graph/src/compiled/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
//! forward across however many times this step interrupts/fails and gets
//! resumed/retried; only once every branch of the step has completed does
//! the executor route them all together, in one call, against one
//! committed state — see [`boundary::CompiledGraph::advance`]'s
//! committed state — see `boundary::CompiledGraph::advance`'s
//! `carried_completed` handling. One caveat: a deferred branch's routing
//! is re-resolved via static/conditional edges only (an explicit
//! `Command::goto` it returned is not itself persisted across the
Expand Down
2 changes: 1 addition & 1 deletion crates/tinyagents-harness/src/agent_loop/tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1844,7 +1844,7 @@ impl<State: Send + Sync, Ctx: Send + Sync> AgentHarness<State, Ctx> {
/// re-attempts the call.
///
/// A call whose tool is no longer registered on this harness (renamed,
/// removed since the interrupted run) is treated as [`ToolReplay::Never`]
/// removed since the interrupted run) is treated as [`tinytools::ToolReplay::Never`]
/// — fail closed rather than blindly re-run an unknown effect.
///
/// Only ledger rows still in [`crate::tool::ToolEffectStatus::Started`]
Expand Down
2 changes: 1 addition & 1 deletion crates/tinyagents-harness/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ impl<Ctx> RunContext<Ctx> {
/// The counter is per-context, not process-global: a freshly constructed
/// context (including a child context, which never inherits its parent's
/// counter) always starts at `0`. Callers that spawn deterministically
/// named children — [`crate::subagent::SubAgent`], for one — use this
/// named children — `tinyagents_orchestration::SubAgent`, for one — use this
/// instead of a process-wide sequence so two processes calling the same
/// parent context's child spawner in the same order derive identical
/// ordinals, and therefore identical child run ids (M-2).
Expand Down
2 changes: 1 addition & 1 deletion crates/tinyagents-harness/src/context/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ pub struct RunContext<Ctx = ()> {
pub(crate) child_ordinal: std::sync::Arc<std::sync::atomic::AtomicU64>,
/// Durable tool-effect ledger for this run, when a host wants crash-safe
/// bookkeeping of tool-call side effects (B5). `None` (the default) means
/// no ledger writes happen and [`crate::tool::ToolPolicy`]'s
/// no ledger writes happen and [`tinytools::ToolPolicy`]'s
/// `runtime.replay` declaration has nothing to guard resume against — the
/// agent loop behaves exactly as it did before this existed. Attach one
/// with [`RunContext::with_tool_effect_ledger`]; a child context inherits
Expand Down
2 changes: 1 addition & 1 deletion crates/tinyagents-harness/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ pub enum TinyAgentsError {
/// the counterpart to [`Self::ModelRetry`]. Folded into a
/// [`tinytools::ToolResult::failed`] result (still recoverable at the
/// transcript level — the model sees the message — but
/// [`crate::retry::RetryMiddleware`] and any other retry policy treat it
/// [`crate::middleware::library::RetryMiddleware`] and any other retry policy treat it
/// as non-retryable rather than re-attempting the call).
#[error("permanent tool failure: {0}")]
ToolFailed(String),
Expand Down
2 changes: 1 addition & 1 deletion crates/tinyagents-harness/src/host/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ pub struct HostCapabilities<State: Send + Sync> {
/// Procedural memory of how this agent has performed before. `None` means
/// no experience is recorded or recalled.
pub experience: Option<Arc<dyn ExperienceStore>>,
/// Whether a resolved [`AgentDefinition`] that declares no tools (an
/// Whether a resolved [`tinyagents_definition::AgentDefinition`] that declares no tools (an
/// empty or absent `tools` list) denies every tool, instead of granting
/// the whole registered catalogue.
///
Expand Down
14 changes: 7 additions & 7 deletions crates/tinyagents-harness/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,20 @@
//!
//! # Host utilities not on the agent loop path (M-10)
//!
//! [`handoff`] and the [`memory`] module's
//! [`memory::ChatHistory`]/[`memory::ShortTermMemory`] are exported for a
//! host to build on, but [`agent_loop`] does not call into any of them on its
//! own — they are opt-in plumbing, not implicit loop behavior. (A
//! [`handoff`] is exported for a host to build on, but [`agent_loop`] does
//! not call into it on its own — it is opt-in plumbing, not implicit loop
//! behavior. (A
//! [`run_queue::RunQueue`] *is* drained by the loop once attached via
//! [`context::RunContext::with_run_queue`]; see that module's docs.)
//!
//! - [`handoff`] is a progressive-disclosure cache for oversized tool
//! results; a host calls [`handoff::apply_handoff`] itself before
//! appending a tool result to history, and registers an extraction tool
//! that reads the same [`handoff::ResultHandoffCache`].
//! - [`memory::ChatHistory`]/[`memory::ShortTermMemory`] persist a thread's
//! transcript across runs; a host reads history into a run's `input` and
//! appends the run's messages back afterward.
//!
//! Persisting a thread's transcript across runs lives in the separate
//! `tinyagents-session` crate: a host reads history into a run's `input` and
//! appends the run's messages back afterward.
//!
//! Wiring any of these directly into the loop is deliberately future work
//! rather than default behavior, so a host that does not need one pays
Expand Down
2 changes: 1 addition & 1 deletion crates/tinyagents-harness/src/limits/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ pub struct RunLimits {
/// Maximum sub-agent / recursion depth allowed for the run tree rooted at
/// this run. A top-level run is depth `0`; each nested child run increments
/// the depth. A sub-agent invocation whose child depth would exceed this cap
/// fails fast (see [`crate::subagent`]). Defaults to
/// fails fast (see the `tinyagents-orchestration` sub-agent invoker). Defaults to
/// [`RunLimits::DEFAULT_MAX_DEPTH`].
pub max_depth: usize,
/// What the run should do when a call cap is reached. Defaults to
Expand Down
2 changes: 1 addition & 1 deletion crates/tinyagents-harness/src/runtime/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ pub struct RunPolicy {
/// [`OutputRetryPolicy`].
pub output_retry: OutputRetryPolicy,
/// What the loop does when one turn's tool calls include both a
/// structured-output "schema" call ([`StructuredStrategy::ToolCall`]'s
/// structured-output "schema" call ([`crate::structured::StructuredStrategy::ToolCall`]'s
/// synthetic tool) and one or more genuine function-tool calls (A6).
/// Defaults to [`EndStrategy::Graceful`].
pub end_strategy: EndStrategy,
Expand Down
4 changes: 2 additions & 2 deletions crates/tinyagents-harness/src/summarization/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -523,8 +523,8 @@ pub struct CompactionRecord {
pub summary: String,
/// Index, into the non-system message slice compaction operated over, of
/// the first message that survives verbatim (everything before it was
/// folded into [`Self::summary`]). Matches [`CutPoint::index`] when the
/// record was produced from a [`CutPoint`].
/// folded into [`Self::summary`]). Matches [`crate::summarization::CutPoint::index`] when the
/// record was produced from a [`crate::summarization::CutPoint`].
pub first_kept_index: usize,
/// Estimated total tokens of the transcript immediately before
/// compaction.
Expand Down
Loading
Loading