Skip to content

Releases

br-agentic-nodes 0.3.0

Choose a tag to compare

@github-actions github-actions released this 02 Nov 17:13
3a16cf6

0.3.0 - 2026-11-02

Implements the sealed intent of minor 0.3: a structured reply for the ReAct agent and the generator.

Added

  • OutputSchemaPort and Ports::with_output_schema: an optional port that gives, at Start, the StructuredShape of the agent that makes steps (label agent of a ReAct agent, generator of a generator-critic), or none. It is never called for the critic or the programmatic critic.
  • With a shape, every step of that agent asks the client for OutputMode::Structured(shape), a step that offers tools included. Without the port, or with no shape, steps ask for plain text as before.
  • The structure rule: a final response of an agent that has a shape must hold a structured block. Otherwise the call is a failed call (CallFailure::NoStructuredReply): the next client of the list when the fallback covers the step, a fault otherwise. A refusal, a filter or an exhaustion is exempt; a response restored at Start is not checked.
  • ReactOutcome::structured_reply and GeneratorCriticOutcome::structured_reply: the first structured block of the last response.
  • Building blocks: structured_reply, check_structure, Worker::with_shape.

Changed

  • CallFailure has a new variant, NoStructuredReply: an exhaustive match on CallFailure must handle it.

br-agentic-nodes 0.2.0

Choose a tag to compare

@github-actions github-actions released this 31 Oct 08:27
068bf9f

0.2.0 - 2026-10-31

Rework to the sealed intent of minor 0.2, on br-agentic-contract 0.2.0.

Removed

  • Fault::NoAuthor, Fault::Thread; the From impls of Fault for the 0.1 PortFault, ThreadError and BoundaryError.
  • Outcome (with response, step_limit_reached, verdict) and OutcomePort; Ports::new no longer takes the outcome port.
  • ReactAgent::with_author, ReactAgent::restart, GeneratorCritic::restart, GeneratorCritic::with_authors, with_generator_author, with_critic_author, with_critic_thinking_field. The shape of the critic answer follows the ThinkingExpectation of the client of each attempt.
  • Worker::new, Worker::load, WorkerParts::author, WorkerParts::system, WorkerParts::tools.
  • The free function mail_tools; a worker gives its mail tools with Worker::mail_tools.

Changed

  • Every port returns PortFuture with PortError; Fault::Port holds a PortError, Fault::Boundary holds the 0.2 BoundaryError.
  • PromptPort::prompt and CriticRequest::text use String; a blank value is a fault.
  • CriticRequestPort::request takes the last model response of the generator; it is called once per round.
  • Ports::new(thread_port, agent_port, thread_access, prompt_port, tools_port, workspace_port).
  • ReactAgent::new(name, ports, settings, ReactSpec).
  • WorkerParts::inbox is an Option<Arc<dyn InboxPort>>.
  • Worker::drain gives Drained { stored, request_stored }.
  • Worker::close_turn takes &mut self and an &AiTurnRef.
  • GeneratorCriticSpec gains generator_author, critic_author and outcome.

Added

  • Worker::open (async, reads the conversation once), Worker::conversation, Worker::steps_made, Worker::with_author, with_system, with_tools, Worker::mail_tools, Worker::drain_at_start; Drained.
  • ReactSpec { fallback, outcome, author }, AuthorSource::{Fixed, Port}, AuthorPort.
  • ReactOutcome (response, steps, step_limit_reached) with ReactOutcomePort; GeneratorCriticOutcome (response, judgement, iterations, step_limit_reached) with GeneratorCriticOutcomePort; Judgement::{Validated, Rejected, NotJudged}.
  • RoundLimitEnding::{FinalGeneration (default), KeepRejectedGeneration, Fail} and GeneratorCritic::with_round_limit_ending.
  • The programmatic critic: ProgrammaticCritic { port, author, refusal_limit }, ProgrammaticCriticPort, ProgrammaticVerdict::{Accept, Refuse}, ProgrammaticRefusalLimit, PROGRAMMATIC_CRITIC_LABEL, GeneratorCritic::with_programmatic_critic.
  • Fault::{Session, ProgrammaticRefusal, BlankExplicitReason, CriticAtRoundLimit}, InvalidLimit::Refusals.
  • The crate README (also the crate documentation).

Behaviour

  • The conversation is read once at Start; the instance applies each write to its copy first and then hands it to the thread access.
  • The queue is read at Start with an awaited read and later by one background read at a time, never awaited at a boundary; an error of a read or of a mark is not a fault.
  • The thinking rule applies only to the calls the fallback covers; a refusal, a filter or an exhaustion without tool call is exempt.
  • The round counter and the programmatic-refusal counter go back to 0 only when a request item is stored.
  • All results of a tool run are opened before any call starts. Inbox errors in the mail tools are error results, never faults.
  • The critic projection drops agent mails and runtime notices from input turns.
  • A failed discard at Result is a fault of the instance, as br-agentic-contract 0.2.0 rules; this crate has no code for it.
  • Web access is held in the settings but not enforced by this crate. No tool can reach the file system of the node: a ToolCode gets only its arguments, a cancel view and a result stream.
  • All calls of a tool run start together: a call to a tool the node does not hold gets its error result after the real calls have started.
  • get_mail_files gives each attachment name quoted, like the mail text, so a name written by the sender cannot forge a line of the result.

