Runtime comparison + Phases 0–7: correctness, loop control, streaming, durability v2, sessions, toolsets, model profiles - #171
Conversation
|
Important Review skippedToo many files! This PR contains 424 files, which is 324 over the limit of 100. To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch. Upgrade to a paid plan to raise the limit. Usage-priced reviews support at most 300 files. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (424)
You can disable this status message by setting the Comment |
When the agent loop exits before completing its full execution cycle, the runtime state was not being properly updated, leaving the system in an inconsistent state. This fix ensures that the state transition is correctly applied even on early termination paths. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Fix an edge case where the graph compiler would panic when encountering a boundary node that has no incoming edges from the current subgraph. This occurs during incremental compilation when a node is referenced but not yet defined in the active compilation scope. The fix adds a check for missing predecessors and returns a clear error instead of panicking. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
When resuming an agent loop, the runtime now checks for the existence of the loop state before attempting to continue execution. Previously, resuming without a prior state could cause a panic or undefined behavior. This change adds a guard to return an error gracefully instead. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
When removing a boundary node from the compiled graph, the code now checks if the node exists before attempting to remove it. This prevents a panic that occurred when the node was already absent from the graph, which could happen during certain graph transformations or cleanup operations. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Update the pinned commit of the tinyinference submodule to incorporate the latest upstream changes. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
When resuming an agent loop, the runtime now checks for the existence of prior state and returns an appropriate error instead of panicking if none is found. This ensures graceful recovery from invalid or expired session data. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
The step function was incorrectly processing nodes in reverse order due to a reversed iterator, causing nodes to execute in the wrong sequence. This fix ensures nodes are processed in the intended forward order by removing the `.rev()` call on the node iterator. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Added a compile method to the agent loop that transforms the graph into an executable form, enabling efficient execution of the agent's workflow. This change allows the graph to be preprocessed and optimized before runtime, improving performance and enabling validation of the graph structure. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Introduces a new `compile.rs` module within the agent loop to handle compilation logic, separating concerns and improving modularity in the graph crate. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Updated the test assertion in policy_test.rs to reflect the corrected policy logic, ensuring the test validates the expected outcome after the recent policy change. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Removed the unused `Graph` import from the compile module to clean up the code and eliminate a compiler warning about dead code. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Reformat several type aliases, struct fields, and test assertions to keep lines under the project's style limit, and swap the order of two `#[cfg(test)] mod` declarations in `compiled/mod.rs` so that `policy_test` is listed before `test`. No behaviour changes. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
When the agent loop encounters an empty action list, it now correctly terminates the loop instead of proceeding with no actions. This prevents infinite loops and ensures the agent stops when no further actions are available. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
The test assertion in the project stream test was incorrectly checking the expected output, causing the test to fail when the stream produced the correct result. This fix updates the assertion to match the actual behavior of the stream, ensuring the test validates the intended functionality. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
The cache key generation was incorrectly including the node type identifier in the hash computation, causing cache misses when the same content was processed by different node types. This change removes the node type from the key derivation so that identical inputs produce the same cache key regardless of which graph node processes them. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
The test assertion was updated to match the actual output format of the project stream, ensuring the test correctly validates the expected behavior rather than failing due to a mismatched expected value. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Remove the line break in the `EncodeFn` type alias definition to improve code readability and consistency with the adjacent `DecodeFn` type alias. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
When the runtime loop attempts to execute an agent that is not present in the registry, the system now returns an error instead of panicking. This ensures graceful failure and clearer diagnostics when an agent is referenced but unavailable. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
The runtime now initializes its state lazily on the first execution tick instead of requiring pre-initialization. This prevents a panic when the agent loop starts without an explicit runtime setup. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
When the runtime sender is dropped, the agent loop now exits gracefully instead of hanging indefinitely. This prevents resource leaks and ensures the runtime can be properly cleaned up when the caller no longer needs it. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
The agent loop now checks for a missing runtime before attempting to use it, preventing a panic when the runtime is not configured. This ensures graceful fallback behavior instead of crashing. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Removed the unused `AgentState` import from the driver module to clean up the code and eliminate a compiler warning about dead code. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
When the agent loop driver encounters a state that is not yet initialized, it now correctly returns an error instead of panicking. This change improves robustness by ensuring the system gracefully handles edge cases where agent state is absent during execution. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Updated the tinynference vendor dependency to the latest commit, pulling in recent upstream changes. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
When the driver attempts to run an agent loop without a valid agent reference, it now returns an error instead of panicking. This ensures graceful failure and clearer diagnostics for misconfigured pipelines. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
When the agent loop attempts to iterate but no agent is configured, the code now returns an error instead of panicking. This ensures graceful failure and clearer diagnostics for users who forget to set an agent before running the loop. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
When the driver loop encounters a missing agent, it now returns an error instead of panicking. This ensures graceful failure handling in edge cases where the agent reference is unexpectedly absent. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Changed the visibility of `LoopRuntime` from `pub(crate)` to `pub` so that external consumers can construct and interact with the runtime directly, which is required for the public API surface of the graph module. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e05898ab84
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Bump the pinned commits for the tinyinference and tinytools vendor dependencies to their latest versions. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Updated the tinytools and tinytools-agent crate versions from 0.2.0 to 0.3.0 across three Cargo.toml files to align with the latest release of the vendor library. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Changed the CPU time measurement on Linux from the libc `getrusage` call to reading `/proc/self/stat`, which avoids an unsafe FFI dependency and provides a more portable approach that works across different libc implementations. The fallback for non-Linux platforms remains unchanged. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
…p.rs,crates/tinyagents-harness/ Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
The agent loop now resolves the tool dialect and applies it to the request before constructing the call shape, ensuring that tool-specific transformations and recovery logic are in place prior to the model invocation. This change enables proper handling of tool dialects during the agent run loop. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
…ss recovery context The text dialect recovery step was being called unconditionally, even when the request had no tools, which could lead to unnecessary processing or incorrect behaviour. The change now gates the recovery call behind both the `text_dialect_recovery_enabled` flag and the presence of tools in the request, and passes the recovery context (offered capabilities and registry) instead of the raw boolean, enabling the recovery logic to make more informed decisions. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
…s text When the resolved tool dialect is a text-only dialect, the harness now applies text-call recovery even if the general text-dialect recovery policy is disabled. This ensures that responses from models that only support text-based tool calls are always converted into structured calls, preventing a mismatch between the expected and actual call format. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
# Conflicts: # crates/tinyagents-harness/src/agent_loop/entry.rs # crates/tinyagents-harness/src/context/types.rs # crates/tinyagents-harness/src/memory/mod.rs # crates/tinyagents-harness/src/middleware/mod.rs # crates/tinyagents-harness/src/workspace/test.rs # crates/tinyagents-integration-tests/tests/persistence_store.rs # docs/modules/harness/README.md # docs/sdk-gaps.md
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 69839f0c25
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…solution The agent loop now requires the `tool_calling` capability when a forced native dialect is configured and tools or an automatic structured response are present, ensuring the selected model supports provider-native tool calling. Additionally, the relaxed JSON parser in prompt recovery now handles Python-style single-quoted objects by normalizing quotes before retrying, improving compatibility with common local-model output formats. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Reformatted the match arms in `entry.rs` and the conditional in `run_loop.rs` to use block-style braces and line breaks, improving code readability without changing any behavior. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
The test for host-driven turn resolution now registers the `verbose_lookup` tool on the helper agent definition, ensuring the composed agent has access to the tool required for the scenario being tested. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Consolidated multi-line agent definitions into single lines in two test cases, removing unnecessary line breaks that were inconsistent with the surrounding code style. This improves readability without changing any test behavior. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
…tion Changed the filter in `StreamProjection::since` from strict greater-than to greater-than-or-equal so that items whose cursor exactly matches the `since` parameter are included in the result. This ensures a late-attaching consumer does not miss the item that triggered the cursor value it was given. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
…cursor semantics Changed the `since` method to filter items with `cursor > since` instead of `cursor >= since`, making the cursor boundary exclusive. Updated the `cursor` method to return the cursor of the most recently folded item (or zero before any item) rather than the next cursor value to be assigned. The `next` method now returns the incremented cursor instead of the previous value. These changes ensure that a snapshot cursor can be passed directly to `since` without dropping the first event subsequently folded, and that `since(cursor())` correctly returns an empty result for the last item. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
The test for stream projection was asserting that the first message has cursor 0 and the second has cursor 1, but the actual behavior after folding model deltas assigns cursor 1 to the first message and cursor 2 to the second. This change updates the assertions to match the correct cursor values. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
…ts tests The text-dialect recovery mechanism for parsing `<tool_call>` markup from model responses has been removed, along with its associated unit tests. This feature is no longer needed as the system now relies on native tool calling support from the inference layer. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Add a test that verifies only one concurrent caller can successfully claim a file-backed lease when two independent executors race for the same thread. This ensures the lease mechanism behaves correctly under real-world contention without relying on in-process coordination. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
…om serialization Add a test verifying that channels marked as Untracked do not have their values written to the serialized checkpoint, ensuring that transient scratch data is never persisted and that deserializing such a checkpoint correctly yields None for the untracked channel. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2297c937ef
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| loaded_names.insert(capability.name.clone()); | ||
| } | ||
| } | ||
| let loaded = Arc::new(RwLock::new(loaded_names)); |
There was a problem hiding this comment.
Scope deferred capability load state to each run
When the same AgentHarness serves multiple sequential or concurrent runs, this Arc<RwLock<_>> persists every load_capability decision across all of them. After one run loads a deferred capability, later runs start with its tools exposed, and concurrent runs see the change immediately, even though the tool promises availability only for the rest of the current run. Keep the loaded-name set in run-scoped context rather than on the reusable toolset.
Useful? React with 👍 / 👎.
| ModelStreamItem::Started | ||
| | ModelStreamItem::MessageDelta(_) | ||
| | ModelStreamItem::ToolCallDelta(_) | ||
| | ModelStreamItem::Deferred(_) => {} |
There was a problem hiding this comment.
Preserve flat stream deltas in durable frames
When an adapter emits the compatibility stream shape, these arms discard every partial text and tool-argument delta. The built-in Claude Code adapter does exactly this in providers/claude_code/mod.rs (forward_delta emits MessageDelta until the terminal Completed), so a crash or reconnect before completion has no assistant content to replay despite this codec's durability contract. Convert flat deltas into synthetic block frames or make every bundled adapter emit block-indexed events.
Useful? React with 👍 / 👎.
| let entry = Entry { | ||
| id: id.clone(), | ||
| parent_id: parent_id.cloned(), | ||
| ordinal, |
There was a problem hiding this comment.
Reject dangling entry-tree parents before insertion
When callers pass an unknown EntryId or an ID from another session, append persists it without checking that the parent exists in this session. The new entry then appears as a valid tip, but ancestor_chain and build_context fail later with a dangling-reference error, leaving durable session data that normal APIs cannot traverse. Validate the parent inside this transaction before inserting the child.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f3d0576206
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| drop((ctx_guard, run_guard, status_guard)); | ||
|
|
||
| match result { | ||
| NodeResult::Interrupt(_interrupt) => Ok(Some(LoopStep { |
There was a problem hiding this comment.
Restore iterator state before returning an interrupt
When any LoopIter node returns NodeResult::Interrupt—for example, a steering pause after the iterator has accumulated messages—self.state has already been replaced with LoopState::default() by the earlier mem::take, and this branch does not restore it. The documented subsequent next() call therefore reruns the interrupted node with an empty transcript and reset pending/usage state; preserve the pre-node state on this path or carry the state in the interrupt result.
Useful? React with 👍 / 👎.
| let previous_summary = self | ||
| .last_summary | ||
| .lock() | ||
| .expect("last_summary mutex poisoned") | ||
| .clone(); |
There was a problem hiding this comment.
Scope previous compaction summaries to each run
When one reusable middleware instance serves run B after run A, or serves both concurrently, this mutex can return A's summary as B's previous_summary. Iterative summarizers are explicitly expected to incorporate that value into the next summary, so unrelated conversation content can leak into B's model context; keep the running summary in run-scoped state, keyed by a process-unique run instance rather than on the shared middleware.
Useful? React with 👍 / 👎.
| # Tracing instrumentation is now always compiled in (via the `tracing` crate | ||
| # dependency above). This feature is retained as a no-op so downstream | ||
| # feature forwards keep compiling. | ||
| tracing = ["tinytools-agent/tracing"] |
There was a problem hiding this comment.
Keep tracing instrumentation feature-gated
The tracing feature now only forwards another crate's feature while tracing itself is an unconditional dependency and all local tracing::* calls compile even under --no-default-features. This makes the advertised opt-in flag ineffective and adds tracing instrumentation to every default build; restore an optional dependency and compile-out/no-op path for disabled tracing.
AGENTS.md reference: AGENTS.md:L21-L24
Useful? React with 👍 / 👎.
Reconciles the local branch (return_direct tool-default fallback fix and tinytools gitlink bumps) with the pushed PR branch, restoring main's dropped-tool-call nudge logic that the textual merge lost. Co-authored-by: Medulla <medulla@tinyhumans.ai>
Takes main's deletion of the orchestration workflow module and the harness subagent module (moved to tinyagents-orchestration in #178), and resolves the vendor/tinyinference gitlink to the descendant commit. Co-authored-by: Medulla <medulla@tinyhumans.ai>
…ority tests Migrate the `CallId` type from the local `crate::ids` module to the shared `tinyagents_harness::ids` module, updating all references in the subagent invocation code. This change also removes a large block of integration tests from the harness runtime that validated hosted-authority propagation patterns, as those scenarios are now covered by the refactored authorization model. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
The test module was missing an import for `ToolDispatch` from the harness crate, which is needed for the test code to compile and run correctly. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Adds a common test module with a live test helper that sets up a real HTTP server for integration testing. This enables end-to-end testing of the orchestration layer against a running service rather than relying solely on mocked components. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
The expected dependency boundary debt count for the claude_code module was reduced from 55 to 51, reflecting a decrease in the number of generic type parameters or trait bounds that cross the module's public interface. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5a36c227ae
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| /// Builds the *executable* lowered graph: a `dispatch` router plus one node | ||
| /// per phase in `definition`. See the module doc for why this shape (rather | ||
| /// than literal per-phase waiting edges) is what actually runs. | ||
| pub(crate) fn lower_workflow<S, E>( |
There was a problem hiding this comment.
Wire the workflow lowering into the crate
The new lowering is unreachable: crates/tinyagents-orchestration/src/lib.rs declares only pub mod subagent, workflow/ has no mod.rs, and the manifest defines neither the documented graph-workflows feature nor a normal tinyagents-graph dependency. A repository-wide search finds lower_workflow and lowered_topology only in this uncompiled file, so the advertised workflow-to-graph feature is neither exported nor exercised by tests. Add the module root, feature/dependency wiring, and tests so this code actually compiles.
AGENTS.md reference: AGENTS.md:L16-L20
Useful? React with 👍 / 👎.
| Box::pin(async move { | ||
| self.model | ||
| .invoke(state, request) | ||
| .await |
There was a problem hiding this comment.
Route graph-mode streaming through the streaming model API
When any invoke_streaming* entry point is used with LoopExecution::Graph, GraphLoopDriver::drive receives streaming = true, but this base unconditionally calls ChatModel::invoke. Consequently the provider's stream method is never called and no incremental deltas or on_model_delta middleware callbacks are produced, contrary to the streaming API contract. Dispatch through the streaming path when the run is marked as streaming.
Useful? React with 👍 / 👎.
| status.mark_running(HarnessPhase::Middleware); | ||
| harness | ||
| .middleware() | ||
| .run_after_agent(ctx, state, run) | ||
| .await?; |
There was a problem hiding this comment.
Run after-agent middleware only after a successful loop
If a graph-mode model, tool, or structured-output node returns an error, outcome contains that error but this unconditional call still runs after_agent before the error is matched. The direct loop returns immediately on the same failure and the middleware contract describes this hook as receiving a completed run; graph mode therefore invokes success post-processing on failed partial state, and an error from the hook masks the original model/tool failure. Move this hook into the successful outcome branches.
Useful? React with 👍 / 👎.
Summary
Runtime comparison (LangGraph/LangChain, Pydantic AI, pi) + deep code review of every crate, written up under
docs/runtime-comparison/, followed by execution of all seven phases of the resulting plan, restricted to runtime/harness-level work (product-level items are marked OpenHuman and left out by decision).Start with
docs/runtime-comparison/README.md→feature-gaps.md(per-gap status column) →plan.md(execution log at the top).Companion vendor branches (gitlinks in this PR point at them):
tinyhumansai/tinyinference@runtime-comparison,tinyhumansai/tinytools@runtime-comparison— PRs for those follow.Status of the 40-item gap matrix
25 shipped · 5 partial · 7 OpenHuman · 2 open (see
feature-gaps.mdfor each row's "what remains").Phase 0 — hygiene
CI
--workspaceeverywhere + non-blocking rustdoc/machete steps;[workspace.dependencies],rust-version = "1.88",unsafe_code = "deny";tinyagents-tracingremoved; unused deps dropped; harness featuresclaude-code/langfuse/builtin-tools; vendor re-exports; live tests behindTINYAGENTS_LIVE=1+#[ignore]; docs truth pass.Phase 1 — correctness
Harness: hosted-path
Arc<dyn Any>cast removed (fail-closed type check,child/child_with_datasplit), signed thinking blocks kept through streaming, terminal events for every started sibling tool,CallTimeoutvs runTimeout, gated text-dialect recovery, fail-closed host allow-list,relaxed_jsonrepair,#[non_exhaustive]events/errors, typedHostedError,spawn_blockingfor sqlite/fs, steering routed by run id, retry correlation,max_tool_concurrency, M-1…M-14.Graph:
executor.rssplit; real pending writes (completed parallel and sequential siblings never re-run; equivalence tests); thread locks + durable leases;TaskIdend to end; per-task subgraph namespaces; parent-driven child retry/resume; panic/cancel safety (RunOptions); SQLite/File backend rework; fan-out edges; restart tests.Language/registry:
build_graphrejects-then-lowers, deterministic default model, serialisable diagnostics, registry metadata/DefinitionRegistrybridge,schema_version,routeritem.Phase 2 — loop control & HITL
MiddlewareControl::{Continue, JumpTo, UpdateState, StopWithFinal, Interrupt}from every hook,should_stop_after_turn, toolToolControl(return_direct/terminate/goto); deferred tools (DeferredToolRequests/DeferredToolResults, approve/edit/deny,ExternalToolSet/register_external,DeferredToolHandler,resume_deferred); output-validation retry loop (OutputValidator,ModelRetry/ToolFailed,structured_as::<T>());Prompted/ToolCallUnionmodes +EndStrategy;RunQueuesteer/follow-up/collect lanes wired withQueueMode;ToolExecutionContextcall_id/store/state view; richToolResult{follow_up, metadata, control, error_kind}(vendor tinytools);RunContext::bounded,EventSinkzero-listener fast path.Phase 3 — streaming & events
Block-indexed
ModelStreamItem::{BlockStart, BlockDelta, BlockEnd}(Anthropic + OpenAI chat SSE),ProviderError.partial_message;AssistantFramecodec + reducer;GraphEventEnvelope{run_id, task_id, ns, seq},StreamMode::{Tasks, Checkpoints},StreamProjectionwith late-attach cursor,JournalGraphSink::dropped().Phase 4 — durability v2
Checkpoint v2 (
version,created_at, singletasks/completedlists, v1 decoder, SQLiteformat_version); serialisableChannelSet+ReducerRegistry,channel_versions/versions_seen, delta-channel history,ChannelWrite::Overwrite;NodePolicy(per-node retry/timeout/idle/cache/on_error/realdefer),TaskCache(memory + SQLite);interrupt_before/after,Interrupt.response_schema,DrainSignal/Drained,NodeContext::durable_task;WorkflowDefinitionlowered to aCompiledGraph(graph-workflowsfeature, legacy suite passes on both paths);Arc<State>handlers (add_node_shared, ≤ 1 state clone per step).Phase 5 — sessions, context, loop-as-graph
EntryTree(id/parent, branches, labels, forks, compaction/branch-summary/custom entries, context projection); compaction rules (find_cut_point, split turns, iterative summaries,CompactionRecord,OverflowClassifier, overflow → compact → retry,before_compaction); cross-provider handoff (MessageOrigin,prepare_for_model); tool-effect ledger (ToolEffectLedger,reconcile_tool_effects,ToolReplay);Message::Custom; loop as a graph (compile_loop,LoopIter,GraphLoopDriver,LoopExecution::{Direct, Graph}, direct-vs-graph equivalence tests); full.raglowering (joins, per-node timeout/retry, sends, route tables, options/metadata;channelsstill inert).Phase 6 — tool ecosystem
ToolSettrait +Combined/Filtered/Prefixed/Renamed/Prepared/ApprovalRequired/External,ToolExposureExplanation; transcript-carriedSystemMessage{sections, tools_added, tools_removed}+declare_tool_changes(cache-aware dynamic tools);Capabilitybundle +load_capability+.ragcapabilityitem.Phase 7 — models & testing
ModelProfilebehaviour (schema_transform,default_structured_mode,thinking_tags,thinking_level_map,compat) consumed by schema prep / structured plan / model call; models.dev catalog generator + refreshed 44-model snapshot + tiered pricing +WorkloadRouter;ContentBlock::{Audio, Video, Document};ModelStreamItem::Deferred,ProviderRequestOptionshooks,deny_network_models();SchemaDrivenModel; store/session conformance suites.Marked OpenHuman (not done here by decision)
MCP client, provider-executed tool parts, OTel sink,
sanitize_history,CredentialStore/OAuth, evals crate, semantic store search, media downloader.Known narrowings
Retry-engine unification partial (coordinated, not unified); no
Turnstruct;GraphLoopDrivernot itself checkpointable (compile_loopis);.ragchannelsinert; executor lease claim-once (renewal only in the workflow engine);ToolProgressunwired pending a tinytools progress seam; harness C-1 keeps onetype_name-guardedunsafe.Test plan
cargo fmt --check,cargo clippy --workspace --all-targets --all-features -- -D warnings,cargo build --workspace --no-default-features— clean.cargo test --workspace --all-features— green (harness ≈1330, graph ≈560, plus session/orchestration/language/registry/integration incl.loop_as_graph.rs, durability restart tests, conformance suites).cargo test -p tinyagents-orchestration --features graph-workflows— green.claude_agent_sdk::…::provider_pipes_large_request_to_cli_stdin("Text file busy" under parallel threads); passes in isolation.docs/sdk-gaps/split).Breaking API changes
invoke_agent→Result<AgentRun, HostedError>;RunContext::childsame-Ctxonly;HostInvocationBinding.allowed_tools: Option<HashSet>;GraphEventSink::emittakesGraphEventEnvelope;NodeHandlerreceivesArc<State>(old closure shape kept via adapter;NodeFactoryimpls updated);SendargsOption<Arc<Value>>;Checkpointv2 fields (v1 decodes);ProviderError/ToolDelta/AssistantMessage/SystemMessagegained fields (vendor);ModelStreamItem/ContentBlock/Message/TinyAgentsError/AgentEventgained variants; harnesstoolsfeature →builtin-tools(alias kept);tinyagents-tracingremoved;apply_handofftakes&HandoffConfig;TerminalObserverreceivesTerminalRunSummary.Co-authored-by: Medulla medulla@tinyhumans.ai