br-agentic-contract 0.2.0

Choose a tag to compare

@github-actions github-actions released this 31 Oct 07:03
b5209a1

0.2.0 - 2026-10-31

Rework of the thread access, the ports, the in-memory ports and the queue rules to the sealed intent of minor 0.2. Breaking: the 0.1 API of the thread access, the inbox, the agent port and the tool content does not compile against 0.2.

Breaking

  • One error type for the ports, PortError (Refused(Reason), Fault(Cause)), replaces PortFault, ThreadError and InboxError. Only the inbox refuses (send, documents); a refusal by the thread access is a fault of the instance.
  • All port methods return PortFuture, a boxed Send future owned by this crate, instead of the BoxFuture of br-llm-clients-core (the same type behind the alias). The agent port and the inbox need no clients-core import.
  • ThreadAccess is rebuilt: one read(thread) gives the whole Conversation, used once at the start; the other operations receive complete additions (add_input_turn(InputTurn), add_entry(AiTurnRef, TurnEntry), close_turn, open_response/open_result giving a stream, complete_*, discard_*) and answer accepted, refused or fault. Removed: load of a ProjectedThread, add_incoming, the agent parameter, Incoming, WrittenEntry, TurnTarget, Stored, Opened and Completed with their Held answers. open_result no longer takes the tool call.
  • AgentPort gives an AgentId; the Agent type and the optional author are removed (the author is a build value of the node or a port of the node library).
  • MailItem::To and NewMail carry the author name of the sender and the source conversation; InboxPort::mark_read takes the EntryId of any item (mail, human message, runtime notification, runtime notice); InboxPort::wait gives nothing; documents refuses with a reason when the agent is not a recipient in to, for an unknown mail too.
  • ThreadRef::new is replaced by ThreadRef::from_uuid, which refuses a UUID that is not a v7. AgentLabel refuses only the empty text. InboxEntry::new returns InvalidInboxEntry.
  • ToolContent holds ToolBlocks (text, image, structured) without identity; into_result gives each block its identity; ToolContent::error_text takes no block id, and into_result takes a generator of block ids (impl FnMut() -> BlockId) for the blocks. ToolCode and ResultStream use PortFuture. FileSystemMode::start and save_files give PortError.
  • Session holds the conversation copy: Session::start (async, gives PortError) replaces Session::new and reads once; Session::open_response takes the &AiTurnRef instead of a TurnTarget and gives Result<ResponseStream, SessionError> instead of Opened; open_result takes no tool call; complete_response and complete_result take the AiTurnRef and the complete object and give SessionError; discard_response, discard_result and discard_all give PortError; every addition is applied to the copy under the rules of br-llm-messages before it is handed over; a refused completion discards the open object. Errors are SessionError.
  • store_at_boundary takes the session, a MailEntryBuilder (the node library builds the entry of a mail) and the id of a new input turn: items the copy holds are skipped and marked; the others go into the open AI turn of the agent after its last entry, else into one new input turn with one entry each; every kind of item is marked as read after its entry is handed over. BoundaryError carries a SessionError; BoundaryFault is removed.
  • AgenticNode::restart and Restart are removed.
  • The in-memory ports are rebuilt (MemoryHub is removed): they take one shared Record and keep one ordered record of every call, with what the streams take; the thread access keeps the completed additions and starts from the conversation of the test; there is no link between the in-memory inbox and the in-memory thread access; MemoryAgentPort takes its identities from the test (with_agent replaces with_author; it mints a UUIDv7 for a label the test did not name); MemoryHub and its methods (create_thread, conversation, inbox, thread_access, deliver_entry, put_document, is_open, is_read, set_author, set_source, response_deltas, result_fragments) and MemoryWorkspace::loads and saves are removed (read Record::calls instead); every constructor of an in-memory port takes the &Record; FixedThreadPort, FixedToolsPort and MemoryWorkspace take the record.
  • Boundary, BoundaryError, SessionError, ToolBlock and memory::Call are #[non_exhaustive]; SessionError has no From<PortError> (an inbox error never becomes a fault through ?); BoundaryError keeps its fault as the source.

Added

  • Session::add_input_turn, Session::add_entry and Session::close_turn: each applies the addition to the copy under the rules of br-llm-messages (a refusal is a SessionError::Rules, or a SessionError::WrongTurn for add_entry and close_turn, and nothing is handed over; the first add_entry for an AiTurnRef creates the AI turn), then hands it to the thread access (a fault is a SessionError::Port).
  • PortFuture, PortError (with PortError::refused, PortError::fault, PortError::is_refusal and PortError::reason), AiTurnRef (public fields id, agent and author), ToolBlock (with ToolBlock::text), SessionError (Port, Rules, with From<br_llm_messages::Error>), MailEntryBuilder, InvalidThreadRef, InvalidInboxEntry.
  • mail_documents (documents only for a mail whose entry the conversation holds as an incoming entry), Queue::read (fills the queue from the batches of the inbox; it gives a PortError::Fault when the inbox gives the same cursor twice in a row, and keeps the items it already read), Boundary::unmarked, Session::holds, Session::holds_incoming, Session::open_ai_turn, Session::thread and Session::conversation (the thread reference and the copy of the conversation that the instance holds).
  • Re-exports of the types of br-graph-contract that the ports name: Fields, InstanceId, Reason, Cause, CancelView, Event, InputReader, Node, and of the StreamSink of br-llm-clients-core.
  • memory::Record (Record::new, Record::calls) and memory::Call (#[non_exhaustive]; one variant per call that a port receives, such as Call::Read, Call::AddInputTurn or Call::InboxSend, and per item a stream takes), memory::SentMail, MemoryThreadAccess::new (the record, the thread and the conversation the test gives), MemoryInbox::new, MemoryAgentPort::new and MemoryWorkspace::new (each takes the &Record; MemoryWorkspace::new also takes the files), MemoryThreadAccess::empty (an empty conversation with the identity the test gives), MemoryThreadAccess::conversation and MemoryThreadAccess::is_open (a test looks at the conversation and at the open objects), MemoryInbox::deliver_entry, MemoryInbox::deliver_mail (delivers a mail from a sender to its recipients, the way send does: PortError::Refused when NewMail::refusal says so, no change for a mail id delivered already), MemoryInbox::put_document, MemoryInbox::mail (the SentMail of a mail id), MemoryInbox::is_read (whether an agent has marked an item as read) and MemoryAgentPort::with_agent (sets the identity of a label).
  • SessionError::WrongTurn (a call names a turn that is not the AI turn of that agent and author, or closes an AI turn that does not exist) and SessionError::WrongMailEntry (the MailEntryBuilder gave an entry that is not the entry of its mail: another id than the mail id, or not an agent mail for a to item or not a runtime notice for a cc item, or an agent mail whose sender, author name or source conversation is not that of the item; store_at_boundary then stores and marks nothing and restores the queue).
  • A crate README (the contract of the crate).

Changed

  • WebAccess::reaches_nothing is also true for an empty whitelist.
  • result gives Event::Failed with the first error of the discards as its cause when a discard of an open object fails (in 0.1 it ignored the error); failed and cancelled still ignore it.
  • An inbox error is never a fault of the instance: mark errors no longer stop store_at_boundary (the item counts as stored or skipped and Boundary::unmarked lists the ids).

br-flow-nodes 0.2.0

Choose a tag to compare

@github-actions github-actions released this 29 Oct 16:14
c355950

0.2.0 - 2026-10-29

A map-reduce node has a fault mode for the fault of one element.

Added

  • FaultMode (NonStrict by default, Strict) and MapReduce::fault_mode(FaultMode). In NonStrict, an element fault (the internal instance sends Failed or Refused, sends Cancelled while the child token is not triggered, has an update that is refused or gives an invalid state, a panic in the apply being one case, or has an event end that reports Vanished) is not a fault of the node: the internal instance of that element is dropped, a failure is recorded for it, the child token is not triggered, the other elements go on, and the slot of the failed element is free for the next element. In Strict, the first element fault stops the node, as in 0.1.0.
  • ElementOutcome (Done(State), Failed(String), with state() and failure()): the end of one element as the reduce function gets it. A failure holds the Display of the MapReduceError for that fault as text only, without the internal state of the element, also when its update was rejected.

Changed

  • BREAKING: the default fault mode of MapReduce is NonStrict; in 0.1.0 every element fault stopped the node. A runner that wants the old behavior calls fault_mode(FaultMode::Strict).
  • BREAKING: the reduce function of MapReduce::new takes &[ElementOutcome<G>], the outcomes of the elements in map order, instead of &[State<G>]; in Strict no outcome is a failure. When every element failed, the node still sends a Result.
  • The faults of the node (an invalid initial value, an internal instance that cannot be created or carries another instance id, an internal instance whose input end is closed when it gets its one input) stop the node in both modes; the cancel token wins over any fault in both modes.

br-agentic-nodes 0.1.0

Choose a tag to compare

@github-actions github-actions released this 29 Oct 21:32
beb2ab7

0.1.0 - 2026-10-29

First release: the agent nodes and their building blocks. It builds on br-agentic-contract, br-graph-contract, br-llm-messages and br-llm-clients-core.

Added

  • ReactAgent<F>: a node of kind agentic and subkind react_agent. One node runs the whole loop: step, tool round, drain, next step, until a final response. Builder: name, Ports<F>, FallbackList, Settings (step limit, Timeouts, WebAccess, FileSystemMode) and optionally with_author.
  • GeneratorCritic<F>: a node of kind agentic and subkind generator_critic. A generator (a ReAct pass) and a critic (one structured call, no tool, no AI turn) share one thread. GeneratorCriticSpec holds the two Ladders, the RoundLimit and the CriticRequestSource (Fixed or a CriticRequestPort); with_authors, with_generator_author, with_critic_author, with_step_limit_ending(StepLimitEnding::{WithoutCriticCall, WithCriticCall}) (default: without) and with_critic_thinking_field.
  • Outcome (last response, step-limit flag, verdict) with the OutcomePort and the PromptPort that the runner gives. Ports<F> groups the ports of the contract with these.
  • The building blocks, public: Worker and WorkerParts (step, tool_round, drain, close_turn, load, steps_remaining), Slot (the settled client of a fallback list), FallbackList, Ladder, ModelClient (built with a required ThinkingExpectation), check_thinking / holds_thinking (the thinking rule), warning_text / WARNING_WINDOW (the warning near the limit), mail_tools with SEND_MAIL and GET_MAIL_FILES, and FileSystem and SandboxPath.
  • Fault, CallFailure and Stop: the faults of an instance with their source chain.

Behaviour to know

  • A client that should think is declared by the runner; br-llm-clients-core gives no such report. The declaration is required: ModelClient::new takes a ThinkingExpectation (Expected or NotExpected) and has no default. The node never reads the settings of a client.
  • The sealed intent asks the instance to enforce the web access and the sandbox. Version 0.1.0 holds the web access and the file system mode, loads the files at the start and saves them before Result, and enforces neither the web access nor the sandbox. A ToolCode receives only its arguments, a cancel view and a result stream, so no tool can reach the file system of the node. Until a later minor, the runner must give only tools that respect the web access.
  • The critic request is added to the projected thread of the critic for one call only: into the last input turn when it shows no agent output, else in a new input turn. It is never stored.
  • In a generator-critic the round counter goes back to 0 when a drain stores items, also a drain inside a pass; the critic of that round keeps the ladder entry read at the start of the pass.
  • get_mail_files acts only on ids whose entry in the projected thread is an incoming agent mail; any other entry or an absent id gives a result with the error flag.
  • After a tool round that leaves the AI turn at a boundary (no outstanding runtime call, no pending provider execution, no provider continuation) the drain stores the queue into the open AI turn; after any other tool round nothing is stored until the next boundary; at the start of a pass and after a final response it stores it where store_at_boundary puts it for an agent with no open AI turn (the last real input turn, else a new one).

br-agentic-contract 0.1.0

Choose a tag to compare

@github-actions github-actions released this 29 Oct 19:23
586d49c

0.1.0 - 2026-10-29

First release: the contract of the agentic nodes. It builds on br-graph-contract, br-llm-messages and br-llm-clients-core, and on no node library.

Added

  • AgenticNode<F> (a Node<F> that tells has_inbox() and declares restart() as Restart::{Resets, Keeps, NoCounters}) and AGENTIC, the kind of an agentic node.
  • The ports a runner gives a node, all asynchronous (BoxFuture), none of them retried: ThreadPort (state snapshot to ThreadRef), AgentPort (snapshot, instance id and AgentLabel to an Agent: an AgentId and an optional Author when the runner supplies the name at run time), ToolsPort, WorkspacePort (load, save), ThreadAccess and InboxPort. A failure of a port is a PortFault with an opaque cause. The state snapshot is the fields F of the first input.
  • ThreadAccess: load the ProjectedThread of one agent; add_input_turn, add_incoming (to a TurnTarget: an input turn, or an AI turn with its author, created by its first entry), close_turn; open_response / complete_response / discard_response and open_result / complete_result / discard_result. An Incoming addition is a MailItem (the thread access builds the entry: an agent mail for a recipient in to, a runtime notice for one in cc, with the mail id as the entry id and no attachment in the content), an InboxEntry (a human message, a runtime notification or a runtime notice, as the inbox gave it) or a WrittenEntry (an agent mail the instance writes, with the author name it gives). An addition whose identity the thread holds already answers Stored::Held and changes nothing, before the rules of br-llm-messages apply; a new addition those rules refuse is ThreadError::Refused with a Reason; any other failure is ThreadError::Fault. open_* answers Opened::Stream or Opened::Held(object); complete_* answers Completed::Added or Completed::Held(object). ResponseStream is a StreamSink of br-llm-clients-core; ResultStream takes fragments of text; neither has an error.
  • InboxPort: items (oldest first, bounded batches, an opaque Cursor for the next batch, None when the list has no more items), wait (resolves with the next item for the agent that the caller has not been given yet, by a read or by an earlier wait), mark_read (the only change to the state of a mail), send (a NewMail, whose sender is the agent of the InboxContext; refused as InboxError::MailRefused with a Reason when it has no recipient in to or lists a recipient twice; NewMail::refusal is that check; sending an id sent already changes nothing) and documents. InboxItem is a MailItem or an InboxEntry; MailItem::To carries the mail id, sender, recipients, subject, body and attachment names, MailItem::Cc the mail id and subject.
  • Tool (a ToolSpec with a ToolCode that gets the arguments, a CancelView and a ResultStream), ToolContent (whole content and error flag; an expected error is content with the error flag, not a fault), ToolFault (opaque cause) and ToolSet, which refuses a name that appears twice among the tools and the mail tools (DuplicateTool).
  • WebAccess (Full, Whitelist(Vec<Site>), None), FileSystemMode (Always, Might, Never) with FileSystemMode::start (loads the files per mode: Never does not call the load; Might has a file system only when the load gives at least one file) and save_files.
  • BoundaryError carries the Boundary done before the failure (fault and done); BoundaryFault is the cause. store_at_boundary needs a new turn id on every call: a turn id the thread holds, with an item that is not held, is refused.
  • The rules of an instance, as functions for the node libraries: Queue and store_at_boundary (stores the queued items in arrival order into the open AI turn, else the input turn that ends the thread (an input turn that shows the AI turn of another agent is not one: a new input turn is made), else one new input turn; skips and marks an item the projected thread holds; marks a mail as read only after its entry is stored; leaves unstored items in the queue), Session (opens, completes and discards objects, and discards every object still open), and result, failed and cancelled, which close the input end and discard the open objects, ignoring the errors of the discards.
  • memory: MemoryHub with MemoryThreadAccess, MemoryInbox, MemoryAgentPort, MemoryWorkspace, FixedThreadPort and FixedToolsPort, for tests. The hub keeps one Conversation for each thread and projects it for an agent; an addition is stored whole or not at all; an open object is never in the projection; a send to a stream that has ended is ignored. The hub knows the author name of an agent only when the test gives it (set_author or MemoryAgentPort::with_author); a mail of an agent without a name is a fault.

br-graph-executor 0.1.0

Choose a tag to compare

@github-actions github-actions released this 26 Oct 21:25
b63c54f

0.1.0 - 2026-10-26

First release: the executor of the graph family. It depends on br-graph-contract only, never on a library of nodes.

Added

  • start and resume: run a graph of br-graph-contract from a state and a list of nodes, or from a checkpoint.
  • RunConfig (cancel token, grace period, optional checkpoint sink and observer), Outcome (RunEnded, RunFailed, RunCancelled) and RunFailure. The cause of RunFailure::NodeFailed is optional: Some when the Failed event of the node carried one, None when the executor itself counts a Cancelled without a cancel request as a failure.
  • Checkpoint: plain serializable data (state, structure, names of the nodes that are not idle), StartError, ResumeError. Checkpoint::state_json() gives the state text and Checkpoint::nodes() the names of the nodes that are not idle; the structure is read through the serialized form.
  • StartError: NoNodes (the list of nodes is empty), UnknownNode (a listed node is not in the graph), DuplicateNode (a node is listed twice). ResumeError: today the node names are checked first (UnknownNode: the checkpoint names a node the graph lacks; DuplicateNode: a node twice; NoNodes: none), then the structure (VersionMismatch: the version differs from the graph; StructureMismatch: the node names or the edges differ), then the state (StateRejected: the state cannot be rebuilt: refused, over the limit, empty, panicking, malformed or of the wrong shape); the intent does not guarantee which fault is returned when there are several, so a checkpoint from another version can return UnknownNode, DuplicateNode or NoNodes before VersionMismatch. Malformed (the document cannot be read) comes only from Checkpoint::from_json_slice; resume never returns it.
  • CheckpointSink, SinkFuture and SinkRefusal (made by SinkRefusal::new(reason)); Observer, Observation, ObservedEvent and OutcomeKind. RunFailure, Outcome, OutcomeKind, StartError, ResumeError, ObservedEvent and Observation are #[non_exhaustive] (match them with a wildcard arm); RunConfig is not.
  • When the host drops the future of start or resume (or aborts the task that polls it), every live instance is dropped and the creation, forwarder and sink-call tasks are aborted; an abort takes effect at the next poll of each task, not before the drop returns. The observer task is not aborted: it ends when its queue is empty, so observations queued before the drop can still reach Observer::observe.
  • The Debug of Checkpoint shows the byte length of the state text, the structure and the node names, never the state text itself; so the Debug of Outcome::RunFailed, Outcome::RunCancelled and RunFailure::SinkRefused does not print the state text of the checkpoint. The reasons and causes print as the node, the fields, the edge or the sink gave them. Compare state_json() in assertions.
  • RunFailure::SinkRefused { reason, checkpoint }: the failure carries the checkpoint the sink refused (the one handed to the sink, which holds the state of its step). The checkpoint of the outcome stays the most current one and can be newer.
  • RunFailure::CheckpointNotBuilt: the state of a run could not be serialized (error or panic) when a checkpoint was built. The checkpoint of the outcome then holds an empty string as its state (never valid JSON, so every Fields refuses it): resume returns StateRejected, never a fabricated state. The failure can be listed twice (once for a step, once for the outcome).
  • Every Result step that does not end the run builds the checkpoint, whether or not the host gave a sink (unless the executor has seen the token triggered or the run is failing): a state that cannot be serialized fails the run with RunFailure::CheckpointNotBuilt even without a sink. The sink, if any, is then handed that checkpoint; without a sink the host gets checkpoints only from outcomes other than RunEnded.
  • A recorded failure is never lost as RunEnded: RunEnded is returned only when every node is idle and no failure was recorded. Every node idle with a recorded failure gives RunFailed when the executor triggered the token, and RunCancelled with the failure listed when the host did first. Such an outcome can carry a checkpoint that names no node (every node idle with a failure listed, in RunFailed or RunCancelled): resuming it is refused as NoNodes.
  • A Result already queued when the grace period ends is applied, and its edge evaluated, before the instances are dropped. With a zero grace the instances are dropped at once and a queued Result is not applied.
  • Checkpoint has one wire form, exact and independent of the serde format: {"state": "<json text>", "structure": ..., "nodes": [...]}. The state is a JSON string that holds the compact JSON text of the state (the text of State::to_json; the checkpoint of a CheckpointNotBuilt outcome holds an empty string instead), so it is kept byte for byte: object key order and integers beyond 64 bits survive through every door (Checkpoint::from_json_slice, serde_json::from_str, serde_json::from_value, #[serde(flatten)], internally tagged enums) and with any serde format. Checkpoint::state_json returns that text, and PartialEq compares it exactly (two states that differ only by key order are different).
  • Checkpoint deserialization refuses unknown fields (at the top level of the checkpoint). Checkpoint::from_json_slice returns ResumeError::Malformed for any document it cannot read, also when state is not a string.
  • resume: a panic of Fields::validate or of the state deserialization while the state is rebuilt is refused as ResumeError::StateRejected with the panic text.
  • A grace period whose deadline cannot be represented, or is too close to the limit of Instant to be rounded up by the runtime timer (Duration::MAX for instance), counts as about 30 years: the deadline never falls back to a zero grace and never panics.
  • A node, creation or edge error whose Display panics does not escape the run: the panic text is the reason of the RunFailure, and the cause is kept.
  • The payload of a caught panic is read for its text and then dropped under a guard; a payload whose Drop panics is leaked, so it cannot turn a caught panic into a panic of the run.
  • Observer::observe is synchronous and runs in a task of the host's runtime: it must return quickly. An observer that blocks its thread stalls a current_thread runtime, and so the run with it.

Known limits

  • A panic of Clone of the host's Fields while the final state of a RunEnded is returned is not caught. The state is moved out without a clone when the run holds the only reference to it.
  • A panic is caught only when unwinding is enabled.

br-flow-nodes 0.1.0

Choose a tag to compare

@github-actions github-actions released this 26 Oct 21:23
b63c54f

0.1.0 - 2026-10-26

First release: two generic programmatic nodes for the control of a graph,
built on br-graph-contract 0.1.0 only.

Added

  • Merger: a node of kind programmatic and subkind merger, made from a
    name, a ready function and a spent function. It calls the spent function
    first, then the ready function, on the state of each input alone; when the
    ready function gives true it closes its input end, reads the inputs it had
    accepted and sends one Result with the empty update. A triggered cancel
    token closes the input end and sends Cancelled.
  • Merger::late_input(LateInput) sets the policy; LateInput (Silent by default, Fail, FireAgain): what a merger does
    with an input that the spent function calls spent; Fail closes the input
    end and sends Refused with a reason.
  • MapReduce: a node of kind programmatic and subkind map_reduce, made
    from a name, a map function, an internal node, a limit (NonZeroUsize) and a
    reduce function, with an optional internal size limit set by
    MapReduce::internal_size_limit (default DEFAULT_STATE_LIMIT). It works on the state of the last input it had
    accepted, creates the internal instances one at a time in list order (at
    most limit live, a fresh UUIDv7 each and a view of the one child cancel token
    of the outer instance), applies each
    internal Result to its internal state and sends the update of the reduce
    function, which receives the final states in map order.
  • MapReduceError: the cause of the Failed of a map-reduce instance, one
    named case per fault (invalid initial value, instance not created, instance
    id mismatch, input refused, internal failed, internal refused, internal
    cancelled while the child token is not triggered, internal vanished, update
    rejected), each with the position of the internal state; #[non_exhaustive].
  • The Display of MapReduceError::InstanceNotCreated and of MapReduceError::InternalFailed ends with : {cause}, the text of their cause, so the reason of a RunFailure built from the top-level Display shows the text of the internal node; the cause stays the source. The executor reads only the top-level Display; a host that walks source() as well prints the cause twice (accepted design).
  • A panic in the map or reduce function of MapReduce, or in the ready or spent function of Merger, ends the instance with no terminal event: the executor reads EventEndError::Vanished, never Failed. A panic in the work of an internal instance (its own task) gives Failed with MapReduceError::InternalVanished. A panic is caught only when unwinding is enabled.
  • A panic in the creation of an internal instance (the call to create or the poll of its future) gives Failed with MapReduceError::InstanceNotCreated (the cause is an opaque cause holding the panic text). A panic in State::apply of an internal state (so also in the Clone, validate and Serialize of its fields, and also when validate panics during an apply) gives Failed with MapReduceError::UpdateRejected; a panic in State::create_with_limit of an initial internal state gives Failed with MapReduceError::InvalidInitialValue. Each carries the position and the panic text (the message when the payload is a &str or a String, else panic without a text message). The other internal instances are dropped as for any fault. The payload of a caught panic is read for its text and then dropped under a guard; a payload whose Drop panics is leaked and cannot escape. Once the creation of an internal instance has given its result or its panic, a later poll gives Pending and never polls the inner future again.
  • An internal node that closes its input end before it receives its one input gives MapReduceError::InputRefused, even if it has already sent a terminal event: the map-reduce node does not read the event before it gives the input.
  • A triggered cancel token before the first input gives Cancelled and closes the input end, for MapReduce and for Merger.
  • A dropped map-reduce instance (either end) triggers the child cancel token and drops its internal instances.

br-graph-contract 0.1.0

Choose a tag to compare

@github-actions github-actions released this 23 Oct 15:27
c19d55c

0.1.0 - 2026-10-23

First release: the contract of the graph family, with no run logic, no
executor and no node library.

Added

  • Fields: the conditions on the runner's data (serializable, copyable,
    comparable, self-validating, with an update type that has an empty update
    and that the fields apply to themselves). State holds the fields behind
    three checked operations: create, apply (all or nothing, the first
    state is untouched) and rebuild, each refused as InvalidState (create,
    rebuild) or ApplyError (Refused by the fields, or InvalidState) with a
    Reason, under a size limit on the JSON form of the
    fields (DEFAULT_STATE_LIMIT, 128 KiB, a positive limit given by the
    runner); a refusal for size names the serialized size and the limit.
    Input is a read-only view of a state, Reason the text of a refusal and
    Cause the opaque cause.
  • Node (name, kind, subkind, async creation of an Instance from a
    CancelView and an InstanceId), PROGRAMMATIC, Edge and Target
    (Target::End, Target::END; Target::node("END") is Target::End).
  • Instance, built with Instance::channel: the executor's InputEnd
    (give, never waits, refused as InputClosed after the close) and
    EventEnd (recv, which gives Result<Event, EventEndError>:
    Finished after the terminal event was delivered or the instance was
    dropped, Vanished when the EventSender was dropped without finish),
    the node's InputReader (next, close, drain) and EventSender
    (finish consumes the sender and sends at most one terminal Event;
    dropped gives the named future Dropped). Event is Result, Failed,
    Refused or Cancelled.
  • InstanceId: a UUIDv7 given by the creator, refused as InvalidInstanceId
    otherwise.
  • CancelToken, CancelHandle, CancelView: a cooperative, runtime-free
    signal with child tokens that keep their parent alive; CancelView::triggered
    gives the named future Triggered.
  • GraphBuilder and Graph: nodes, edges, entries and a version validated
    as a whole (BuildError, one named case per refusal), the checked edge
    evaluation (EdgeError) and Graph::structure.
  • Structure: the graph as plain, sorted, serializable data, rebuilt from
    JSON objects (a positional array is refused) through the same build rules
    (StructureError).

br-llm-graph 0.6.0

Choose a tag to compare

@github-actions github-actions released this 16 Oct 20:16
dc4cf49

0.6.0 - 2026-10-16

Breaking: a built graph describes itself as data (Graph::describe), so a
host can compare the graph it built with the graph it meant to build; edges
may declare their targets, which the build and the run then hold them to.

Added

  • Graph::describe gives a GraphDescription: the graph's entry, its
    signature, its schema (state and configuration keys with their kinds,
    and the keys that have a default, by kind; an agent's private keys are
    not part of it) and its nodes in
    registration order, each with its id, its kind, its realizes labels, its
    edge's declared targets in the order given (or undeclared) and the
    fields of its kind, nested graphs included. It runs nothing, calls no
    client, reads no state and no configuration; two builds from the same
    parts give equal descriptions, in the same order.
  • A description writes no state value, default, prompt, client, author
    name or secret; it does write fixed bounds (a fixed Limit or Count,
    the generator-critic's final_generation). The schema is {"state", "config", "defaults"?}, defaults mapping each state key that has a default to
    its kind, never the default's value, whatever its kind (a string, a
    list, a conversation). An author (of a model-calling node, of an LLM
    call's conversation request) is written by its id, {"id"}, never its
    name, which is rendered into what a model reads.
  • Its JSON, br-llm-graph-description/1 (DESCRIPTION_VERSION):
    {"description_version", "entry", "signature", "schema", "nodes"}; a
    node {"id", "kind", "realizes"?, "join"?, "edge", ...}; an edge
    {"targets": [{"node"} | {"end"}]} or {"undeclared": true}; a nested
    graph without its version. Written only: GraphDescription and
    NodeDescription implement Serialize and not Deserialize (pinned by
    compile_fail doctests).
  • The kinds: node and join (a host's node, by default), call,
    react_agent and generator_critic (a call of an agent's graph, with
    everything a call holds), map, llm_call, model_call (an agent's
    model-calling step and the critic's call) and tools (a tool node). A
    node of another kind registered as a join carries "join": true.
  • What each kind holds:
    • a call: its inputs, configuration keys and outputs with their sources
      and targets, its end label, its failure policy with its captures, and
      the description of its graph; a fixed value is written as its kind
      ({"const": kind}), never the value;
    • a map: its list and item keys, its concurrency limit, its item-failure
      policy and its body, a node without id or edge;
    • an LLM call: how its model and system are given, its request and its
      output;
    • a model-calling node: its author's id, how its model, system and history
      are given (fixed, ready_made or function, never the value), its
      tool names, its enabled key, its tool concurrency and round limit. The
      critic's node adds the critique budget, final_generation, the verdict
      retries, how the review request is given and whether the verdict shape
      is the default; the generator-critic's check holds the revision limit
      and how the rule check and the empty-answer critique are given;
    • a tool node: the names of its tools.
      A Limit or a Count is {"fixed": n} or {"config": key}.
  • Node::describe, a provided method: what a node says of itself
    (NodeDescription), by default node or join with nothing nested. A
    host's node may give its own kind label (NodeDescription::labelled; a
    label that is not an identifier or that names a library kind is refused,
    Identifier with the field kind_label) and a nested description
    (NodeDescription::nesting). A node that wraps another forwards it; the
    generator-critic's prepare does.
  • Realizes labels: Node::realizing(labels) wraps any node in
    Realizing<N>, which runs, checks and describes as its node does and
    adds the labels, free text in the order given; the library never reads
    them. NodeDescription::realizing adds labels to a description.
  • Edge::declared, a provided method: the targets an edge may give, known
    without running it; None by default (undeclared). Always declares its
    target; FnEdge::targets declares an edge built from a function, its list
    kept as given. The graph asks for the declared targets once, at build,
    and keeps them in the order given, a repeated target once at its first
    place: the run checks that copy and the description writes it.
  • GraphError::UndeclaredTarget { node, target }: a declared edge that
    returns a target it did not declare fails the run, the run's error
    itself, before any target runs. Per edge, in registration order: the
    edge's error, EmptyEdge, UndeclaredTarget, then UnknownNode.
  • GraphError::EdgeToUnknownNode { node, target }: the build refuses a
    declared node target the graph does not hold, checked last, after the
    node checks; undeclared targets and end labels are not checked.
  • The library's own graphs declare every edge's targets: the ReAct agent
    (enter, the model call with its tool nodes, its round-limit node and
    the answer, the tool nodes, the round-limit node), the generator-critic
    (route, check, critique, finish with its labels validated and
    exhausted, prepare).
  • Target is displayed (node {id}, end {label}) and serialized
    ({"node": id}, {"end": label}). The two new refusals read alike: "the
    edge of node a declares node ghost, which is not in the graph", "the edge
    of node a returned node ghost, which it does not declare".
  • The public module describe holds GraphDescription, NodeDescription,
    Realizing and DESCRIPTION_VERSION, all re-exported at the root.
    GraphDescription derives Debug, Clone and PartialEq;
    NodeDescription derives Debug, Clone, PartialEq and Default (the
    description of a node without a kind of its own). Realizing::labels
    gives a wrapped node's labels, Realizing::node the node it carries. The
    rustdoc of GraphDescription tables the fields of each kind.
  • The example describe prints the description of a graph with declared
    edges, a call, a map and an agent.

Changed

  • Breaking: an Always (or a declared FnEdge) naming a node the
    graph does not hold is refused at build with EdgeToUnknownNode; 0.5
    built it and failed the run only when the edge ran (UnknownNode).
  • Breaking: GraphError gains UndeclaredTarget and
    EdgeToUnknownNode; a match on it without a wildcard arm no longer
    compiles.
  • Breaking: Edge gains the provided method declared and Node the
    provided methods describe and realizing: a host type that implements
    them and also has inherent or trait methods of these names may need to
    disambiguate its calls.
  • GraphBuilder::build documents and applies the new refusal order:
    EdgeToUnknownNode after InvalidNode.