<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: rebornace</title>
    <description>The latest articles on DEV Community by rebornace (@rebornace).</description>
    <link>https://dev.to/rebornace</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4127161%2F90390741-aa85-460d-a5fd-8e6e7f58bcb0.jpg</url>
      <title>DEV Community: rebornace</title>
      <link>https://dev.to/rebornace</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rebornace"/>
    <language>en</language>
    <item>
      <title>Freeze a Real Run into a Corpus: How Baize Scores Agents with Deterministic Evidence</title>
      <dc:creator>rebornace</dc:creator>
      <pubDate>Wed, 28 Oct 2026 01:51:36 +0000</pubDate>
      <link>https://dev.to/rebornace/freeze-a-real-run-into-a-corpus-how-baize-scores-agents-with-deterministic-evidence-1hpa</link>
      <guid>https://dev.to/rebornace/freeze-a-real-run-into-a-corpus-how-baize-scores-agents-with-deterministic-evidence-1hpa</guid>
      <description>&lt;p&gt;Judging an agent by "how good the final reply sounds" is rarely enough.&lt;/p&gt;

&lt;p&gt;What teams actually worry about happens at the tool-call layer. After changing a prompt, swapping a model, or tweaking a tool, the replies may look better while the agent quietly starts to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hit "approve" on a ticket it hasn't fully read;&lt;/li&gt;
&lt;li&gt;call a write endpoint it should never touch;&lt;/li&gt;
&lt;li&gt;run "check inventory, then place order" in the wrong order;&lt;/li&gt;
&lt;li&gt;trigger an action twice — or, when it should act, do nothing at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that shows up by reading the streaming reply. Baize evaluates agents on the &lt;strong&gt;tool-call timeline&lt;/strong&gt; instead: not what the model said, but what it actually did — and by default the scoring is deterministic code rules, not an LLM judge.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbp19y48rglwzet46ej1e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbp19y48rglwzet46ej1e.png" alt="Baize agent behavior evaluation at a glance" width="800" height="1000"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The corpus comes from real runs, not hand-written fixtures
&lt;/h2&gt;

&lt;p&gt;The first step of evaluation is not writing test cases out of thin air — it is turning &lt;strong&gt;real runs that already happened into replayable corpus&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A run leaves a complete event stream in the runtime (tool calls, tool results, human waits, etc.). Baize exposes an online sampling pipeline in the settings page:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Find candidates&lt;/strong&gt;: filter historical runs by time window, run status, duration, and whether they contain a certain event type. The store fetches up to about &lt;strong&gt;500&lt;/strong&gt; rows in one pass; after filtering, roughly the first &lt;strong&gt;100&lt;/strong&gt; are kept as the candidate pool (current defaults, adjustable via page/API parameters).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sample deterministically&lt;/strong&gt;: sort candidates by a stable hash of &lt;code&gt;seed + run ID&lt;/code&gt;, take the first N — about &lt;strong&gt;10&lt;/strong&gt; per batch by default, hard cap &lt;strong&gt;20&lt;/strong&gt;. The same seed always picks the same batch, so the corpus is reproducible instead of jittering every run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ingest into a case&lt;/strong&gt;: freeze the run's full event stream plus its input snapshot into one evaluation case (EvalCase). The same run is ingested only once by default; repeated sampling skips it, and you can explicitly replace when needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fill in expectations&lt;/strong&gt;: a case enters as a &lt;strong&gt;draft&lt;/strong&gt;; once you state what this run "should have done," it upgrades to &lt;strong&gt;confirmed&lt;/strong&gt; — and &lt;strong&gt;only confirmed cases are scorable&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What gets collected this way is not made-up toy dialogue but paths that actually ran in production — the tickets to read, the approvals to go through, the downstream calls that matter, all still in the events.&lt;/p&gt;

&lt;p&gt;The same "assertion contract" has two more entry points that share the same scoring semantics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;EVAL.yaml&lt;/code&gt;&lt;/strong&gt; next to a skill's directory, for live evaluation of the skill;&lt;/li&gt;
&lt;li&gt;the repo script &lt;strong&gt;&lt;code&gt;scripts/evidence-eval&lt;/code&gt;&lt;/strong&gt;, for offline replay or CI.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rest of this article follows the settings-page corpus as the main line and ties all three entry points together at the end.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. A case is a set of behavior assertions
&lt;/h2&gt;

&lt;p&gt;The core of a case is not a "reference answer text" but a set of assertions about tool behavior (fields use snake_case in JSON/YAML):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Required reads&lt;/td&gt;
&lt;td&gt;&lt;code&gt;required_reads&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read-only tools that must have succeeded before any write&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Consequential&lt;/td&gt;
&lt;td&gt;&lt;code&gt;consequential&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Side-effect actions that must occur; "any one of" via &lt;code&gt;expect_consequential_any&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chain&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;chain&lt;/code&gt; (V2)&lt;/td&gt;
&lt;td&gt;"A must succeed, then B must appear afterwards"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Forbidden tools&lt;/td&gt;
&lt;td&gt;&lt;code&gt;forbid_tools&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tools that must never be called&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arg contains&lt;/td&gt;
&lt;td&gt;&lt;code&gt;arg_contains&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A tool call's arguments must contain a certain substring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Downstream state&lt;/td&gt;
&lt;td&gt;&lt;code&gt;expect_state&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Compare against a downstream ticket snapshot (or a live state check) at scoring time&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The safety principle is simple: &lt;strong&gt;read first, then write.&lt;/strong&gt; Before a side-effecting write happens, the read-only queries it depends on must have completed successfully.&lt;/p&gt;

&lt;p&gt;When filling expectations in the UI, protocol version, required reads, and consequential are enough to score; finer assertions — forbidden tools, arg contains, chain, downstream state — can be maintained in the exported JSON or updated via the management API.&lt;/p&gt;

&lt;p&gt;One note on the chain fields: the pair is named &lt;code&gt;after&lt;/code&gt; / &lt;code&gt;before&lt;/code&gt;, with the semantics — &lt;strong&gt;&lt;code&gt;after&lt;/code&gt; must succeed first, and then &lt;code&gt;before&lt;/code&gt; must appear later on the timeline&lt;/strong&gt;. &lt;code&gt;before&lt;/code&gt; here is a code field name, not "happens before" in the literal sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Scoring: rebuild the tool timeline from the event stream
&lt;/h2&gt;

&lt;p&gt;Scoring does not call an LLM judge. It re-assembles the frozen event stream into an ordered &lt;strong&gt;tool-call timeline&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;llm.tool_call&lt;/code&gt;: register one call (name, arguments);&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tool.result&lt;/code&gt;: attach that call's result and whether it errored;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hitl.waiting&lt;/code&gt;: mark that it was waiting on human approval.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then assertions are applied to the timeline one by one. Whether a run passes comes down to whether any failure code fired:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Code&lt;/th&gt;
&lt;th&gt;Trigger (plain language)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BSR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Required reads were not all done&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PAR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Acted before reading, or wrote in a read-only scenario&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NO_ACTION&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;None of the expected consequential actions happened&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MULTI&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;More than one consequential action (mainly V1; one is usually expected)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GAP&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Chain broken: the required "A then B" order did not appear&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FORBID&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A forbidden tool was called&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ARG&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tool arguments missed the required content&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;STATE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Downstream ticket snapshot missing or mismatched&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BAD_PROTOCOL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Unrecognized protocol version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TIMEOUT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The run status used for scoring was marked as timed out&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Pass = zero failure codes.&lt;/strong&gt; The same events plus the same assertions always produce the same score: no judge-model noise, no scoring tokens consumed, safe to replay inside any automation or CI.&lt;/p&gt;

&lt;p&gt;Optionally, a confirmed case can run a &lt;strong&gt;live state check&lt;/strong&gt;: read-only fetch of the current downstream ticket snapshot (mock or real), compare against expectations, and re-score — useful when you want to verify the latest state on top of a snapshot.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Assertion protocols follow business maturity
&lt;/h2&gt;

&lt;p&gt;A case carries a protocol version (V0 / V1 / V2), with strictly increasing strictness:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;V0 (read-only probe)&lt;/strong&gt;: all required reads must complete, and no write action may occur — checks whether this scenario is being treated as a pure query;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;V1 (behavior closure)&lt;/strong&gt;: at least one expected consequential action must fire; no acting before reads, no multiple consequentials;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;V2 (ordering constraints)&lt;/strong&gt;: on top of V1, add chains, e.g. "check inventory before creating an order."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same corpus can be cut to a stricter protocol as the business grows from read-only guardrail to side-effecting closure — the assertions tighten with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Making the automatic scorer itself trustworthy
&lt;/h2&gt;

&lt;p&gt;Deterministic scoring is not born correct. Baize adds a &lt;strong&gt;human calibration&lt;/strong&gt; layer:&lt;/p&gt;

&lt;p&gt;For each already-scored confirmed case, a human can mark "agree / disagree" and leave a note; the system aggregates an agreement rate (fraction where the automatic verdict matches the human one). After tightening or relaxing the scoring rules, watch whether this rate drops — the automatic scorer itself is under continuous review.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. In practice: sample in the UI, export the results
&lt;/h2&gt;

&lt;p&gt;It is lightweight: pick the time window and filters in the settings page, choose draft mode or a template-driven score mode, and one batch ingests a set of real runs into corpus. Draft mode only collects; you fill expectations and score later. With a template, you can score immediately — suited to when you already know what "this kind of failure" looks like.&lt;/p&gt;

&lt;p&gt;Each case also exports to JSON with the same semantics as evidence-eval (case assertions + event stream in one package) for offline replay in external scripts or CI. The repo's PowerShell script splits &lt;code&gt;case&lt;/code&gt; and &lt;code&gt;events&lt;/code&gt; by its input parameters, or you can reuse the same Go scoring logic directly.&lt;/p&gt;

&lt;p&gt;From failed or half-successful runs, the settings page also offers &lt;strong&gt;skill-draft&lt;/strong&gt;: turn the trajectory into a skill draft, then install it after human review — evaluation is not only scoring; it can also distill bad paths into reusable skills and conventions.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Three entry points, one evidence contract
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Entry point&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Settings "evaluation corpus"&lt;/td&gt;
&lt;td&gt;Sample real runs, fill expectations, re-score, human review, export&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skill-side &lt;code&gt;EVAL.yaml&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Lightweight evaluation contract bundled with a skill, live-runnable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scripts/evidence-eval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Offline fixtures, scripted batch runs, local or CI&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The common ground: &lt;strong&gt;judge the tool timeline, speak in failure codes, and by default don't use an LLM as the judge.&lt;/strong&gt; After changing prompts, models, or tool routing, look at whether this corpus regresses first — far more stable than staring at a single reply.&lt;/p&gt;




&lt;p&gt;Baize is a team-facing, self-hosted AI Agent runtime: a single Go binary, MIT-licensed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Source: &lt;a href="https://github.com/rebornace/baize" rel="noopener noreferrer"&gt;https://github.com/rebornace/baize&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docs: &lt;a href="https://rebornace.github.io/baize" rel="noopener noreferrer"&gt;https://rebornace.github.io/baize&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Turning "did the agent move wrongly" from eyeballing logs into a replayable, repeatable regression corpus with a visible human-agreement rate is how runtime behavior is actually held in line.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>opensource</category>
      <category>go</category>
    </item>
    <item>
      <title>Restart-Safe and Duplicate-Free: How Baize Makes Long-running Agent Runs Resumable</title>
      <dc:creator>rebornace</dc:creator>
      <pubDate>Mon, 26 Oct 2026 02:47:28 +0000</pubDate>
      <link>https://dev.to/rebornace/restart-safe-and-duplicate-free-how-baize-makes-long-running-agent-runs-resumable-2275</link>
      <guid>https://dev.to/rebornace/restart-safe-and-duplicate-free-how-baize-makes-long-running-agent-runs-resumable-2275</guid>
      <description>&lt;p&gt;An agent &lt;strong&gt;Run&lt;/strong&gt; is not a single HTTP request. It can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;call a dozen tools in a row and run for several minutes;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;hit a write action that needs human approval — and that wait can last hours;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;be triggered by a schedule, e.g. a daily 9 AM summary;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;after grouped subtasks are enabled, spawn a bounded number of &lt;strong&gt;group-scoped child runs&lt;/strong&gt; (one level only), isolating the tool surfaces and approval flows of different business systems;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;after finishing, push the result back to IM or call back a business system.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Along this chain, processes restart, scale out to multiple replicas, downstream APIs time out, and browsers disconnect. If a Run were just a goroutine in memory, a pending channel, then any hiccup would either lose the task or run it twice.&lt;/p&gt;

&lt;p&gt;Baize handles this class of &lt;strong&gt;runtime reliability&lt;/strong&gt; in the runtime layer. The core idea is one sentence: &lt;strong&gt;make a Run a first-class row in the database, not a goroutine in memory.&lt;/strong&gt; The queue and workers can restart at any time or scale out freely; "no loss, no duplicate" is guaranteed by database state, leases, and a few background daemon loops.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The numbers here (8 workers, a 60s lease, etc.) are current defaults and are configurable; the mechanisms do not depend on any specific cloud vendor or middleware.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fli7ux7jjvuxubq4xvzvs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fli7ux7jjvuxubq4xvzvs.png" alt="baize-longtask-resilience-diagram" width="800" height="1274"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Every trigger becomes a Job; the Run row is the source of truth
&lt;/h2&gt;

&lt;p&gt;All trigger sources — chat, webhooks, IM channel messages, schedules, human approvals, child-run receipts — are unified into a single &lt;strong&gt;Job&lt;/strong&gt; enqueued into the queue. Think of it as a work order asking the runtime to do something. There are three job types:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Normal run&lt;/td&gt;
&lt;td&gt;Start a chat turn, process a channel message, a scheduled trigger, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Approval resume&lt;/td&gt;
&lt;td&gt;A Run was paused on human approval (or login); once a human approves/rejects, continue it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Child receipt&lt;/td&gt;
&lt;td&gt;A child run finished and wakes its parent with a structured result.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At the same time, every Run has a row in the primary datastore, and the state machine can be read intuitively as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(occasionally) queued → running → succeeded / failed / cancelled / rejected
running ⇄ waiting for external signal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A newly created Run from chat usually goes straight to &lt;strong&gt;running&lt;/strong&gt;; &lt;strong&gt;queued&lt;/strong&gt; mostly appears in crash recovery and re-enqueue scenarios. The important state is the middle one: &lt;strong&gt;waiting for external signal&lt;/strong&gt; (internal name &lt;code&gt;waiting_human&lt;/code&gt;). Whether a write needs approval, a downstream login is required, or a parent is waiting for a child receipt, the Run enters this single suspended state; the reason (approval / login / child receipt) is stored in the suspension payload instead of being split into many incompatible state names.&lt;/p&gt;

&lt;p&gt;Workers pull Jobs off the queue with a default concurrency of &lt;strong&gt;8&lt;/strong&gt;. The queue, event bus, rate limiter, primary datastore, and blob storage all go through &lt;strong&gt;swappable drivers&lt;/strong&gt;: implement the unified interface, register it, and change the driver name in config. The built-in drivers are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Primary datastore:&lt;/strong&gt; SQLite by default, plus Postgres and in-memory;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Queue / event bus / rate limiter:&lt;/strong&gt; in-memory by default, Redis for multi-replica setups;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Blob:&lt;/strong&gt; local files, S3, in-memory.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture is &lt;strong&gt;not tied to&lt;/strong&gt; any particular database or message middleware. If a team already runs MySQL, Kafka, etc., they can extend the same driver contract without touching the orchestration core — the built-in implementations are "a subset that works today," not an upper bound.&lt;/p&gt;

&lt;p&gt;One design tradeoff runs through the whole article: &lt;strong&gt;the queue only carries delivery; it is not the source of truth. The Run row in the primary datastore always is.&lt;/strong&gt; All recovery logic reduces to "query the DB, change state, then re-enqueue."&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Lease + heartbeat: how one Run executes exactly once across replicas
&lt;/h2&gt;

&lt;p&gt;After scaling out, the same Job may be visible to multiple replicas at once. Baize uses a &lt;strong&gt;database lease&lt;/strong&gt; for atomic claiming, not an in-process lock in the worker. Before actually starting, the executor tries to "lease" the Run, semantically equivalent to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;runs&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;lease_until&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'queued'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'running'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'waiting_human'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lease_until&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;lease_until&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The update only matches when the lease is empty or already expired, so only one claim succeeds. Default parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Lease TTL is &lt;strong&gt;60 seconds&lt;/strong&gt;;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;During execution, a heartbeat renews it roughly every &lt;strong&gt;20 seconds&lt;/strong&gt; (one third of the TTL), proving "I'm alive and still running";&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The lease is released when execution finishes;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If claiming fails (another replica holds a valid lease), the worker skips it and does nothing.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Result: even if the same Run is delivered to multiple replicas due to re-enqueue, only one actually executes it.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Reconciler: orphan Runs auto-resume after a worker crash
&lt;/h2&gt;

&lt;p&gt;When a worker crashes, the Runs it claimed may still be stuck in &lt;code&gt;running&lt;/code&gt;, with the lease still under its name. A &lt;strong&gt;reconciler&lt;/strong&gt; covers this.&lt;/p&gt;

&lt;p&gt;On startup it runs one scan immediately, then scans roughly every &lt;strong&gt;15 seconds&lt;/strong&gt;. Each round:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Finds orphan Runs still in &lt;code&gt;queued&lt;/code&gt;/&lt;code&gt;running&lt;/code&gt; whose lease has expired (or whose creation is more than about &lt;strong&gt;30 seconds&lt;/strong&gt; old and unclaimed);&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Re-enqueues a "normal run" job for each orphan;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Handles about &lt;strong&gt;100&lt;/strong&gt; per batch, leaving the rest to the next round.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The 30-second grace window avoids catching normal Runs that were just created and not yet claimed, reducing false positives.&lt;/p&gt;

&lt;p&gt;Note: Runs stuck in "waiting for external signal" are &lt;strong&gt;not&lt;/strong&gt; treated as orphans and forcefully re-run — they are waiting for approval, login, or a child receipt and go through dedicated resume jobs.&lt;/p&gt;

&lt;p&gt;Why is there no double execution? Two layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A healthy running Run keeps heartbeating its lease, so the reconciler never picks it up;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Even if something is re-enqueued in an extreme case, the execution entry has three gates: already in a terminal state → skip; waiting on a human but received a normal-run job → skip; failed to acquire a lease → skip.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Effect: after a process crash, the task usually resumes from its DB breakpoint within one reconcile cycle, without double-starting.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Shutdown doesn't kill in-flight work; approvals survive restarts
&lt;/h2&gt;

&lt;p&gt;What happens to in-flight Runs during a deploy restart?&lt;/p&gt;

&lt;p&gt;Each Job runs on an &lt;strong&gt;independent context&lt;/strong&gt;, deliberately not tied to the "pull new jobs" lifecycle. On shutdown, the worker stops taking new work first and allows about a &lt;strong&gt;5-second&lt;/strong&gt; grace window: short jobs finish if possible; Runs long-paused on approval may lose their in-process wait goroutine, but the row in the primary DB still says "waiting for external signal." After restart, a cold-recovery path reconnects, rather than depending on a goroutine that lives forever.&lt;/p&gt;

&lt;p&gt;Human approval is the most extreme wait in long tasks — it can span hours or days. Baize covers it with two paths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Same-process hot path:&lt;/strong&gt; while someone is still waiting for this Run on this host, an approval wakes it directly without re-acquiring a lease;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cross-restart cold path:&lt;/strong&gt; the process has changed and the in-process waiter is gone; the approval is re-enqueued as an "approval resume" job, which acquires a lease and continues from the suspended state in the DB.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After grouped subtasks are enabled, the parent also enters "waiting for external signal," with the reason being "waiting for a child receipt"; when the child finishes, a "child receipt" job wakes the parent. Child runs are an &lt;strong&gt;optional, one-level-only&lt;/strong&gt; capability, with a concurrency cap on active children per parent (the current implementation allows at most &lt;strong&gt;2&lt;/strong&gt; active child runs) — meant to isolate different business systems' tool surfaces and approvals, not to grow into an infinite fan-out of generic sub-agent trees.&lt;/p&gt;

&lt;p&gt;Also, when entering a human wait, the worker &lt;strong&gt;releases its slot&lt;/strong&gt;: it no longer holds a worker slot idle, leaving concurrency for tasks that can still make progress; after the human approves, the run cold-resumes.&lt;/p&gt;

&lt;p&gt;Every job is wrapped in panic recovery on the outside: success, failure, and errors all ack the message, so a poison message never jams the queue.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Frontend disconnect doesn't lose the stream: numbered, replayable events
&lt;/h2&gt;

&lt;p&gt;A long task is streaming: thoughts, tool calls, and incremental text are continuously emitted. Backgrounding a tab or network jitter can drop the SSE connection.&lt;/p&gt;

&lt;p&gt;Baize writes every event to an event table, and each frame pushed to the frontend carries a monotonically increasing sequence number. On reconnect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;It prefers the standard &lt;code&gt;Last-Event-ID&lt;/code&gt; header, and also supports &lt;code&gt;?after=N&lt;/code&gt;;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It replays the missing events in order from that sequence number;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When the Run reaches a terminal state, one final end frame is emitted.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Between replicas, a lightweight event bus carries only a &lt;strong&gt;wake-up nudge&lt;/strong&gt; ("this Run has new events") — never the payload. The payload is always re-read from the primary DB by sequence number, so any replica can continue serving the same Run. A lost nudge is harmless: reconnect or periodic fallback fetch still fills in by sequence number.&lt;/p&gt;

&lt;p&gt;Refreshing the page, reconnecting, or even landing on a different replica — the process and results are never lost.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Scheduled tasks: periodic triggers without duplicates or missed cycles
&lt;/h2&gt;

&lt;p&gt;Chat scheduled tasks (once / daily / weekly) are essentially unattended long-task triggers. Each task is persisted in the DB with state (active / paused / completed), the next fire time, and a placeholder field for "which run is currently in flight."&lt;/p&gt;

&lt;p&gt;The schedule scan runs roughly every &lt;strong&gt;15 seconds&lt;/strong&gt;; on startup it first clears stale "placeholder intermediate states" left by an abnormal shutdown, so a fake placeholder never blocks later cycles. Each round does three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Release terminal placeholders:&lt;/strong&gt; if the run pointed to by the placeholder has finished, free the slot, so a long-completed Run never blocks future cycles forever;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Advance periodic time:&lt;/strong&gt; for daily/weekly tasks, even if the previous round is still running, once the next time point arrives, advance "next fire" forward first — avoiding a backlog storm after a long task ends;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Atomically claim due tasks:&lt;/strong&gt; use a conditional update to grab a task that is "due and idle," then start a new Run. Once-only tasks are marked completed; periodic tasks write back the next time.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The supporting fallbacks also live in the DB: failing to start a Run releases the placeholder and records the error; times are computed in IANA time zones; each account has an active-schedule cap (default &lt;strong&gt;20&lt;/strong&gt;). Atomic placeholders, terminal release, and startup cleanup work together to prevent duplicate triggers at the same moment, and to ensure a crash never permanently skips a later cycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Result delivery: the transactional outbox and backoff
&lt;/h2&gt;

&lt;p&gt;When a Run finishes, the result must be pushed back to IM or a business webhook. If the process crashes between "generating the reply" and "sending the HTTP request," or the downstream returns a temporary 5xx, the message must not be lost.&lt;/p&gt;

&lt;p&gt;Baize uses a &lt;strong&gt;transactional outbox&lt;/strong&gt; for channel outbound: it first writes the message to an outbox table keyed by an idempotent delivery key, then a separate deliverer sends it — generation and delivery are decoupled.&lt;/p&gt;

&lt;p&gt;The deliverer pulls a batch of due messages every second (or when woken), about 20 per batch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A &lt;code&gt;2xx&lt;/code&gt; response → marked delivered;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Network errors, &lt;code&gt;429&lt;/code&gt;, &lt;code&gt;5xx&lt;/code&gt; → exponential backoff (1s, 2s, 4s… capped around &lt;strong&gt;60s&lt;/strong&gt;) then retry;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Non-retryable errors, or exceeding the max retry count → dead letter, resettable manually in the UI to redeliver.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Large objects like images and attachments are written to Blob first (local files by default, switchable to S3; also a swappable driver), and the outbox stores only the reference — never stuffing binary into the queue table. Business-side webhook notifications have the same reliable delivery and backoff. HTTP plugin callbacks write their events back to the Run's trail through the event-persistence path, which is a separate line from the channel outbox — both emphasize "persist first, then send outward," but they should not be mixed into the same box.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. The engine layer also holds context for long tasks
&lt;/h2&gt;

&lt;p&gt;So far this is the &lt;strong&gt;runtime&lt;/strong&gt;: don't lose tasks, don't double them, don't block the process while waiting. Long tasks have another side — long conversations, huge tool results, occasional model timeouts — which is handled mainly in the &lt;strong&gt;execution engine&lt;/strong&gt;, orthogonal to the queue/lease and equally transparent to business code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;When turns pile up, do rolling summaries, and optionally project "key points of the long conversation" (only changing what the model sees as input, not the original messages);&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Especially large tool results or pastes can be offloaded to the session workspace, so the model side only gets a preview and a path, reading back segments on demand;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Model-call failures retry and fall back; tool timeouts can be split; read-only tools in the same turn run in parallel;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Human waits release the worker slot (see section 4), so one approval doesn't fill all concurrency.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The two layers fit together as: &lt;strong&gt;the runtime guarantees "this task still exists and can resume"; the engine guarantees "it can resume, and the context holds."&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  9. One table: failure scenarios and their guards
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Failure scenario&lt;/th&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Same Run seen by multiple replicas at once&lt;/td&gt;
&lt;td&gt;DB-backed CAS lease&lt;/td&gt;
&lt;td&gt;Only one replica executes it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worker crash, Run stuck in &lt;code&gt;running&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Lease expiry + periodic reconciler scan&lt;/td&gt;
&lt;td&gt;Auto re-enqueued and resumed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deploy / process shutdown&lt;/td&gt;
&lt;td&gt;Job-independent context + short grace + DB source of truth&lt;/td&gt;
&lt;td&gt;Short in-flight jobs finish; long waits recover from DB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Process restart during an hours-long approval&lt;/td&gt;
&lt;td&gt;Suspended state persisted + approval-resume job&lt;/td&gt;
&lt;td&gt;Continues from the breakpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parent restarted when a child finishes&lt;/td&gt;
&lt;td&gt;Suspended state persisted + child-receipt job&lt;/td&gt;
&lt;td&gt;Parent/child results not lost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser disconnect, refresh, replica switch&lt;/td&gt;
&lt;td&gt;Event sequence + reconnect replay (plus fallback fetch)&lt;/td&gt;
&lt;td&gt;Process and results not lost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Crash at the moment a schedule fires&lt;/td&gt;
&lt;td&gt;Atomic placeholder + startup cleanup of intermediate state&lt;/td&gt;
&lt;td&gt;No duplicates, no missed cycles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Downstream 5xx / timeout / rate limit&lt;/td&gt;
&lt;td&gt;Outbox + exponential backoff + dead letter&lt;/td&gt;
&lt;td&gt;Outbound replies not lost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unparseable poison message in the queue&lt;/td&gt;
&lt;td&gt;Deserialization failure acks directly&lt;/td&gt;
&lt;td&gt;Never blocks later jobs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  10. Reliability lives in the runtime
&lt;/h2&gt;

&lt;p&gt;These mechanisms are mostly transparent to the people writing agents and configuring skills on top: they face chat, tools, and approvals, without having to write their own retry, recovery, and dedup.&lt;/p&gt;

&lt;p&gt;A single machine can use the default SQLite and get "restart-resumable, wait-continuable" without first standing up a full middleware stack. When you need to scale out, switch the queue/event bus to the existing Redis driver, switch the primary DB to Postgres, or extend the driver contract to the team's own databases and messaging systems — &lt;strong&gt;the same execution code still holds.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For team-facing agents, the real dividing line is rarely whether a single "think → choose tool → execute" loop works. It's this: when the agent is attached to real business systems, must wait for human approval, must run on a schedule, and must push results back across IM — after a process restart or a network hiccup, are the results still deterministic? Keeping these concerns in the runtime layer is what lets the upper layers focus on the business itself.&lt;/p&gt;




&lt;p&gt;Baize is a team-facing, self-hosted AI Agent runtime: a single Go binary, MIT-licensed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Source: &lt;a href="https://github.com/rebornace/baize" rel="noopener noreferrer"&gt;https://github.com/rebornace/baize&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Docs: &lt;a href="https://rebornace.github.io/baize" rel="noopener noreferrer"&gt;https://rebornace.github.io/baize&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More runtime internals will follow, including a replayable evaluation corpus.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>agents</category>
      <category>go</category>
    </item>
    <item>
      <title>Two Paths to Take an Agent Runtime onto Mobile: a Remote Console and an On-Device Go SDK</title>
      <dc:creator>rebornace</dc:creator>
      <pubDate>Sat, 24 Oct 2026 14:19:44 +0000</pubDate>
      <link>https://dev.to/rebornace/two-paths-to-take-an-agent-runtime-onto-mobile-a-remote-console-and-an-on-device-go-sdk-1e31</link>
      <guid>https://dev.to/rebornace/two-paths-to-take-an-agent-runtime-onto-mobile-a-remote-console-and-an-on-device-go-sdk-1e31</guid>
      <description>&lt;p&gt;&lt;strong&gt;Project: Baize — a team-facing AI assistant runtime (Go 1.25+, MIT)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/rebornace/baize" rel="noopener noreferrer"&gt;https://github.com/rebornace/baize&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the follow-up to &lt;a href="https://dev.to/rebornace/from-a-single-agent-loop-to-an-auditable-runtime-baizes-architecture-5b5e"&gt;From a Single Agent Loop to an Auditable Runtime: Baize's Architecture&lt;/a&gt;. That post covered the server-side runtime; this one focuses on the two mobile paths.&lt;/p&gt;

&lt;p&gt;Baize is a single-process Agent runtime written in Go that sits alongside your business systems, consolidating the LLM, tools, legacy HTTP APIs, and human checkpoints into auditable runs. Once the server side was stable, mobile was not built as a simple mobile web page. Instead, Baize offers two paths: one uses a Capacitor shell to remotely control the full console, so you can approve work on the go with zero development; the other compiles the Go orchestration core into an Android AAR and an iOS XCFramework via gomobile, so the conversation runs on the device and the UI is drawn entirely by your own app. This article walks through the actual code: how the two paths divide the work, the key and approval design of the on-device SDK, and how to choose.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. There are actually two kinds of mobile demand
&lt;/h2&gt;

&lt;p&gt;Before building anything for mobile, it helps to separate the requirements, because they point at two different problems.&lt;/p&gt;

&lt;p&gt;The first kind is internal teams and operators handling things on the go. They need to read conversations, do human approvals, change settings, and deal with logins from their phone — essentially "carry the desktop console in my pocket." In this scenario the conversation engine, connectors, and approval flow are already running stably on the server; the phone only needs an entry point, not a second implementation.&lt;/p&gt;

&lt;p&gt;The second kind is embedding Agent capabilities into your own product. These teams want a branded, native UI, with the conversation and tool loop running directly on the device and less dependence on an always-on sidecar, and they have their own requirements for UI and key management. Here the phone is not a remote control — it is where the capability lives.&lt;/p&gt;

&lt;p&gt;The two demands differ in who owns the UI, where the code runs, and the integration cost. Forcing them into a single path would leave embedders stuck with a generic page, while operators who just wanted mobile approvals would be forced to build a native UI. Baize therefore splits them into two paths, each handling one side.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. One diagram: the two paths
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft11yg4ro4kxq80k9ujro.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft11yg4ro4kxq80k9ujro.png" alt="baize-mobile-paths-diagram" width="800" height="1000"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Path A is a remote-control console: the phone shell does not run a conversation engine, it loads the full remote console from the same origin. Path B is the on-device SDK: the Go orchestration core is compiled into the app, the UI is drawn by the app, and the conversation runs on the device. Let's expand each in turn.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Path A: a thin shell, the full console with zero development
&lt;/h2&gt;

&lt;p&gt;Path A is deliberately light. You can open the reachable &lt;code&gt;/ui&lt;/code&gt; directly in a mobile browser, or use the Capacitor project in the repository to wrap a WebView and build an Android or iOS app.&lt;/p&gt;

&lt;p&gt;The shell loads the remote console from the same origin: once you configure the server address, the WebView simply opens the remote &lt;code&gt;/ui&lt;/code&gt;. Conversation, settings, human approvals, connectors, MCP, messaging channels, and browser login are all identical to the desktop — nothing is lost by moving to the phone. The only things the shell handles itself are mobile nuisances: the safe area, the keyboard, and opening login pages in the system browser.&lt;/p&gt;

&lt;p&gt;There is one deliberate choice here: the shell loads the remote &lt;code&gt;/ui&lt;/code&gt; from the same origin by default rather than proxying another layer of HTTP inside the shell. Conversation and approval depend on server-pushed events (SSE); adding an HTTP proxy inside the shell would buffer those streaming responses and break the experience of messages arriving one by one. Loading from the same origin keeps streaming replies and approvals smooth inside the shell.&lt;/p&gt;

&lt;p&gt;From an architecture standpoint, the shell runs no conversation engine at all — it is, quite literally, a remote control. The integration cost is close to zero: without writing any mobile code, the team gets the same operational capabilities on a phone as on desktop. The only deployment detail is that the public address must be reachable from the phone — the phone talks to the server address, not the &lt;a href="https://localhost" rel="noopener noreferrer"&gt;localhost&lt;/a&gt; loopback on your computer.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Path B: compile the Go orchestration core into the app
&lt;/h2&gt;

&lt;p&gt;Path B answers a different need: embedding Agent capabilities into your own product, with a native, branded UI.&lt;/p&gt;

&lt;p&gt;Baize compiles the Go orchestration core into an Android AAR and an iOS XCFramework via gomobile, and exposes a binding-friendly public API (the binding package is &lt;code&gt;mobileagent&lt;/code&gt;). There is a real engineering detail here: gomobile cannot bind Go &lt;code&gt;internal&lt;/code&gt; packages, so Baize re-exports the binding-friendly interfaces from a separate top-level package, while the internal implementation stays under &lt;code&gt;internal&lt;/code&gt;. The binding layer only surfaces the handful of methods an app actually needs.&lt;/p&gt;

&lt;p&gt;What runs on the device is a slimmed-down, OpenAI-compatible conversation and tool loop. The division of work between the app and the SDK is clear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;app&lt;/strong&gt; owns layout, theming, the history list, the input box, and the approval card; it owns secure storage of keys and the remote-config fetch strategy; and it owns thread management (network calls must not block the main thread).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;mobileagent&lt;/strong&gt; owns the session, the conversation, the tool loop, and the pause/resume of approval. Every result is returned as a JSON string, and it never touches any UI.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The call surface is intentionally small:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Configure&lt;/code&gt; sets the model endpoint, key, and model name once.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Send&lt;/code&gt; runs one conversation turn and stops when it hits a tool that needs approval.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Approve&lt;/code&gt; and &lt;code&gt;Deny&lt;/code&gt; handle the two approval decisions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ChatOnce&lt;/code&gt; does a single-turn connectivity test without tools.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Reset&lt;/code&gt; clears the in-memory history.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;NewAgent&lt;/code&gt; creates an independent session.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;MessageCount&lt;/code&gt; returns the number of messages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;HasAPIKey&lt;/code&gt; reports whether a key is already held in memory.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Why the on-device SDK is slimmed down
&lt;/h2&gt;

&lt;p&gt;The on-device SDK deliberately does not move the full server-side runtime into the phone. On the device it keeps only an in-memory session and a minimal tool surface: today there are two tools, &lt;code&gt;get_time&lt;/code&gt; and &lt;code&gt;http_get&lt;/code&gt;. &lt;code&gt;get_time&lt;/code&gt; executes directly and returns the time, while &lt;code&gt;http_get&lt;/code&gt; must pass human approval first. The full connector set, MCP bridging, messaging channels, and the operational approval product surface stay on the server Runtime.&lt;/p&gt;

&lt;p&gt;This is a division of labor, not a capability cut. The server Runtime is aimed at "connect many systems and keep operating them as a team," which requires the full connector, audit, and operations surface. The on-device SDK is aimed at "close, light, embedded in your own product," which needs a controllable conversation loop and a minimal tool set. Keeping the two separate makes the on-device component small and controllable and leaves teams free to draw their own experience on top; when the full capability is needed, the app can always fall back to the server Runtime.&lt;/p&gt;

&lt;p&gt;The tool loop itself has a boundary: a single turn's tool loop is capped at a fixed number of rounds (currently 8). When it reaches the cap, it ends and returns, so the model cannot loop forever on the device. The session and keys live only in memory and are never persisted in the Go binding layer — they are cleared on reset or exit.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Keys go to the system safe, not the Go package
&lt;/h2&gt;

&lt;p&gt;There is one explicit rule in the on-device design: the Go binding layer only holds the key in memory and never writes it to disk. Anything that needs to persist is handed to the native safe provided by the app's platform.&lt;/p&gt;

&lt;p&gt;On Android, the sample uses &lt;code&gt;EncryptedSharedPreferences&lt;/code&gt;. The master key uses the AES256-GCM scheme, with keys encrypted with AES256-SIV and values with AES256-GCM. What gets stored includes the model endpoint, key, model name, and config endpoint, and there is a separate method to clear the key. On iOS, the equivalent is the Keychain, stored by service name and account name, and restricted to this device only and available after first unlock.&lt;/p&gt;

&lt;p&gt;The flow is: when the app starts (or needs the key), it reads the key from the system safe, then injects it into the on-device runtime's memory via &lt;code&gt;Configure&lt;/code&gt;; the Go binding layer never handles persistence. The reason is that a cross-platform Go binding cannot guarantee the security level of key storage, whereas &lt;code&gt;EncryptedSharedPreferences&lt;/code&gt; and the Keychain are the security boundaries the platform actually endorses — hardware-backed or managed by the system as a whole. The binding does not overstep into storage it cannot do well; it hands security to the platform, and the app fully owns the key lifecycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Foreground human approval: a JSON status protocol
&lt;/h2&gt;

&lt;p&gt;Human-in-the-loop on the device is expressed through a simple JSON status protocol. The return values of &lt;code&gt;Send&lt;/code&gt;, &lt;code&gt;Approve&lt;/code&gt;, and &lt;code&gt;Deny&lt;/code&gt; are all JSON strings that the app parses to decide what to render:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;done&lt;/code&gt;: the turn is finished; the UI shows the reply in the &lt;code&gt;text&lt;/code&gt; field.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;needs_approval&lt;/code&gt;: a tool is waiting for approval, carrying &lt;code&gt;call_id&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;args_json&lt;/code&gt;, and &lt;code&gt;summary&lt;/code&gt;. The app renders an approval card from these; approving calls &lt;code&gt;Approve&lt;/code&gt; with the &lt;code&gt;call_id&lt;/code&gt;, and rejecting calls &lt;code&gt;Deny&lt;/code&gt;. Until the user makes a choice, no new &lt;code&gt;Send&lt;/code&gt; should be issued.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;error&lt;/code&gt;: execution failed; the UI shows &lt;code&gt;message&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The approval summary is a single human-readable sentence. For &lt;code&gt;http_get&lt;/code&gt;, it produces the method ("GET") plus a truncated URL, so on a phone you can see at a glance what the app is about to call.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;http_get&lt;/code&gt; that requires approval is a read-only tool, but even though it is read-only, Baize still requires explicit approval and adds a set of guardrails when it runs: only &lt;code&gt;http&lt;/code&gt; and &lt;code&gt;https&lt;/code&gt; are allowed; requests have a timeout (currently 15 seconds); redirects are capped (currently 3); the response body has a size limit (currently 256 KB, and anything larger is truncated and marked); and the result carries the final address (&lt;code&gt;final_url&lt;/code&gt;) so you can verify where a redirect actually landed. Rejecting fires no network request at all and simply lets the conversation continue.&lt;/p&gt;

&lt;p&gt;Narrowing what an on-device model is allowed to do is the core idea: keep tools read-only, require explicit approval for actions, and keep the network boundary clear — then on-device autonomy is contained within a safe range.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Remote config, multiple sessions, and desktop debugging
&lt;/h2&gt;

&lt;p&gt;So the app does not have to hardcode the model endpoint and model name, the binding layer offers remote config: &lt;code&gt;FetchRemoteConfig&lt;/code&gt; pulls JSON from a config endpoint you own, and &lt;code&gt;ApplyRemoteConfigJSON&lt;/code&gt; merges that config into the in-memory session. The config can include the model endpoint and model name, and optionally the key. The fetch strategy is decided by the app; the SDK only applies it.&lt;/p&gt;

&lt;p&gt;For multi-session scenarios, use &lt;code&gt;NewAgent&lt;/code&gt; to obtain an independent instance rather than sharing the package-level default session. Each instance maintains its own history and pending-approval state.&lt;/p&gt;

&lt;p&gt;You do not have to tether a phone during development. Baize ships a desktop debug entry point that runs the binding layer directly on your computer, and it includes a fake model so you can get the UI and approval cards working before swapping in a real model. When an Android emulator needs to reach the host machine, use the emulator's standard host address (10.0.2.2).&lt;/p&gt;

&lt;h2&gt;
  
  
  9. How to choose
&lt;/h2&gt;

&lt;p&gt;The decision is straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;If you want the full settings pages, connectors, and operational approval flow, and the phone is only a portable entry point, choose Path A.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you want a branded native UI, the conversation running on the device, and less dependence on an always-on sidecar, choose Path B and draw your own UI using the empty shell in the repository as the calling sample.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The two can coexist: operators and internal teams use Path A, while the end-user-facing app uses Path B.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;The two mobile paths are, at heart, separating two different problems: "the operator's portable console" and "the embedded on-device Agent." The former pursues zero development and full parity with the desktop; the latter pursues a native experience, on-device control, and a clear key boundary. The key decisions in the on-device SDK — holding keys only in memory, requiring approval even for read-only tools, giving network actions explicit guardrails, and keeping the session and tool loop minimal — all revolve around one goal: letting the capability run safely, and personally, on the device.&lt;/p&gt;

&lt;p&gt;Baize is an open-source project under continuous development (written in Go, MIT licensed). The mobile Capacitor shell, the Android and iOS empty shells, the on-device bindings, and the corresponding build scripts are all in the repository.&lt;/p&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/rebornace/baize" rel="noopener noreferrer"&gt;https://github.com/rebornace/baize&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Docs: &lt;a href="https://rebornace.github.io/baize" rel="noopener noreferrer"&gt;https://rebornace.github.io/baize&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are also putting Agents into production — especially if you are thinking about how to safely bring capabilities onto a phone — I'd like to hear how you handle mobile and key management. The full mobile getting-started guide and build steps will be covered in a follow-up post.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>mobile</category>
      <category>go</category>
    </item>
    <item>
      <title>From a Single Agent Loop to an Auditable Runtime: Baize's Architecture</title>
      <dc:creator>rebornace</dc:creator>
      <pubDate>Fri, 23 Oct 2026 15:32:37 +0000</pubDate>
      <link>https://dev.to/rebornace/from-a-single-agent-loop-to-an-auditable-runtime-baizes-architecture-5b5e</link>
      <guid>https://dev.to/rebornace/from-a-single-agent-loop-to-an-auditable-runtime-baizes-architecture-5b5e</guid>
      <description>&lt;p&gt;Baize is a single-process Agent runtime written in Go that sits alongside your existing systems. Early on it was little more than a single "think, pick a tool, act" loop. As multi-client access, long-running tasks, connector groups, and other capabilities landed, the architecture converged onto five core abstractions: &lt;strong&gt;Runtime, Agent, Tool, Connector, and Run&lt;/strong&gt;. This article walks through the current structure with one layered diagram: how the different entry points connect, the three execution forms of the Run engine, the three connector types with bidirectional MCP, and the runtime surface that makes every Run auditable, observable, and recoverable.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. What it is: a standalone Agent runtime
&lt;/h2&gt;

&lt;p&gt;To understand Baize's architecture, the first thing to be clear about is that it is &lt;strong&gt;not a library embedded inside your business application&lt;/strong&gt;. It is a &lt;strong&gt;standalone Agent runtime&lt;/strong&gt; that runs as a sidecar or gateway next to the systems you already have.&lt;/p&gt;

&lt;p&gt;In one sentence: it consolidates the LLM, tools, legacy HTTP APIs, and human checkpoints into &lt;strong&gt;auditable Runs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Concretely, it reads API documentation and turns existing system endpoints into capabilities the Agent can call. For write operations such as creating an order or changing a status, it pauses at the web console and waits for human confirmation before executing. The process is visible in the conversation or messaging channel, and full credentials never appear in conversation replay. Usually, no business code needs to change.&lt;/p&gt;

&lt;p&gt;This shape gives it a few immediate properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Platforms and integrators connect over REST / HTTP, with no lock-in to any particular language (there is no official language SDK).&lt;/li&gt;
&lt;li&gt;It is a single process with externalized configuration; once stopped, it leaves almost nothing behind on the business side.&lt;/li&gt;
&lt;li&gt;The web console, mobile clients, messaging channels, and incoming alerts all share the same approval, memory, and outbound-reply surface.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once the product grows from a single conversation loop into multiple clients, systems, and tasks, a straight-line flow can no longer cover everything. The layered diagram below shows the current shape of Baize.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi7azmp1lzxxkebwhx3l1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi7azmp1lzxxkebwhx3l1.png" alt="baize architecture diagram" width="800" height="1117"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The five core abstractions: Runtime, Agent, Tool, Connector, Run
&lt;/h2&gt;

&lt;p&gt;The most fundamental change in the architecture is the convergence onto five abstractions: &lt;strong&gt;Runtime, Agent, Tool, Connector, and Run&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Runtime&lt;/strong&gt; is the single process itself, responsible for orchestration, scheduling, and lifecycle management.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent&lt;/strong&gt; is the carrier of conversation and policy; it understands intent, calls the LLM, and decides the next action.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool&lt;/strong&gt; is a callable capability. Reading an order, checking inventory, or raising an approval are all, from the engine's perspective, uniform tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connector&lt;/strong&gt; describes how an external system is connected, how credentials are carried, and where its capabilities come from.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run&lt;/strong&gt; is one complete execution, and the most important abstraction in the system. It records everything a conversation depends on from start to finish — the model, the tool contracts, connector revisions, and human checkpoints — so it can be audited, observed, and recovered.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is a deliberate choice here: Skills, Memory, Channels, and linear workflows are &lt;strong&gt;not promoted to a sixth core abstraction&lt;/strong&gt;. They live as configuration or plugin forms, layered on top of the five.&lt;/p&gt;

&lt;p&gt;The benefit is a stable core: adding a skill, integrating a channel, or defining a workflow does not require changing the Runtime kernel — it is all extension in the configuration and plugin layer. For a project that evolves over a long time, "a small, stable core with extension happening at the outer layer" is what allows it to keep adding capabilities without modules colliding.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The three execution forms of the Run engine
&lt;/h2&gt;

&lt;p&gt;Of the five abstractions, the Run engine is where the actual work happens. One engine carries three execution forms, each matching a different kind of task.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The first is the single-Agent ReAct loop, which is the default.&lt;/strong&gt; The engine advances as "choose a tool, execute, record the trajectory"; the model acts step by step as it reasons, deciding the next move from the previous result. It suits open-ended tasks where the goal is clear but the path must be judged on the fly — for example, "find out why this batch of orders is stuck, and fix the ones you can." When there are many systems, you can enable tool pre-filtering (the "smart acceleration" setting) so a shorter candidate list is handed to the main model; if that fails, it falls back to the full list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The second is a linear workflow inside a Skill.&lt;/strong&gt; When a process is a fixed sequence that can be designed up front, you write it into a Skill's &lt;code&gt;workflow.yaml&lt;/code&gt;, and the engine executes the steps in order — each step optionally carrying a human approval. These are linear sequences with no branching or loops. It suits predictable, consistency-focused standard processes, such as "verify qualifications, create the order, then notify the contact." Crystallizing a deterministic process into linear steps reduces uncertainty during execution and gives every step a clear checkpoint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The third is a connector-group sub-run (opt-in).&lt;/strong&gt; Connector groups themselves only affect the candidate set during tool narrowing; they do &lt;strong&gt;not&lt;/strong&gt; spawn sub-runs automatically. Once "allow sub-runs within a group" is enabled, the model can call &lt;code&gt;spawn_group_run&lt;/code&gt;: it derives a sub-run exactly one level deep for a given connector group. The sub-run's tool surface is limited to that group's connectors, and it has its own run binding and its own approvals; the parent enters &lt;code&gt;waiting_child&lt;/code&gt; and resumes when the sub-run reports back. Nested sub-runs are not allowed. This is for when business domains such as finance or transactions need their approvals not to collide — the emphasis is on isolation, with each domain's actions and approvals kept separate rather than two orchestrations running concurrently as parent and child. When the same turn only needs to fan out read-only investigations and aggregate results, that uses the engine's built-in parallel read-only calls instead, without consuming a sub-run.&lt;/p&gt;

&lt;p&gt;All three forms sit on the same Run engine, the same trajectory, and the same audit model. You do not maintain two frameworks for "open exploration" and "fixed process"; you pick the form that fits the task. The default ReAct loop and the optional linear workflow and group sub-runs can be combined as needed, with group sub-runs always gated by the switch and the one-level constraint.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Connector as a first-class citizen: three connector types and bidirectional MCP
&lt;/h2&gt;

&lt;p&gt;Baize is built for the reality of "many systems, a large share of them legacy," which is why connectors are first-class citizens. There are three standard ways to bring an external system in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenAPI wildcard.&lt;/strong&gt; Upload or point to an OpenAPI / Swagger document, and each interface operation becomes a tool. This is the most direct way to integrate legacy REST systems; names prefer the &lt;code&gt;operationId&lt;/code&gt;, and credentials, login, and change comparison are all handled uniformly at the connector layer. If an enterprise already has its own execution entry point and does not want to implement full plugin discovery, it can instead configure an &lt;strong&gt;execution callback&lt;/strong&gt; for the connector: the Runtime POSTs the tool name, arguments, and &lt;code&gt;run_id&lt;/code&gt; to that endpoint, which performs the work — no need to implement the &lt;code&gt;/v0/tools&lt;/code&gt; discovery endpoint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTTP plugin protocol v0.&lt;/strong&gt; When the API document cannot cover some legacy logic, or a capability needs to be implemented on the business side, you write a small service (a sidecar Connector) that exposes three endpoints per protocol v0: a health check, a tool list, and a tool invocation. The Runtime calls it over the same network, carrying the run identifier, tenant identifier, and protocol version in the headers; how the capability is implemented is entirely up to the business side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP bridge.&lt;/strong&gt; Baize acts as an MCP client, connecting to an existing MCP Server and pulling its tools into its unified catalog. This lets MCP tools already in the ecosystem be used by Baize directly.&lt;/p&gt;

&lt;p&gt;These three sources, together with manually maintained extra tools, are unified into the tool catalog (sources tagged &lt;code&gt;spec&lt;/code&gt;, &lt;code&gt;plugin&lt;/code&gt;, &lt;code&gt;mcp&lt;/code&gt;, and &lt;code&gt;extra&lt;/code&gt;). The engine and human checkpoints only read the &lt;strong&gt;enabled&lt;/strong&gt; entries in the Registry; tool names across connectors are required to be globally unique, and the catalog API can still show disabled rows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worth calling out on its own: MCP in Baize is bidirectional.&lt;/strong&gt; The MCP bridge above is Baize acting as a client to consume other services' capabilities. In the reverse direction, Baize can also act as an MCP server, exposing a read-only subset of its tool catalog through &lt;code&gt;/v0/mcp/export&lt;/code&gt; to MCP-capable Agent clients such as Cursor and Claude Desktop. This path does not go through Baize's own LLM; it safely shares already-integrated business capabilities with external Agents. An export Key can bind identity slots by connector and role, with the permission boundary corresponding to the downstream business account. When downstream authentication fails, it first guides the user through hosted login rather than pasting a password into the conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. How a single Run is auditable, observable, and recoverable
&lt;/h2&gt;

&lt;p&gt;Moving an Agent from a demo to long-term operation is not primarily about single-turn capability — it is about every Run leaving a trace, being observable, and being resumable. Baize provides a set of capabilities that work together at this layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run binding (&lt;code&gt;run.binding&lt;/code&gt;).&lt;/strong&gt; Before the first main-model call, every Run records the model endpoint and profile in effect, the policy parameters, memory injection, the narrowed tool contract fingerprint, and the connector catalog revision. When you later trace the basis of a decision in that run, you get the real snapshot from that moment rather than the current configuration, which has since changed. Tool-call events carry the contract fingerprint and catalog revision, so you can compare directly before and after an interface fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Human-in-the-loop (HITL).&lt;/strong&gt; Write operations can be marked as requiring approval; the Run enters a waiting state and resumes after an operator approves or rejects on a card. When a downstream system needs login, the conversation can pause and guide the user to log in via the browser, skip that system for the turn, or end the turn. Long tasks also release the slot while a gate is pending, so a single waiting approval does not tie up a worker thread for the whole Run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Long-task resilience.&lt;/strong&gt; For long-running task stability, the engine combines step-budget wind-down, retries and fallback after LLM call failures, timeout splitting, and parallel read-only tool calls. Within a turn it also performs context compression, conversation retrieval, and skill archiving, so a task can keep its goal and continue after many rounds. After user confirmation, one-time, daily, or weekly scheduled instructions can be saved; when the time comes, a new round opens in the original conversation, and write operations still go through approval.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context management.&lt;/strong&gt; Long conversations get a rolling summary, and an optional "keep the key points of long conversations" projection changes only what the model sees without rewriting the original messages. Exceptionally large tool results or pasted content are offloaded to the session workspace's &lt;code&gt;offload/&lt;/code&gt; directory; the model only receives a preview and a path, and can read it back in sections with &lt;code&gt;read_file&lt;/code&gt; when needed, instead of blowing up the context in one go.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Observability.&lt;/strong&gt; Structured &lt;code&gt;slog&lt;/code&gt; logging, Prometheus metrics, and optional OpenTelemetry tracing cover the Run and tool calls. After a connector is re-imported, the interface change comparison is returned and can be pushed asynchronously via Webhook. Most of these observability options can be viewed and hot-reloaded from the settings page, without editing the config file and restarting each time.&lt;/p&gt;

&lt;p&gt;Each piece of this layer deserves its own article; here it is only positioned within the overall architecture, and later posts will expand on them one by one.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. The second client beyond the server: two mobile paths
&lt;/h2&gt;

&lt;p&gt;Once the Runtime was reliably carrying the server side, mobile was not implemented as a simple mobile web page. Instead, there are two paths with different positioning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Path one: remote-control console.&lt;/strong&gt; Open the reachable &lt;code&gt;/ui&lt;/code&gt; directly in a mobile browser, or use the Capacitor shell in the repository to wrap a WebView that loads the remote console. Conversation, settings, approvals, and browser login are identical to the desktop. The shell does not run a conversation engine; it effectively turns the phone into a remote control, which suits internal teams integrating with zero development.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Path two: on-device SDK.&lt;/strong&gt; The Go orchestration core is compiled via gomobile into an Android AAR and an iOS XCFramework (the public binding package is &lt;code&gt;mobileagent&lt;/code&gt;) and linked into your own app. On the device it runs a &lt;strong&gt;slimmed-down&lt;/strong&gt; conversation and tool loop with foreground human approval. The app holds the keys and places them in OS-backed secure storage (such as the iOS Keychain or Android encrypted storage), then injects them into the on-device runtime's memory; configuration can be synced from a remote endpoint, and the UI is drawn entirely by the integrator. This path deliberately does &lt;strong&gt;not&lt;/strong&gt; ship the full &lt;code&gt;baize serve&lt;/code&gt; stack into the phone — there is no connector farm, MCP, channels, or full HITL product surface; it focuses on in-memory conversations and a small tool set. It suits teams that want to embed Agent capabilities into their own product and have their own requirements for UI and key management.&lt;/p&gt;

&lt;p&gt;The two paths do not replace each other: one is a portable entry point to the full console, the other is on-device capability embedded in a self-built app. The full mobile getting-started guide and build steps will be covered in a later post.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Storage and deployment
&lt;/h2&gt;

&lt;p&gt;Baize uses a single process with externalized configuration. The main database, middleware (task queue / event bus / rate limiter), and Blob storage all use &lt;strong&gt;pluggable drivers&lt;/strong&gt;: implement the unified interface, register it with &lt;code&gt;RegisterDriver&lt;/code&gt;, and select it by name with &lt;code&gt;driver&lt;/code&gt; in the configuration, so the Runtime is not locked to any particular database or message middleware. The implementations built into the repository today are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Main database: SQLite by default, with Postgres and in-memory implementations.&lt;/li&gt;
&lt;li&gt;Task queue, event bus, and rate limiter: in-memory by default, switchable to Redis when scaling horizontally (multiple replicas).&lt;/li&gt;
&lt;li&gt;Files and attachments (Blob): local files, S3, or in-memory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other databases or message queues (for example MySQL or Kafka) can be added under the same driver contract without changing the Runtime kernel — the extension surface is the driver layer, not the orchestration layer. On the model side, any OpenAI-compatible API is supported, and the decision model can run on local Ollama, which further lowers the deployment bar.&lt;/p&gt;

&lt;p&gt;This combination lets the project evolve smoothly from "run it on one machine" to "plug into the team's infrastructure": it works with zero dependencies by default, and when you connect to existing team middleware you simply add or switch a driver. For messaging, IM channels are integrated through external adapters using declarative configuration; the repository ships reference adapter examples in the examples directory for you to deploy as needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;The thread through this round of architecture evolution is clear: &lt;strong&gt;consolidate the LLM, tools, legacy interfaces, and human checkpoints into auditable Runs; keep the core abstractions few and stable; and let the frequently changing parts — skills, memory, channels, and workflows — extend at the configuration and plugin layer.&lt;/strong&gt; The five abstractions, three execution forms, three connector types, and bidirectional MCP, along with the server Runtime and the mobile client, are all built around that thread.&lt;/p&gt;

&lt;p&gt;Next, I'll write separate posts on the two mobile paths, long-task resilience, and the evaluation corpus, explaining the design trade-offs and implementation based on the actual code.&lt;/p&gt;

&lt;p&gt;Baize is an open-source project under continuous development (written in Go, MIT licensed). Repository: &lt;a href="https://github.com/rebornace/baize" rel="noopener noreferrer"&gt;github.com/rebornace/baize&lt;/a&gt;, site: &lt;a href="https://rebornace.github.io/baize/" rel="noopener noreferrer"&gt;rebornace.github.io/baize&lt;/a&gt;. If you are also deploying Agents and working on integrating legacy systems and keeping them running reliably over the long term, you're welcome to open an issue or share your own Agent runtime practices in the comments. I'll keep following up and documenting the architecture as it evolves.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>mcp</category>
      <category>go</category>
    </item>
    <item>
      <title>TraceScope: Impact Analysis, Design Diff, and Crash Triage for DeepSeek Harness</title>
      <dc:creator>rebornace</dc:creator>
      <pubDate>Sun, 11 Oct 2026 11:09:27 +0000</pubDate>
      <link>https://dev.to/rebornace/tracescope-impact-analysis-design-diff-and-crash-triage-for-deepseek-harness-3nck</link>
      <guid>https://dev.to/rebornace/tracescope-impact-analysis-design-diff-and-crash-triage-for-deepseek-harness-3nck</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Open source · MIT&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;· GitHub: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/rebornace/dsh-tracescope" rel="noopener noreferrer"&gt;rebornace/dsh-tracescope&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;· npm: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;@rebornace/dsh-tracescope&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;(v0.3.2)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;AI models are great at reasoning about code — but only when they're handed grounded inputs. In practice, most app-quality work never reaches a model: after a PR merges, the diff sits in Git, the design spec sits in Figma, the crash stack sits in a monitoring console, and someone has to gather and structure all of it by hand before an AI can say anything useful.&lt;/p&gt;

&lt;p&gt;I built &lt;strong&gt;TraceScope&lt;/strong&gt; to lower that barrier: a plugin for &lt;strong&gt;DeepSeek Harness (DSH)&lt;/strong&gt; — DeepSeek's desktop agent environment with a sidebar-plugin system — that turns your Git diff, your design specs, and your crash feeds into structured, deterministic inputs, so the AI analysis you get back is grounded in your actual codebase. It runs in the DSH sidebar, and the same capabilities are exposed over &lt;strong&gt;MCP&lt;/strong&gt;, so you can drive the whole workflow from Cursor, Claude Code, Codex — or any other MCP-capable agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this fits in your stack
&lt;/h2&gt;

&lt;p&gt;Before the details, the honest positioning — TraceScope is not trying to replace the tools you already use; it sits next to them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Visual regression&lt;/strong&gt; (Percy, Chromatic) compares &lt;em&gt;screenshots&lt;/em&gt; of your running app. TraceScope's design diff compares the &lt;strong&gt;design file itself&lt;/strong&gt; against your code, statically — no build, no emulator, no browser session. It answers "does the implementation match the spec?" earlier than screenshot tools can.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Crash monitoring&lt;/strong&gt; (Sentry, Crashlytics, Bugsnag) tells you &lt;em&gt;that&lt;/em&gt; something crashed. TraceScope's crash analysis helps with the next step: matching the stack to your repo and drafting the fix direction. Today it wires into &lt;strong&gt;Firebase Crashlytics&lt;/strong&gt; and &lt;strong&gt;Umeng U-APM&lt;/strong&gt;; Sentry and others are on the roadmap (more below).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Impact analysis&lt;/strong&gt; is the piece most teams still do by gut feel: "what does this change touch?" It computes the answer from the diff and the dependency graph instead.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The consistent design bet: &lt;strong&gt;everything that can be parsed is parsed deterministically, so the AI analysis you get is grounded in facts, not guesses.&lt;/strong&gt; No model is needed to diff a design tree or resolve a stack frame; the model does what it's actually good at — reading your source and reasoning about what the change, the drift, or the crash means for your app.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three pillars
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Impact analysis&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Stable ↔ candidate commits (local Git, or a hosted code-platform API)&lt;/td&gt;
&lt;td&gt;Direct changes + static ripple, as a reviewable regression checklist&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Design diff&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Figma / Lanhu design links + code repository&lt;/td&gt;
&lt;td&gt;Static UI-difference list + side-by-side highlight, AI-assisted write-back, issue submission&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Crash analysis&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Firebase Crashlytics / Umeng U-APM, or a pasted stack trace + code directory&lt;/td&gt;
&lt;td&gt;Stack parsing, repo file hits, report, AI conclusion written back to the session&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All three share one sidebar, one tracker configuration, and one MCP tool surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Impact analysis: "what's the blast radius of this change?"
&lt;/h2&gt;

&lt;p&gt;Give it two commits — the stable baseline and the candidate you're about to release — and it computes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Direct changes&lt;/strong&gt;: the files that actually changed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Static reverse-dependency ripple&lt;/strong&gt;: who imports or references them, propagated to a configurable depth (default 2). This is a real impact-analysis pass over your dependency graph, not a file list with guesswork.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The ripple pass understands actual source languages: Kotlin/Java, Swift/Objective-C, Dart/Flutter, TypeScript/JavaScript, Vue, the CSS family, and HTML. Other languages still get a direct-changes list — just no ripple. The local-Git mode builds its index from &lt;strong&gt;commit objects&lt;/strong&gt;, so it doesn't even need a readable working tree; there's a hosted-code-platform API fallback for setups where local Git isn't an option (that mode skips static ripple).&lt;/p&gt;

&lt;p&gt;The output is a checkbox regression checklist with human-readable feature names (from a &lt;code&gt;tracescope.modules.yml&lt;/code&gt; mapping, falling back to static titles, then heuristics), per-item notes, screenshots (up to 3 per item), Markdown/CSV export, and one-click issue submission to your tracker. It's the "what to re-verify before release" list, generated instead of remembered.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Design diff: "does the implementation match the spec?"
&lt;/h2&gt;

&lt;p&gt;Design fidelity work today is screenshot-based: you run the app, capture it, and diff images. TraceScope takes a different route — it treats the &lt;strong&gt;design file as the source of truth&lt;/strong&gt; and compares it to the code &lt;strong&gt;statically&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Page discovery&lt;/strong&gt; covers Android (XML / Compose / View), iOS (Xib / SwiftUI / UIKit), Flutter, React Native, Harmony ArkUI, uni-app, Taro, Web (HTML / React / Vue / Svelte / Angular), mini-programs (WXML / AXML / TTML / Swan), and .NET MAUI XAML.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Layered comparison&lt;/strong&gt;: L0 fingerprints the design page against code pages (or you pin the file manually / let AI re-match); L1 does attribute-level static tree comparison (&lt;code&gt;DesignDoc&lt;/code&gt; ↔ &lt;code&gt;DesignDoc&lt;/code&gt;) — geometry, colors and tokens, typography (with unit conversion across dp / sp / rpx / &lt;code&gt;cqw&lt;/code&gt;), multi-layer shadows and blur, transforms, visibility, resources; L2 falls back to heuristics (copy, control counts) when there's no precise geometry tree; L3 hands the source-reading list and prompts to the AI and writes its conclusions back to the sidebar.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Depth is pluggable, not forked&lt;/strong&gt;: a design-enrichment registry currently ships a full static layout engine for Android XML (viewport measurement, dependency-closure fingerprints, Adapter-bound list-item restoration, dynamic text projection). Other stacks get uniform attribute-level comparison; a sidebar toggle forces uniform granularity when you want speed over depth.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Design sources&lt;/strong&gt;: &lt;strong&gt;Figma&lt;/strong&gt; and &lt;strong&gt;Lanhu&lt;/strong&gt;, both normalized into a single &lt;code&gt;DesignDoc&lt;/code&gt; — the comparison pipeline doesn't care which tool produced the file.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because it never needs a running app, the check is cheap enough to run on &lt;em&gt;every&lt;/em&gt; screen you touch — not just the ones someone remembered to eyeball.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Crash analysis: "what is this crash, and where do I start?" &lt;em&gt;(new in 0.3.x)&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;The newest pillar picks up where crash monitoring leaves off. It pulls crash / ANR lists from a provider — or accepts a &lt;strong&gt;pasted stack trace&lt;/strong&gt; when no credentials are set — and analyzes them against your code directory:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sources&lt;/strong&gt;: &lt;strong&gt;Firebase Crashlytics&lt;/strong&gt; and &lt;strong&gt;Umeng U-APM&lt;/strong&gt;. No credentials set? Paste a stack trace and the whole analysis flow still works.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Filtering stays aligned with the console&lt;/strong&gt;: the filter area is collapsed by default, and version / OS / device options are sourced from the same metadata the console uses — so you can only select what the console would actually return.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Local analysis&lt;/strong&gt;: &lt;code&gt;analyze&lt;/code&gt; parses the stack, matches frames to files in the code directory by name, and produces a summary with hit files and fix hints — &lt;strong&gt;no model call&lt;/strong&gt;, fully deterministic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AI write-back&lt;/strong&gt;: "Analyze &amp;amp; write to session" creates an AI job and drops a prompt with a job ID into the current session; when the agent calls &lt;code&gt;tracescope_publish_crash_analysis&lt;/code&gt;, the sidebar polls and auto-displays the conclusion. Manual write-back and paste fallbacks exist.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Issue submission&lt;/strong&gt;: a dedicated crash body (optionally including the AI conclusion) goes to your tracker — GitHub Issues, GitLab Issues, a generic webhook, or Yunxiao (Alibaba Cloud DevOps).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Crash providers are registered through the same &lt;code&gt;CrashProvider&lt;/code&gt; adapter contract as everything else — adding Sentry or another APM is a new adapter, not a fork of the main path (see the roadmap below).&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP: the same engine, any agent
&lt;/h2&gt;

&lt;p&gt;Everything above is exposed through &lt;code&gt;@rebornace/tracescope-mcp&lt;/code&gt;. The execution layer is shared with the DSH host via &lt;code&gt;@rebornace/dsh-tracescope/agent-api&lt;/code&gt; — host routes are thin wrappers, so behavior is identical whether you drive it from the sidebar or from a tool call.&lt;/p&gt;

&lt;p&gt;There are 19 tools across the three pillars, grouped by workflow: impact (diff, impact analysis, manual-verification job creation and write-back), design review (snapshot, findings, page re-matching), and crash (probe, filter options, list, detail, local analyze, AI job creation, publish, job polling). Credentials can be passed as tool parameters or read from local config (&lt;code&gt;~/.tracescope&lt;/code&gt;). The MCP surface is client-agnostic: the full list → analyze → write-back loop runs in Cursor, Claude Code, Codex — or any other MCP-capable agent — with or without the sidebar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@rebornace/tracescope-core        ← deterministic analysis engine (impact + design + crash parse)
@rebornace/dsh-tracescope         ← DSH plugin: Host API + sidebar UI + agent-api
@rebornace/tracescope-mcp         ← MCP server (same tool surface as the Host)
packages/.../design-enrichment/   ← per-stack deep-enhancement plugins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two design principles worth calling out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deterministic floor, AI ceiling.&lt;/strong&gt; Everything that &lt;em&gt;can&lt;/em&gt; be computed is computed — the AI never hallucinates a diff or a stack match, because it never needs to.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No vendor lock-in.&lt;/strong&gt; Crash providers, trackers, and design sources are all registry-based adapters (&lt;code&gt;CrashProvider&lt;/code&gt;, &lt;code&gt;TrackerAdapter&lt;/code&gt;, design-source normalization). Adding a new one doesn't fork the main path.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The plugin also self-updates without depending on a plugin marketplace: it checks npm &lt;code&gt;latest&lt;/code&gt; through the Host and installs via the official plugin manager, with a visible update button and status card in the desktop UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install &amp;amp; use
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Node.js &lt;code&gt;&amp;gt;= 20&lt;/code&gt;; the official DeepSeek Harness desktop app is recommended.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Install and open the &lt;strong&gt;TraceScope&lt;/strong&gt; sidebar:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dsh plugin &lt;span class="nt"&gt;--profile&lt;/span&gt; desktop add @rebornace/dsh-tracescope
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;UI language (English / Chinese) follows the desktop app's setting.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Install from npm &lt;code&gt;latest&lt;/code&gt;; community marketplace cards may lag behind.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A typical crash workflow: pick the source → save credentials → fetch the list → open a detail → &lt;em&gt;analyze&lt;/em&gt; (local, deterministic) → &lt;em&gt;analyze &amp;amp; write to session&lt;/em&gt; → the agent writes back the conclusion → submit the issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Platform coverage today — and where it's going
&lt;/h2&gt;

&lt;p&gt;Honest scope: &lt;strong&gt;the adapter coverage is still limited&lt;/strong&gt;, and it will grow toward the platforms teams actually use. The current wiring, and what's coming:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Extension surface&lt;/th&gt;
&lt;th&gt;Currently wired&lt;/th&gt;
&lt;th&gt;On the roadmap&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Crash providers&lt;/td&gt;
&lt;td&gt;Firebase Crashlytics, Umeng U-APM, pasted stacks&lt;/td&gt;
&lt;td&gt;Sentry, Bugly, other APMs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trackers&lt;/td&gt;
&lt;td&gt;GitHub Issues, GitLab Issues, generic webhook, Yunxiao&lt;/td&gt;
&lt;td&gt;Jira, Feishu / Lark, other ticketing systems&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Design sources&lt;/td&gt;
&lt;td&gt;Figma, Lanhu&lt;/td&gt;
&lt;td&gt;MasterGo, other design collaboration tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code-stack adapters&lt;/td&gt;
&lt;td&gt;Android XML (deep enhancement), iOS, Flutter, RN, Web, mini-programs, and more&lt;/td&gt;
&lt;td&gt;more per-stack layout engines and list restoration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;If you need a specific platform — your APM, your ticketing system, your design tool — open a &lt;/strong&gt;&lt;a href="https://github.com/rebornace/dsh-tracescope/issues" rel="noopener noreferrer"&gt;GitHub Issue&lt;/a&gt;&lt;strong&gt; with the platform name, the auth method, whether a public API exists, and your typical use case. Clearly-scoped requests get prioritized.&lt;/strong&gt; That feedback drives the roadmap order. The full roadmap lives in the repo docs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;TraceScope is open source under MIT, built to lower the barrier to applying AI analysis to app quality: &lt;strong&gt;impact analysis answers "what changed and what to re-check", design diff answers "where does the implementation drift from the spec", and crash analysis answers "what is this crash and where do I start fixing it"&lt;/strong&gt; — from one sidebar, one tracker config, one MCP surface.&lt;/p&gt;

&lt;p&gt;Try it, open an Issue for the platform you need, or send a PR.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Links&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;GitHub: &lt;a href="https://github.com/rebornace/dsh-tracescope" rel="noopener noreferrer"&gt;rebornace/dsh-tracescope&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;npm: &lt;a href="https://www.npmjs.com/package/@rebornace/dsh-tracescope" rel="noopener noreferrer"&gt;@rebornace/dsh-tracescope&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Full guide: &lt;a href="https://github.com/rebornace/dsh-tracescope/blob/main/docs/GUIDE.md" rel="noopener noreferrer"&gt;docs/GUIDE.md&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Changelog: &lt;a href="https://github.com/rebornace/dsh-tracescope/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;CHANGELOG.md&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Feedback: &lt;a href="https://github.com/rebornace/dsh-tracescope/issues" rel="noopener noreferrer"&gt;Issues&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>deepseek</category>
      <category>testing</category>
      <category>ai</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Connect Your Business APIs Once, Share Them with Every Agent Client via MCP</title>
      <dc:creator>rebornace</dc:creator>
      <pubDate>Sat, 10 Oct 2026 04:31:43 +0000</pubDate>
      <link>https://dev.to/rebornace/connect-your-business-apis-once-share-them-with-every-agent-client-via-mcp-n3h</link>
      <guid>https://dev.to/rebornace/connect-your-business-apis-once-share-them-with-every-agent-client-via-mcp-n3h</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Project: &lt;strong&gt;Baize&lt;/strong&gt; — a team-facing AI assistant runtime (Go 1.25+, MIT)&lt;br&gt;
Repo: &lt;a href="https://github.com/rebornace/baize" rel="noopener noreferrer"&gt;https://github.com/rebornace/baize&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. A very real pain point
&lt;/h2&gt;

&lt;p&gt;When a team rolls out AI, one thing you can't avoid: everyone uses a different Agent client.&lt;/p&gt;

&lt;p&gt;One person codes in Cursor, another uses Claude Desktop, another uses something else. Business API docs, login credentials, callback URLs — if you reconfigure them in every client, you end up wiring the same legacy system once in Cursor, then again in Claude Desktop, with credentials scattered everywhere. The day that legacy system changes its auth, you have to go fix each one.&lt;/p&gt;

&lt;p&gt;Baize sits on the "wired up the business system" side of this problem. The OpenAPI doc is imported into Baize once, login is completed in Baize's console once, and write-approval is audited in Baize once. The remaining question is: &lt;strong&gt;how do you let the Agent clients your team already uses reuse this wired-up capability, instead of reconfiguring it all over again?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's what MCP server export is for.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Where the decision happens, and where it doesn't
&lt;/h2&gt;

&lt;p&gt;One thing to get out of the way: once Baize exports the tools, &lt;strong&gt;the decision is not on Baize's side.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Cursor model in Cursor, Claude in Claude Desktop — they still decide for themselves "which tool to call this turn, with what arguments." What Baize exports is only two things: a tool catalog (name, description, parameter schema), and an execution channel for when those tools are called. Baize's own main model, ReAct loop, decision layer, memory, and context compression are all not involved in this export path.&lt;/p&gt;

&lt;p&gt;The payoff is direct: the decision layer stays free to pick on the client side — whichever model you use in Cursor is none of Baize's business — while the execution layer stays centralized in Baize. Business API integration, auth, and approval are maintained in exactly one place.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Transport and auth
&lt;/h2&gt;

&lt;p&gt;The export channel runs over the standard MCP &lt;strong&gt;Streamable HTTP&lt;/strong&gt;, stateless. When a client connects, it sends a Bearer key in the request header.&lt;/p&gt;

&lt;p&gt;On the server side:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wrong key, or missing key → 401.&lt;/li&gt;
&lt;li&gt;Keys are stored as &lt;strong&gt;hashes only&lt;/strong&gt;, never in plaintext.&lt;/li&gt;
&lt;li&gt;Each key maps to a separate export identity (e.g. "the one for Cursor" and "the one for Claude Desktop" are two different keys).&lt;/li&gt;
&lt;li&gt;Keys can be revoked at any time; revoking one immediately disconnects that client.&lt;/li&gt;
&lt;li&gt;The whole export feature has a master toggle; when off, all requests return 503.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;"One key per identity" isn't ceremony — it's what makes the later login-state and isolation behavior possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. "Read-only" isn't a slogan — it's four layers of hard constraints
&lt;/h2&gt;

&lt;p&gt;The most common objection to exposing business tools to external clients is: what about writes? Baize doesn't just say "read-only" in marketing copy. It stacks four layers in the export policy:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 1: HTTP tools only expose GET/HEAD by default.&lt;/strong&gt; Before a tool is exported, Baize looks at its HTTP method. Anything that isn't GET or HEAD isn't exported, unless the tool is explicitly marked &lt;code&gt;force_allow&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 2: MCP-sourced write tools are hard-rejected.&lt;/strong&gt; Baize itself also connects to external MCP servers as a client. Tools coming out of those servers — if their name or description contains &lt;code&gt;insert&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt;, &lt;code&gt;delete&lt;/code&gt;, &lt;code&gt;drop&lt;/code&gt;, &lt;code&gt;truncate&lt;/code&gt;, &lt;code&gt;execute_write&lt;/code&gt;, and similar — are never exported, even if someone tries to &lt;code&gt;force_allow&lt;/code&gt; them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 3: Tools that require approval are not exported.&lt;/strong&gt; Write operations flagged in Baize as "need human confirmation before calling" (e.g. creating a record) simply don't appear on the export surface — because the export channel has no approval card, and exposing one would mean bypassing human review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 4: Policy is re-checked at call time.&lt;/strong&gt; Tools pass policy once when they're registered into the MCP catalog, and again when they're actually invoked. That means if you disable or un-export a tool in Baize's backend, the next external client call fails immediately — no "catalog still lists it but it's already off" gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. How login state is handled
&lt;/h2&gt;

&lt;p&gt;This is the subtlest part of the export path. Legacy APIs require login. Credentials are configured in Baize's console, but when an external client (say, Cursor) calls a tool, whose login state does the call go out with?&lt;/p&gt;

&lt;p&gt;Baize's approach: &lt;strong&gt;each export identity is bridged to a separate internal identity.&lt;/strong&gt; It has its own session namespace (prefixed with &lt;code&gt;mcp-export-id:&lt;/code&gt; plus the export identity ID), and every call is forced through that identity. Concretely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You log in once inside Baize for the "Cursor identity" and configure its credentials.&lt;/li&gt;
&lt;li&gt;When Cursor calls any tool with that key, Baize uses that identity to reach the legacy system.&lt;/li&gt;
&lt;li&gt;The "Claude Desktop key" goes through a different identity, a different login state — the two never cross.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The user inside Cursor never sees the legacy system's login page; credentials are already maintained on Baize's side.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. The actual usage path
&lt;/h2&gt;

&lt;p&gt;Three steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Wire up the business system in Baize's console (OpenAPI doc import, login done, approval rules configured).&lt;/li&gt;
&lt;li&gt;Create an export identity in the export settings and generate a key.&lt;/li&gt;
&lt;li&gt;Plug Baize's MCP Streamable HTTP endpoint and the Bearer key into your Agent client (Cursor, Claude Desktop — any MCP-supporting client works the same way).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After that, say something in the client; the client model picks which already-wired Baize tool to call, and Baize handles execution and returns the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. How this relates to the previous article
&lt;/h2&gt;

&lt;p&gt;In the earlier post ("How I wired a ten-year-old Java legacy system into AI with zero changes"), MCP only got a short paragraph — the focus was how to bring a legacy system in without touching it. This article zooms in on the MCP server side: Baize doesn't just turn legacy systems into its own tools, it can also share those tools with whatever Agent clients the team already uses.&lt;/p&gt;

&lt;p&gt;Putting bidirectional MCP together, Baize's position in the ecosystem is clear: on the way in, it can connect to external MCP tool servers and pull others' tools in; on the way out, it can expose its own wired catalog over MCP so the team doesn't reconfigure anything twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Closing
&lt;/h2&gt;

&lt;p&gt;"Wire the business API once, let every Agent client on the team use it" sounds like just "sharing a tool catalog." Doing it for real means a pile of concrete questions: how to block writes, how to manage keys, how to isolate login state, how to make config changes take effect immediately. Baize converges those into three areas: export policy, key management, and identity bridging. The default is conservative (read-only, approval-gated things stay hidden, revocable); opening anything up takes an explicit action at each step.&lt;/p&gt;

&lt;p&gt;This export capability is still evolving, and different clients support MCP at different depths. Try it out and open an issue — or tell me in the comments how your team connects business tools to your own clients. I'll keep iterating.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/rebornace/baize" rel="noopener noreferrer"&gt;https://github.com/rebornace/baize&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>agents</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Baize's Decision Layer: Pulling High-Frequency Judgments Out of the Main Model</title>
      <dc:creator>rebornace</dc:creator>
      <pubDate>Fri, 09 Oct 2026 07:41:22 +0000</pubDate>
      <link>https://dev.to/rebornace/baizes-decision-layer-pulling-high-frequency-judgments-out-of-the-main-model-ag9</link>
      <guid>https://dev.to/rebornace/baizes-decision-layer-pulling-high-frequency-judgments-out-of-the-main-model-ag9</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Project: &lt;strong&gt;Baize&lt;/strong&gt; — a team-facing AI assistant runtime (Go 1.25+, MIT)&lt;br&gt;
Repo: &lt;a href="https://github.com/rebornace/baize" rel="noopener noreferrer"&gt;https://github.com/rebornace/baize&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Before we start, four names in this article map directly to the product UI, so let's align them:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Main model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Writes replies, calls tools. This is where most of your token spend lives.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Decision model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;System One (default &lt;code&gt;tev1&lt;/code&gt;). Only answers short yes/no or pick-which questions: is memory extraction worth it this turn, which backends does this turn need, should a huge tool result be kept, should routing be light or heavy.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fallback model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Configured via &lt;code&gt;decide_profile_id&lt;/code&gt;. Only kicks in when the decision model is unavailable. &lt;strong&gt;If you've configured a decision model, leave this empty.&lt;/strong&gt; Leaving it empty does &lt;em&gt;not&lt;/em&gt; fall through to the main model.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Retrieval model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;e.g. &lt;code&gt;bge-m3&lt;/code&gt;. Only handles tool retrieval — it does not make any of the judgments above.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  1. Why "judgment" deserves its own layer
&lt;/h2&gt;

&lt;p&gt;Once you hang Baize next to a business system and point it at an API doc, hundreds of endpoints become hundreds of callable tools. One real backend easily brings in 300+ tools.&lt;/p&gt;

&lt;p&gt;With that many tools, every turn carries a pile of high-frequency judgments before the main model ever writes a word: which systems should the main model see, which tools make the candidate list, is memory extraction worth running at all, which model tier to pick.&lt;/p&gt;

&lt;p&gt;These judgments share three traits: &lt;strong&gt;high frequency&lt;/strong&gt; (almost every turn), &lt;strong&gt;short answers&lt;/strong&gt; (usually "pick a few" or "yes/no"), and &lt;strong&gt;not expensive&lt;/strong&gt; — if every judgment goes through the main model's generative call, the tokens saved cost more than the judgment itself.&lt;/p&gt;

&lt;p&gt;The standard move is to pull high-frequency judgments out of the generative loop and run them on a cheaper capability — the same shape Jev popularized: short answers, enum output, no long reasoning essays. Here's how Baize layers prefiltering, retrieval enhancement, and the decision chain.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Stage 1: deterministic prefiltering to push down noise
&lt;/h2&gt;

&lt;p&gt;This tier needs no decision model. Baize starts with a &lt;strong&gt;zero-cost, degradable&lt;/strong&gt; deterministic path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;System routing&lt;/strong&gt;: discriminative terms in the query force-hit the matching connector; the main model's pick can only add to the forced set, never remove it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool prefiltering&lt;/strong&gt;: BM25 over Latin terms and Chinese bigrams (standard matching) ranks relevant tools into a short candidate list; the main model only sees that list.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's deterministic, explainable, and works offline. It only pushes down noise though — when the user phrases something vaguely, irrelevant entries can still slip through.&lt;/p&gt;

&lt;p&gt;Note: this "narrowing" is a runtime prefilter strategy. It is &lt;em&gt;not&lt;/em&gt; the same code as the &lt;strong&gt;Rules&lt;/strong&gt; tier in the decision chain below — Rules today mainly covers "is memory extraction worth running this turn."&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Stage 2: retrieval enhancement (vectors), with fallback to lexical
&lt;/h2&gt;

&lt;p&gt;v0.4 added the "can you find the right tool" dimension. The prefilter was BM25; dense vectors are now fused via RRF. You can optionally plug in an embedding model (&lt;code&gt;bge-m3&lt;/code&gt; on local Ollama, or any OpenAI-compatible embedding API) to vectorize every tool doc and rank by similarity at query time.&lt;/p&gt;

&lt;p&gt;In the fusion, &lt;strong&gt;vectors outrank BM25&lt;/strong&gt; (in the code, dense weight is roughly 2× BM25). It's steadier for mixed Chinese/English, business jargon, and non-standard phrasing. If enhancement fails, it automatically falls back to standard matching — your conversation is unaffected.&lt;/p&gt;

&lt;p&gt;This tier answers "can you find the right tools." It does &lt;em&gt;not&lt;/em&gt; answer "should we extract memory, should a tool result be kept verbatim, should routing be light or heavy" — that's the decision layer's other track.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Stage 3: wiring in the decision model
&lt;/h2&gt;

&lt;p&gt;v0.5.0 routes "needs judgment" into the decision model, forming today's chain:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decision model (System One) → Fallback model (optional) → Rules&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 1: Decision model (System One).&lt;/strong&gt; Local Ollama (≥0.35) auto-pulls &lt;code&gt;tev1&lt;/code&gt; (CPU-friendly), or you can point it at any cloud / self-hosted service compatible with &lt;code&gt;POST /v1/systemone&lt;/code&gt; (not a plain Chat Completions endpoint). Short, high-frequency judgments are answered locally or on a dedicated decision endpoint — they don't burn the main model's generative budget.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 2: Fallback model (optional).&lt;/strong&gt; Only when System One is unavailable, a cheap preconfigured model profile takes over. &lt;strong&gt;If you've already configured the decision model, leave this field empty&lt;/strong&gt; — leaving it empty does &lt;em&gt;not&lt;/em&gt; route back to the main model; it falls through to the next tier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 3: Rules.&lt;/strong&gt; Deterministic rules. Today this mainly covers memory extraction; other judgment points opt out of this tier and continue on the original path: we'd rather follow a hard-coded fail direction than pretend to be smarter than we are.&lt;/p&gt;

&lt;p&gt;The whole chain is &lt;strong&gt;fail-open&lt;/strong&gt;: if a tier times out, isn't configured, or opts out, it doesn't drag the conversation down — tools schemas are still sent, memory extraction still runs. We'd rather spend one extra call than silently drop a capability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;About scores.&lt;/strong&gt; The System One protocol may return an internal ranking score; the client only uses it to pick an enum result (e.g. ≥0.5 → yes). It does &lt;em&gt;not&lt;/em&gt; expose scores to product logic, and it never writes "if p &amp;gt; 0.9, auto-execute." The numbers a typical local decision model emits are &lt;strong&gt;uncalibrated ranking scores&lt;/strong&gt;, not business thresholds. Truly calibrated probabilities ("90% means ~90%") require a dedicated training path; the open-source default path doesn't pretend to have it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why now?&lt;/strong&gt; The decision model wasn't invented in v0.5. v0.4 already pulled local Ollama installation, &lt;code&gt;bge-m3&lt;/code&gt; pulls, model paths, and uninstall into the "Matching &amp;amp; Decision" settings page. v0.5's delta is mostly pulling one more model (&lt;code&gt;tev1&lt;/code&gt;) on the same Ollama, behind the same install UX, unified under &lt;code&gt;/v1/systemone&lt;/code&gt;. Once the infrastructure is in place, the marginal cost of adding a decision model is low.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Four easy-to-mix-up points
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Why CPU-friendly?&lt;/strong&gt; High-frequency judgments need to be short and resident. &lt;code&gt;tev1&lt;/code&gt; is CPU-friendly per the product docs — it runs on an ordinary office machine, so you don't round-trip a cloud generative call just to "think for a second."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why two layers of switches?&lt;/strong&gt; If you've configured a decision model but haven't flipped the master toggle under "Runtime → Smart acceleration," you &lt;strong&gt;do not&lt;/strong&gt; save main-model tokens. After the master is on, you still turn on the individual features you want: tool candidate narrowing, tool-result pruning, memory pre-judgment, long-doc selection. The system needs to know explicitly which capabilities are on — it doesn't assume "model installed = everything on."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retrieval and decision can share one Ollama.&lt;/strong&gt; &lt;code&gt;bge-m3&lt;/code&gt; for matching and &lt;code&gt;tev1&lt;/code&gt; for decision can both live on the same Ollama instance. The "Matching &amp;amp; Decision" settings page keeps both links together: install progress, model paths, custom directory, cleanup — all in one place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Installing Ollama.&lt;/strong&gt; Both System One and enhanced matching assume you have Ollama running. The settings page walks you through install, model pull, and cleanup — get Ollama first, none of the decision chain matters if you can't run it.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Measured numbers — two separate ledgers
&lt;/h2&gt;

&lt;p&gt;Don't mix "we swapped the decision backend" with "the main model's prompt got shorter" on one chart.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.1 The main model got cheaper: narrowing + prefiltering
&lt;/h3&gt;

&lt;p&gt;Benchmark: 37 real read-only business requests across 3 backends (390 tools total), main model DeepSeek-Flash. Relative to prefilter width &lt;strong&gt;32&lt;/strong&gt;, the default width &lt;strong&gt;16&lt;/strong&gt; cuts turn-0 prompt tokens by &lt;strong&gt;~34%&lt;/strong&gt;; over 5 rounds (185 runs), run success is 99.5%. Compared to sending all 390 tools in one turn (~85k prompt tokens at turn-0 vs ~1.4k–3.8k), it's an order-of-magnitude drop, not just 34%.&lt;/p&gt;

&lt;p&gt;These numbers reflect &lt;strong&gt;tool narrowing + prefiltering&lt;/strong&gt; on the main model's prompt. Corpus and scripts live in &lt;code&gt;scripts/tool-routing-eval&lt;/code&gt; and are reproducible. &lt;strong&gt;It is not "System One alone saves 34%"&lt;/strong&gt; — when prefilter width is fixed and only the decision backend swaps to &lt;code&gt;tev1&lt;/code&gt;, the main model's prompt width is roughly the same.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.2 Which processes moved off the generative main model
&lt;/h3&gt;

&lt;p&gt;A decision-model call is usually far cheaper than a main-model call (self-hosted &lt;code&gt;tev1&lt;/code&gt; ≈ electricity). What you actually want to see is: &lt;strong&gt;which processes no longer go (or go less) through generative calls.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Process&lt;/th&gt;
&lt;th&gt;Without smart acceleration / decision model&lt;/th&gt;
&lt;th&gt;With System One on and the matching toggle enabled&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Run memory extraction?&lt;/td&gt;
&lt;td&gt;Often another generative call to extract&lt;/td&gt;
&lt;td&gt;Decide first; if "not worth it," &lt;strong&gt;the whole extraction is cancelled&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Which backends this turn?&lt;/td&gt;
&lt;td&gt;Full schema (or another model asked)&lt;/td&gt;
&lt;td&gt;Decision + prefilter; main model only sees the narrowed list&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keep a huge tool result?&lt;/td&gt;
&lt;td&gt;Often the whole block goes into context / summary&lt;/td&gt;
&lt;td&gt;Decide first; if useless, don't feed it to the main model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Light or heavy routing?&lt;/td&gt;
&lt;td&gt;Heuristic, or another model arbitrates&lt;/td&gt;
&lt;td&gt;Optionally decided by the decision model&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The big savings are: &lt;strong&gt;fewer schemas and less context fed to the main model&lt;/strong&gt;, plus &lt;strong&gt;generative calls cancelled outright&lt;/strong&gt;. Swapping the decision backend for a cheaper implementation is the second ledger.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.3 Decision backend head-to-head: System One vs fallback model
&lt;/h3&gt;

&lt;p&gt;Same 37 requests, width 16, same machine, one run each (this is a decision-backend comparison — &lt;strong&gt;not&lt;/strong&gt; another pass over the width table):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Decision backend&lt;/th&gt;
&lt;th&gt;Run success&lt;/th&gt;
&lt;th&gt;Expected tools hit&lt;/th&gt;
&lt;th&gt;Wall clock&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;System One (local &lt;code&gt;tev1&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;37/37&lt;/td&gt;
&lt;td&gt;31/37&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;~376 s&lt;/strong&gt; (~10.2 s/request)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fallback model (&lt;code&gt;deepseek-flash&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;37/37&lt;/td&gt;
&lt;td&gt;29/37&lt;/td&gt;
&lt;td&gt;~556 s (~15.0 s/request)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Event sources are &lt;code&gt;systemone&lt;/code&gt; and &lt;code&gt;remote&lt;/code&gt;; main-model prompt width is the same. The fallback model's wall clock is &lt;strong&gt;~1.48×&lt;/strong&gt; — wiring in a decision model isn't "hiring another model to chat along," it's pulling high-frequency judgments out of generative calls. Scripts and notes are in &lt;code&gt;scripts/tool-routing-eval&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. How to try it
&lt;/h2&gt;

&lt;p&gt;Requirements: Go 1.25+, or grab a prebuilt binary from &lt;a href="https://github.com/rebornace/baize/releases" rel="noopener noreferrer"&gt;GitHub Releases&lt;/a&gt; (use v0.5.0). Out of the box it's still zero-component standard matching. For the full decision chain:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In &lt;strong&gt;Settings → Matching &amp;amp; Decision&lt;/strong&gt;, flip on System One (local Ollama pulls &lt;code&gt;tev1&lt;/code&gt;, or point at &lt;code&gt;/v1/systemone&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;Runtime → Smart acceleration&lt;/strong&gt;, enable the master toggle, then turn on the per-feature switches you want.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The article tracks the repo; the repo docs are the source of truth.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Closing
&lt;/h2&gt;

&lt;p&gt;From deterministic prefiltering, to optional vector retrieval, to a decision model wired into a chain — Baize moves high-frequency judgments from expensive places to cheaper ones, and when it can't or isn't sure, it fails open onto the original path. Judgment can plug into a dedicated decision endpoint; whether it's accurate and how well-calibrated it is, that's another ledger. Try it and tell me what breaks.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/rebornace/baize" rel="noopener noreferrer"&gt;https://github.com/rebornace/baize&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>go</category>
      <category>agents</category>
    </item>
    <item>
      <title>Baize v0.4.0: Tool Matching Upgraded — Optional Vector Retrieval, Lexical Baseline Unchanged</title>
      <dc:creator>rebornace</dc:creator>
      <pubDate>Thu, 08 Oct 2026 02:27:30 +0000</pubDate>
      <link>https://dev.to/rebornace/baize-v040-tool-matching-upgraded-optional-vector-retrieval-lexical-baseline-unchanged-20h4</link>
      <guid>https://dev.to/rebornace/baize-v040-tool-matching-upgraded-optional-vector-retrieval-lexical-baseline-unchanged-20h4</guid>
      <description>&lt;h2&gt;
  
  
  What this release is about
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/rebornace/baize" rel="noopener noreferrer"&gt;Baize&lt;/a&gt; is a team-facing AI assistant runtime: you point it at an existing service, upload an OpenAPI / Swagger-style doc, and the endpoints become tools the assistant can call. A real internal backend, once wired up, usually brings hundreds of tools into the catalog.&lt;/p&gt;

&lt;p&gt;That scale creates a problem: stuffing hundreds of tool schemas into every prompt is expensive, and the model has to pick from a very long list. Baize already solved the first part with a decision layer — before the main model call, a cheap deterministic step narrows the candidate tools. Published numbers: against 37 real read-only requests across 3 backends (390 tools total, DeepSeek-Flash), narrowing to the default 16 candidates cut turn-0 prompt tokens by about 34%, and 185 runs across 5 loops finished at 99.5% success.&lt;/p&gt;

&lt;p&gt;The lexical baseline, however, has a known ceiling: it ranks by tokens. Same meaning, different wording, and a tool may simply not surface. v0.4.0 enhances that step without touching the baseline.&lt;/p&gt;

&lt;h2&gt;
  
  
  What v0.4.0 adds
&lt;/h2&gt;

&lt;p&gt;Baize's tool retrieval has always been Tool-RAG in spirit: each tool is indexed as a small document (name, description, HTTP method and path, parameter names).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lexical baseline stays exactly as it was.&lt;/strong&gt; Every tool document gets a BM25 index plus a sparse TF-IDF index; at query time the two scores are fused with RRF. It ships on by default, needs zero dependencies, and stays the zero-friction path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's new is the dense channel.&lt;/strong&gt; Once an embedder is configured, each tool document also gets a vector. At query time, the query is embedded and cosine similarity is computed — and in the fusion, the dense channel is the primary one, with BM25 demoted to an auxiliary channel. The two are merged again with RRF before being handed to the model. Division of labor: BM25 catches exact terms — proper nouns and fixed phrases in your business domain; the dense channel catches paraphrases, so a user asking the same thing with different words still surfaces the right tool. The mixed mode and the pure lexical mode are distinct internal states, observable and reversible.&lt;/p&gt;

&lt;p&gt;You can pick either embedding source:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Local Ollama.&lt;/strong&gt; For setups that don't want to send text to a cloud embedder. The settings page walks through the whole flow: one-click Ollama install , pulling the matching model, showing install and model paths, a custom models directory, and a clean uninstall when you don't need it. No command line required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Any OpenAI-compatible Embedding API.&lt;/strong&gt; If you already have a compatible endpoint, fill in the base URL and key — the same habit Baize uses for chat models.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Design tradeoffs
&lt;/h2&gt;

&lt;p&gt;Making this an &lt;em&gt;optional&lt;/em&gt; enhancement rather than the default was deliberate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero-cost onboarding.&lt;/strong&gt; Fresh install, lexical matching just works. You only opt into the dense channel when you feel lexical recall isn't enough.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fail open to the lexical path.&lt;/strong&gt; If the dense channel misbehaves — Ollama not ready, embedding API timeout — retrieval automatically falls back to the pure lexical index and the conversation continues. Consistent with the runtime's fail-open philosophy: speedups should never become the new failure point.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Narrowing never disables tools.&lt;/strong&gt; Pruning only decides which schemas land in this step's prompt; any registered tool still runs if the model names it directly. When nothing matches, it falls back to the full set, and system / login tools are always retained.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easier to find.&lt;/strong&gt; The runtime settings page is now organized into tabs: common, speedups, memory &amp;amp; compaction, security. Tool matching lives under "speedups".&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reproducibility
&lt;/h2&gt;

&lt;p&gt;The release also ships an evidence evaluation script: it replays and scores tool-call traces. After a change to matching, one run tells you whether recall regressed, instead of relying on gut feel. Corpus and script are in the repo.&lt;/p&gt;

&lt;p&gt;(The previous v0.3.x line already brought the web workspace, long-horizon projection — model-facing hints only, saved chats never rewritten — and a built-in self-help skill. This post focuses on the v0.4.0 matching upgrade; the README covers everything else.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;You'll need Go 1.25+, or grab a prebuilt binary from GitHub Releases (which also ships the optional WeChat channel adapter). Unpack, fill in your .env file, and start — the console is at /ui. Out of the box you get lexical matching; to try the dense channel, open the tool matching page in settings and pick Ollama or an Embedding endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;The direction is straightforward: the more tools you wire in, the more retrieval at this step matters. Baize keeps it "zero-cost by default, enhancement on demand, fail open" — users who want no new dependencies see no change, and users willing to spend a little on embeddings get more reliable tool recall.&lt;/p&gt;

&lt;p&gt;v0.4.0 is out: &lt;a href="https://github.com/rebornace/baize" rel="noopener noreferrer"&gt;https://github.com/rebornace/baize&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feedback on matching quality, or on the Ollama one-click flow, is very welcome — I'm following the issues and will keep shipping.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>agents</category>
      <category>go</category>
    </item>
    <item>
      <title>Context as a File: Baize's CLM-Inspired Long-Conversation Projection</title>
      <dc:creator>rebornace</dc:creator>
      <pubDate>Sun, 04 Oct 2026 02:40:48 +0000</pubDate>
      <link>https://dev.to/rebornace/context-as-a-file-baizes-clm-inspired-long-conversation-projection-50n9</link>
      <guid>https://dev.to/rebornace/context-as-a-file-baizes-clm-inspired-long-conversation-projection-50n9</guid>
      <description>

&lt;blockquote&gt;
&lt;p&gt;Project: Baize — an AI assistant runtime for your team (Go 1.25+, MIT)&lt;br&gt;
Repo:&lt;br&gt;
&lt;a href="https://github.com/rebornace/baize" rel="noopener noreferrer"&gt;https://github.com/rebornace/baize&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. The short version
&lt;/h2&gt;

&lt;p&gt;Baize v0.3.x takes inspiration from the recent Context Language Models (CLM) papers and ships a constrained take: &lt;strong&gt;keep important facts in long chats&lt;/strong&gt;. It treats the context that reaches the model as something that can be curated — pin the essentials, roll up rolling summaries, drop oversized tool results — but it &lt;strong&gt;never rewrites the saved chat messages&lt;/strong&gt;, and on failure it automatically falls back to the existing compaction logic.&lt;/p&gt;

&lt;p&gt;One line for the tradeoff: CLM hands editing rights over the context to the model; Baize puts the editing in a system-side projection layer, trading some freedom for reliability.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Why long conversations are a hard problem for agents
&lt;/h2&gt;

&lt;p&gt;An agent's context is the classic append-only structure: every new message, tool result and reasoning trace gets appended to the history. Once a conversation gets long, the problems show up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Attention dilution&lt;/strong&gt;: with thousands of lines of history, one critical configuration item can get lost; the model may not reliably pick it up anymore.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Truncation is irreversible&lt;/strong&gt;: cutting old messages silently drops early agreements and context the user never notices.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Compaction is irreversible&lt;/strong&gt;: rolling summaries save space, but a summary is a lossy, one-way transformation; key details can simply disappear.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The traditional answer is to hand context management to an external framework: compress on a threshold, offload, retrieve. Those approaches work, but they are "the framework defines the actions, the model can only pick one".&lt;/p&gt;

&lt;h2&gt;
  
  
  3. What CLM brings
&lt;/h2&gt;

&lt;p&gt;CLM is a paper from late September 2026 (arXiv:2609.37725, by the University of Washington, Meta Superintelligence Labs, MIT and others). It turns the problem around: &lt;strong&gt;context is not an append-only log — it is a file the model has write permission on&lt;/strong&gt;. The model can append, but it can also edit, delete and rewrite that file, and every change is synced into the next turn's context.&lt;/p&gt;

&lt;p&gt;The observations in the paper are direct:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Models can learn on their own what is most worth keeping in context, and behaviors show up that previous frameworks never had — maintaining a tracker table for multi-agent collaboration, or defining reusable context-management functions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On the deep-research benchmark BrowseComp-Plus, out-of-the-box CLM beats the strongest baseline by 11.4% in accuracy while using 21.5% fewer inference FLOPs; on a 24-hour, multi-repo agent-swarm task it achieves 65% higher end-to-end speedup at the same compute.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Because context management becomes model behavior rather than an external harness policy, the strategy itself can keep improving through reinforcement learning.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The appeal is that context management stops being a hard-coded rule set and becomes a product the model can understand and learn. But handing the model full editing rights has real production gates: is the editing reliable, does the archive stay trustworthy, and what happens when it fails?&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Baize's constrained take: keep important facts in long chats
&lt;/h2&gt;

&lt;p&gt;Baize did not copy CLM wholesale. It extracted the core idea — &lt;strong&gt;context is curatable output, not an append-only log&lt;/strong&gt; — and added three constraints:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The projection only lives on the model input side; saved chats are never rewritten.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a thread gets long, the assistant keeps a model-facing projection of the facts it still needs: it pins what actually matters, summarizes the secondary stuff, and drops oversized tool results. The chat the user sees stays untouched, ready to review, fork or roll back at any time. This matters for team use: the archive is the basis of audit and trust. The model input can be curated aggressively; the archive must stay true.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Fail-open.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the projection fails, the assistant falls back to the original compaction and the conversation keeps going — context curation must never become an availability bottleneck. This matches the philosophy Baize's decision layer already follows: optimization must not affect whether the tools themselves still work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. No promise of lower cloud token usage.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The projection itself costs tokens; the curated result is not necessarily cheaper than plain compaction. The docs say it plainly. What this feature solves is &lt;strong&gt;key-fact retention and context usability&lt;/strong&gt; — not the bill.&lt;/p&gt;

&lt;p&gt;The knob is optional (default off), hot-reloadable, and costs nothing when unused.&lt;/p&gt;

&lt;p&gt;Why "constrained"? Baize talks to OpenAI-compatible APIs: it cannot read logits, and there is no native model-side context-editing capability. Rather than letting the model rewrite history in an uncontrolled place, the editing lives in the system-side projection layer, where it can be made reliable and reversible. CLM is the radical route of native model editing; Baize's projection is the pragmatic route of framework-style editing. Same goal, different cost and reliability.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. A little design philosophy
&lt;/h2&gt;

&lt;p&gt;Looking back at this upgrade, a few decisions are consistent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Optional, rather than deciding for the user by default&lt;/strong&gt;: the projection and the decision layer are both runtime knobs, off by default, taking effect via hot reload when enabled.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fail-open first&lt;/strong&gt;: projection falls back to compaction, routing falls back to full tools — availability is never harmed by an optimization.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are also working on long-conversation handling for agents, come discuss your tradeoffs at the repo: &lt;a href="https://github.com/rebornace/baize" rel="noopener noreferrer"&gt;https://github.com/rebornace/baize&lt;/a&gt;. Everything mentioned here (the projection and workspaces) already exists in the public repository; docs live under docs/developers in both English and Chinese. I'll keep following up on issues and suggestions.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Reference: Context Language Models (arXiv:2609.37725): &lt;/em&gt;&lt;a href="https://arxiv.org/abs/2609.37725" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2609.37725&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>go</category>
      <category>agents</category>
      <category>opensource</category>
    </item>
    <item>
      <title>After Jev Blew Up, I Brought Its "System One Judgment" Idea Into My Open-Source AI Agent</title>
      <dc:creator>rebornace</dc:creator>
      <pubDate>Wed, 23 Sep 2026 15:22:04 +0000</pubDate>
      <link>https://dev.to/rebornace/after-jev-blew-up-i-brought-its-system-one-judgment-idea-into-my-open-source-ai-agent-584e</link>
      <guid>https://dev.to/rebornace/after-jev-blew-up-i-brought-its-system-one-judgment-idea-into-my-open-source-ai-agent-584e</guid>
      <description>

&lt;blockquote&gt;
&lt;p&gt;Project: Baize — an AI assistant runtime for your team (Go 1.25+, MIT)&lt;br&gt;
Repo: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/rebornace/baize" rel="noopener noreferrer"&gt;https://github.com/rebornace/baize&lt;/a&gt;&lt;br&gt;
This post is not a tutorial for "integrating Jev". It shares one judgment: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jev is worth learning for more than its model — the engineering idea that high-frequency decisions shouldn't ask a generative model to write an essay.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And that idea lands without depending on Jev's service at all.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. Decision layer design: three principles + one red line
&lt;/h2&gt;

&lt;p&gt;Baize is a sidecar AI assistant runtime: a single process deployed next to your business systems, turning OpenAPI / MCP / HTTP plugins into callable tools, with human approval required for write operations. It calls models through OpenAI-compatible APIs, so it cannot read logits — and therefore cannot get calibrated probabilities. The decision layer was designed from the start with no probability-dependent logic.&lt;/p&gt;

&lt;p&gt;Three hard principles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pluggable&lt;/strong&gt; — one decision interface; local rules, a local small model, or a remote decision service are all implementations. Default to the cheapest. No dependency on any external service.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Degradable&lt;/strong&gt; — every decision point fails open to the existing rules path; the decision layer is never a single point of failure for a run.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Observable&lt;/strong&gt; — every judgment records its source and any degradation (decide.degraded, decide.tool_narrow events).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One red line: &lt;strong&gt;no confidence numbers of any kind in judgment results.&lt;/strong&gt; Without calibrated logits, a number that looks like a probability but is not calibrated is more dangerous than no number at all — it invites branches like "auto-execute when p &amp;gt; 0.9".&lt;/p&gt;

&lt;h3&gt;
  
  
  Interface shape: verdicts only
&lt;/h3&gt;

&lt;p&gt;The return shape is deliberately minimal: verdicts are yes / no enums; multi-select returns the chosen items; every answer carries its source (rules, remote, or fallback) and a degraded flag. There is no float confidence field in the interface — a type-level guarantee against treating uncalibrated numbers as confidence.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Chain: errors must be swallowed by degradation
&lt;/h3&gt;

&lt;p&gt;All implementations are chained and tried in order; the first success returns. If all fail, the chain returns the fallback verdict hard-coded by the call site, marked degraded. The chain itself never returns an error — errors are consumed by degradation, never propagate into the main flow. Callers always get a legal enum value; a decision-layer failure looks like "old behavior", never like a crash.&lt;/p&gt;

&lt;p&gt;Easy to miss: &lt;strong&gt;the fallback verdict is hard-coded at the call site, not a config item.&lt;/strong&gt; Failure directions differ per decision point — memory extraction fails open to Yes (spend a call rather than lose a memory), tool narrowing fails open to the full set (spend prefill rather than miss a tool). As a config knob, someone would eventually flip it under cost pressure and silently lose data. Failure direction is a safety property, not an ops parameter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementations today
&lt;/h3&gt;

&lt;p&gt;Two tiers on the chain, remote first: when a dedicated decision-model profile is configured, a small model answers under a prompt that forces enum-only output, validated by regex / JSON parsing; unparseable replies abstain and let the chain degrade. Multi-select answers accept only names from the offered set; out-of-set values are dropped. Because structured-output support varies across OpenAI-compatible endpoints, the contract is enforced by prompt + parsing, not by endpoint features.&lt;/p&gt;

&lt;p&gt;With no profile configured (or remote unavailable), the zero-latency rules backstop handles memory extraction only — it abstains everywhere else: short chitchat with no fact cue returns No; explicit fact cues ("remember that", "my number", passwords, addresses, phone numbers), a run of three or more digits, or substantial length returns Yes.&lt;/p&gt;

&lt;p&gt;Want to plug in a self-hosted inference endpoint that can read logits later? Add one more tier to the chain; callers don't change.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Four decision points: moving "judgment" out of "essay-writing"
&lt;/h2&gt;

&lt;p&gt;Four places in Baize were classic waste: paying a generative model to write a paragraph just to get a "yes / no / which one" answer. The decision layer moves each one down.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.1 DP-1: memory-extraction pre-check (the best cost/benefit)
&lt;/h3&gt;

&lt;p&gt;Baize used to fire an LLM call after every successful conversation to extract memory. Most conversations contain nothing worth remembering and return an empty array — but each one paid a full call plus a JSON-parse risk.&lt;/p&gt;

&lt;p&gt;Now, before paying for that call, the engine asks the layer "is this turn worth extracting?": a No skips extraction and records a skip event (marked as decided by the layer) together with the estimated input tokens saved, so the win is measurable; a Yes — or an unavailable layer — extracts exactly as before.&lt;/p&gt;

&lt;p&gt;The probe itself is capped at 1500 characters: the probe must never cost more than the call it tries to save.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.2 DP-2: two-level tool-candidate narrowing (the structural one)
&lt;/h3&gt;

&lt;p&gt;The most worthwhile change in Baize. Its positioning is "give it OpenAPI, get tools automatically"; the more connectors you attach, the more full tool schemas go to the main model every turn — prefill cost grows linearly with connectors. That is a structural cost that scales with your users.&lt;/p&gt;

&lt;p&gt;Narrowing has two levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Level 1: system routing (System Targets).&lt;/strong&gt; The decision model picks which backend systems — a handful of connector ids — this turn needs; it may pick several. Choosing a tool among hundreds is hard; choosing among three to five systems is easy. System descriptions are not hand-written: they are auto-derived from each system's tool names and descriptions via within-system term frequency times cross-system IDF, so adding a connector needs no routing-corpus maintenance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Level 2: in-system keyword prefilter.&lt;/strong&gt; Within the chosen systems, deterministic IDF keyword scoring (tool names weighted 2x, descriptions 1x, stopwords removed, CJK tokenized as bigrams), merged round-robin across systems, narrowed to 16 candidates by default.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Narrowing is not one-size-fits-all; three floors prevent over-pruning:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Category-tool floor:&lt;/strong&gt; for "list the users", a generic pagination tool ranks poorly under IDF — but a list request needs exactly that tool. List / detail intent forces the matching admin read tool into the set.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Auth floor:&lt;/strong&gt; once a system is chosen, its login / current-session primitives are always kept — on a 401 the model must recover the session on the spot, and the user will never say "login".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hybrid routing:&lt;/strong&gt; discriminative domain words in the query (e.g. pets, orders) force their systems and can never be removed by the model — the small model can only add on top, so it cannot vote away the only correct system.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Also, main-model calls support a choice constraint (tool_choice=required) via optional interfaces, without breaking existing provider signatures; providers that don't support it fail open to an ordinary call. A constraint is an optimization, not a hard gate.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.3 DP-3: tool-result pruning
&lt;/h3&gt;

&lt;p&gt;In long conversations, a big JSON returned by an earlier tool call keeps bloating the context. When compaction triggers, the layer judges each of the bulkiest tool results "worth keeping verbatim?"; unworthy ones become a short placeholder that preserves the message / tool-call pairing — the model can re-invoke the tool, and the original result stays in the run log.&lt;/p&gt;

&lt;p&gt;Three bounds keep the judgment cheap: only tool results are judged (not ordinary turns), only those over roughly 500 estimated tokens, at most 8 per turn. All failures mean keep everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.4 DP-4: tier arbitration
&lt;/h3&gt;

&lt;p&gt;Baize used a pure heuristic (image present? code fences?) to route each turn to light / standard / power tiers. The problem: "help me refactor this function" contains no code fences, gets classified as light, and the strong model never runs.&lt;/p&gt;

&lt;p&gt;DP-4 does not replace the heuristic. It only consults the layer when the heuristic lands on the ambiguous standard tier, the turn is long enough (at least 400 characters), and routing is Auto — asking for a light / power choice. Short turns are not worth asking: "hello" barely differs across tiers, and every consult costs latency.&lt;/p&gt;

&lt;p&gt;DP-4's judgment goes through the same chain: a dedicated decision tier makes the two-way pick when available; on degradation, the heuristic's standard tier stands — the layer advises, never rewrites the routing outcome.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Results and validation
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reproducible benchmark:&lt;/strong&gt; 37 real read-only business requests across 3 connected backends (390 tools total), model DeepSeek-Flash, stability run of 5 rounds (185 requests). With the default top-16: run success rate &lt;strong&gt;99.5% (184/185)&lt;/strong&gt;, average turn-0 prompt &lt;strong&gt;~3,090 tokens&lt;/strong&gt;, about &lt;strong&gt;34% lower&lt;/strong&gt; than top-32; sending all 390 tools directly measured &lt;strong&gt;~85k&lt;/strong&gt; turn-0 prompt tokens. The dataset and scripts live in scripts/tool-routing-eval — reproducible.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Default narrowing:&lt;/strong&gt; convergence kicks in above the threshold (default 12 tools); 16 candidates is the default cap — the measured cost / success sweet spot.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Regression harness:&lt;/strong&gt; sweep (width), stability, and analyze (offline) scripts that restore your previous configuration when they finish. An optimization that changes behavior should only go live with shadow data first, then flip shadow off.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything ships with the decision layer off by default and enabled per point: the master switch (decide_enabled) defaults to off, each decision point has its own knob, all controlled through the existing hot-reload settings — no wiring changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Honest boundaries
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No calibrated probabilities.&lt;/strong&gt; Baize calls OpenAI-compatible APIs and cannot read logits. The layer only ever returns enums, and there is no "auto-execute above a probability threshold" feature. Write approval stays deterministic rules + human approval. That red line does not bend for Jev.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;This is not "integrating Jev".&lt;/strong&gt; The decision layer depends on no external decision service. If you later point your inference at a local vLLM running a decision plugin, the remote implementation works as-is — a free win, not an architectural prerequisite.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  5. Closing
&lt;/h2&gt;

&lt;p&gt;Jev made one thing click for me: &lt;strong&gt;models matter, of course — but plenty of high-frequency little decisions shouldn't ask a generative model to write an essay every time.&lt;/strong&gt; Pulling "judgment" out of "generation" into a pluggable, degradable, observable layer is engineering any agent project can do — it does not depend on any company's window.&lt;/p&gt;

&lt;p&gt;Baize's decision layer is live in the main branch (internal/decide + four decision points). Come take a look, run it, or open an issue:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;GitHub: &lt;a href="https://github.com/rebornace/baize" rel="noopener noreferrer"&gt;https://github.com/rebornace/baize&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;More: the README's decision-layer section, and the bilingual developer docs under docs/developers&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have hit the same wall — tool schemas going to the model every turn while prefill gets more expensive — I'd love to hear your solution. I'll keep following up on feedback and issues.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>opensource</category>
      <category>jev</category>
    </item>
    <item>
      <title>Building a Lightweight AI Agent in Go: Baize's Architecture and Trade-offs</title>
      <dc:creator>rebornace</dc:creator>
      <pubDate>Thu, 17 Sep 2026 02:06:02 +0000</pubDate>
      <link>https://dev.to/rebornace/building-a-lightweight-ai-agent-in-go-baizes-architecture-and-trade-offs-4c0k</link>
      <guid>https://dev.to/rebornace/building-a-lightweight-ai-agent-in-go-baizes-architecture-and-trade-offs-4c0k</guid>
      <description>

&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;Baize is a "sidecar" AI assistant runtime: a single process that sits beside services you already run, turns API documentation into tools the assistant can call, and pauses important writes until a person approves them. Stop it and it leaves almost nothing behind.&lt;/p&gt;

&lt;p&gt;One sentence positioning: &lt;strong&gt;it's a runtime, not a framework.&lt;/strong&gt; That positioning drives every architecture decision below.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Why Go
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Single binary, zero dependencies
&lt;/h3&gt;

&lt;p&gt;The core requirement of sidecar deployment is "one process, copy it over, run it." A Go binary needs no interpreter, no dependency installation, no virtualenv on the target machine. For an assistant meant to live in enterprise environments, that's the lowest-cost delivery form.&lt;/p&gt;

&lt;h3&gt;
  
  
  Goroutine concurrency
&lt;/h3&gt;

&lt;p&gt;One assistant process serves multiple entry points at once: the web console, signed alert/ticket ingress, and IM channels. Goroutines make "one process handling many sessions concurrently" straightforward — and parallel tool calls later become almost free.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cross-compilation
&lt;/h3&gt;

&lt;p&gt;Enterprise environments run everything: Windows, Linux, macOS, ARM. A single &lt;code&gt;GOOS=linux GOARCH=arm64 go build&lt;/code&gt; produces a binary for the target platform, with no toolchain setup on the destination machine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Static typing and tool contracts
&lt;/h3&gt;

&lt;p&gt;Tool input schemas come from OpenAPI documents; mapping them onto Go's strong types catches many errors at compile time. For a long-running daemon, that saves a lot of operational pain.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Architecture Overview
&lt;/h2&gt;

&lt;p&gt;A clean three-layer structure: &lt;strong&gt;core loop → tool router → executors&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Users / Channels ──► Agent Core Loop (think → pick tool → run → report)
                           │
                           ▼
                     Tool Router (Registry)
                           │
          ┌────────────────┼────────────────┐
    OpenAPI connector    HTTP plugin      MCP connector
          └────────────────┼────────────────┘
                           ▼
              Invoker closures (registered in Registry)
                           │
                     [ HITL approval gate ]
                           │
             ┌─────────────┴─────────────┐
       Direct execution          HTTP callback executor
     (plugin / proxy)            (callback to your side)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Core loop&lt;/strong&gt; (&lt;code&gt;internal/run&lt;/code&gt;): LLM thinks → picks a tool → executes → reports. The event stream (&lt;code&gt;llm.thinking&lt;/code&gt;, &lt;code&gt;llm.tool_call&lt;/code&gt;, &lt;code&gt;tool.result&lt;/code&gt;) is persisted, so the console can follow along in real time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tool router&lt;/strong&gt; (&lt;code&gt;internal/tool&lt;/code&gt;): a mutex-protected map that registers not functions but "tool contracts + invoker closures".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Executors&lt;/strong&gt; (&lt;code&gt;internal/connector&lt;/code&gt;): tools enter through three sources — OpenAPI docs, HTTP plugins, MCP tool servers — plus a "callback execution" mode that hands execution back to your side.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Key Design Decisions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why HTTP callback instead of a plugin protocol
&lt;/h3&gt;

&lt;p&gt;This is Baize's most important trade-off.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem with plugin protocols&lt;/strong&gt;: in-process plugins (Go plugins, shared libraries, language-binding SDKs) require the plugin to compile with the host process — language, version, and ABI must all align. Most enterprise systems aren't written in Go: legacy systems, Java/.NET/Python services can't load an in-process plugin at all. And even when they could, upgrading a plugin means restarting the process — "sidecar in, clean out" is gone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Baize does instead&lt;/strong&gt;: it doesn't execute the tool itself; it POSTs the invocation to your own endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tool"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"create_ticket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"run_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"run_xxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"agent_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"agent_xxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"idempotency_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uuid-xxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"callback_urls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://your-service/baize-events"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your side executes it and returns the result. Benefits: language-agnostic, process-isolated, auditable; &lt;code&gt;idempotency_key&lt;/code&gt; makes retries safe (no duplicate execution); &lt;code&gt;callback_urls&lt;/code&gt; lets your side keep driving follow-up actions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The cost&lt;/strong&gt;: one extra network round-trip, the callback endpoint must be reachable, and to prevent forged callbacks you need signed requests (Baize uses callback signing with a TTL against replay).&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic tool registration and discovery
&lt;/h3&gt;

&lt;p&gt;The registry (&lt;code&gt;tool.Registry&lt;/code&gt;) is the core data structure: a &lt;code&gt;sync.RWMutex&lt;/code&gt; guarding a map, with runtime register/unregister and per-connector bulk unregister — adding a tool or disabling a connector never requires a restart.&lt;/p&gt;

&lt;p&gt;Three tool sources share one registration path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;OpenAPI docs&lt;/strong&gt;: import Swagger/OpenAPI/Postman-style docs, each operation becomes a tool;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;HTTP plugins&lt;/strong&gt;: a small companion service declares "what tools exist and how to invoke them";&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;MCP tool servers&lt;/strong&gt;: connect to external tool ecosystems as an MCP client.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Security policy is baked into each entry at registration time: &lt;code&gt;require_approval&lt;/code&gt; (needs a human), &lt;code&gt;require_login&lt;/code&gt; (needs a session), &lt;code&gt;security_schemes&lt;/code&gt; (which auth scheme to use). &lt;strong&gt;Security policy is decided at registration, not asked at execution time&lt;/strong&gt; — this is the precondition for letting the assistant actually act.&lt;/p&gt;

&lt;p&gt;Discovery is trivial: &lt;code&gt;Registry.List()&lt;/code&gt; / &lt;code&gt;Registry.Specs()&lt;/code&gt; feed the model's tool list, visible live in the console.&lt;/p&gt;

&lt;h3&gt;
  
  
  Graceful degradation on failure
&lt;/h3&gt;

&lt;p&gt;Failures are the norm in AI agents, so degradation design matters more than the happy path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Timeout guardrail&lt;/strong&gt;: every tool invocation is bound to &lt;code&gt;context.WithTimeout&lt;/code&gt; (default 60s, configurable);&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Failure is content&lt;/strong&gt;: &lt;code&gt;Invoker&lt;/code&gt; returns &lt;code&gt;(content, isError, err)&lt;/code&gt; — &lt;code&gt;err&lt;/code&gt; is an infrastructure failure (timeout, network), &lt;code&gt;isError&lt;/code&gt; is a business-side failure. Both flow back to the model as structured content, so the model can retry, switch tools, or explain to the user — instead of crashing the whole session;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Approval rejection is not a crash&lt;/strong&gt;: when a human rejects a write, the run settles into an explicit "rejected" terminal state with a trail, no panic;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Everything is observable&lt;/strong&gt;: the &lt;code&gt;llm.tool_call → tool.result&lt;/code&gt; event stream is persisted, so any problem can be traced step by step;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Context compaction&lt;/strong&gt;: long sessions get rolling summaries so quality doesn't degrade as the thread grows.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. Go vs Python / Node.js for Agent Scenarios
&lt;/h2&gt;

&lt;p&gt;Let's be honest first: &lt;strong&gt;Python is the best choice in the AI/Agent ecosystem&lt;/strong&gt;. LangChain, LlamaIndex and most reference implementations live there. If your goal is fast experimentation and deep reuse of the LLM ecosystem, Python has no rival.&lt;/p&gt;

&lt;p&gt;Baize chose Go because its positioning is different:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Go&lt;/th&gt;
&lt;th&gt;Python&lt;/th&gt;
&lt;th&gt;Node.js&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Deployment&lt;/td&gt;
&lt;td&gt;Single binary, zero deps&lt;/td&gt;
&lt;td&gt;Interpreter + deps/venv&lt;/td&gt;
&lt;td&gt;Node runtime + node_modules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resource footprint&lt;/td&gt;
&lt;td&gt;Low; one resident process is cheap&lt;/td&gt;
&lt;td&gt;Higher; resident processes need care&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Concurrency&lt;/td&gt;
&lt;td&gt;Native goroutines&lt;/td&gt;
&lt;td&gt;GIL-limited; multi-process/async&lt;/td&gt;
&lt;td&gt;Event loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Type safety&lt;/td&gt;
&lt;td&gt;Static, compile-time checks&lt;/td&gt;
&lt;td&gt;Dynamic, found at runtime&lt;/td&gt;
&lt;td&gt;Dynamic / TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM ecosystem&lt;/td&gt;
&lt;td&gt;Newer, catching up fast&lt;/td&gt;
&lt;td&gt;Richest&lt;/td&gt;
&lt;td&gt;Rich&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-platform&lt;/td&gt;
&lt;td&gt;Cross-compile to all platforms&lt;/td&gt;
&lt;td&gt;Needs interpreter on target&lt;/td&gt;
&lt;td&gt;Needs Node on target&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The conclusion isn't "Go is better than Python" — it's &lt;strong&gt;positioning decides the language&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Goal: framework / fast experimentation → Python;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Goal: sidecar, resident, one-command deployment to enterprise environments, running on modest hardware for a long time → Go's advantages in deployment and resource usage are hard to replace.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  5. Core Code Snippets (Go)
&lt;/h2&gt;

&lt;p&gt;All snippets are from the project source, lightly trimmed. Each comes with one line on what problem it solves.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. A tool = contract + invoker closure
&lt;/h3&gt;

&lt;p&gt;Modeling a "tool" as "a contract the model sees (Spec) + an invoker closure injected by the connector" fully decouples routing from execution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Invoker&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isError&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Meta&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Spec&lt;/span&gt;            &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToolSpec&lt;/span&gt;
    &lt;span class="n"&gt;ConnectorID&lt;/span&gt;     &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Method&lt;/span&gt;          &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Path&lt;/span&gt;            &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;RequireLogin&lt;/span&gt;    &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;SecuritySchemes&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Dynamic registration with policy baked in
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;require_approval&lt;/code&gt; / &lt;code&gt;require_login&lt;/code&gt; are written into the entry at registration; the tool list is "hot" — adding/removing connectors never requires a restart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Registry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;RegisterMeta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt; &lt;span class="n"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inv&lt;/span&gt; &lt;span class="n"&gt;Invoker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requireApproval&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Spec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;            &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Spec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;invoker&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;         &lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;requireApproval&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;requireApproval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;requireLogin&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequireLogin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;connectorID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConnectorID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;          &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;            &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. HTTP callback executor
&lt;/h3&gt;

&lt;p&gt;Hands execution back to your side; the idempotency key makes network retries safe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"tool"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;            &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"arguments"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"run_id"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;          &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"agent_id"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AgentID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"idempotency_key"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IdempotencyKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TrimSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CallbackEventURL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"callback_urls"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"event"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CallbackEventURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;rawPayload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRequestWithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MethodPost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rawPayload&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Writes automatically enter the approval gate
&lt;/h3&gt;

&lt;p&gt;Non-GET/HEAD/OPTIONS operations are automatically flagged "needs approval" at registration, and only run after a human clicks approve/reject in the console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;needApproval&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequireApproval&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requireApprovalMutating&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;isMutatingMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Source&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToolSourceSpec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;needApproval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Timeouts and failure degradation
&lt;/h3&gt;

&lt;p&gt;A timeout guardrail plus "failure is content" semantics keeps one bad tool call from blowing up the whole session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;toolCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;toolCancel&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toolTimeout&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;toolCancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;invErr&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tools&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toolCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToolName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Arguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;invErr&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Infrastructure failure (timeout/network): persist and close the round&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;finalizeFailedRun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;runID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;invErr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;// When isError is true, the failure flows back to the model as content;&lt;/span&gt;
&lt;span class="c"&gt;// the model decides whether to retry or explain.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Baize is still early. The trade-offs above are far from "optimal" — especially the approval UX, channel adapters, and executor extensibility. If you have real-world scenarios, I'd love to hear them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Repo: &lt;a href="https://github.com/rebornace/baize" rel="noopener noreferrer"&gt;https://github.com/rebornace/baize&lt;/a&gt; (MIT)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Issues: open a discussion with your scenario — I'll follow up.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>ai</category>
      <category>agents</category>
      <category>go</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Add AI to Your Legacy System in 5 Minutes: A Baize Hands-On Tutorial (Spring Boot Example)</title>
      <dc:creator>rebornace</dc:creator>
      <pubDate>Wed, 16 Sep 2026 04:34:42 +0000</pubDate>
      <link>https://dev.to/rebornace/add-ai-to-your-legacy-system-in-5-minutes-a-baize-hands-on-tutorial-spring-boot-example-4i0p</link>
      <guid>https://dev.to/rebornace/add-ai-to-your-legacy-system-in-5-minutes-a-baize-hands-on-tutorial-spring-boot-example-4i0p</guid>
      <description>&lt;p&gt;Got a backend system that's been running for years — piles of APIs, complex business logic, and the thought of adding AI feels like a massive undertaking? This tutorial shows you how to hook up an AI assistant to your existing system in 5 minutes using &lt;strong&gt;Baize&lt;/strong&gt; — no refactoring, no core code changes, at most one new endpoint.&lt;/p&gt;

&lt;p&gt;Baize is an open-source AI Agent runtime written in Go. Its core ideas: &lt;strong&gt;sidecar deployment, APIs-as-tools, and human-in-the-loop approval for critical operations&lt;/strong&gt;. It connects to business systems over HTTP, so it doesn't matter what your backend is — Spring Boot, Django, Express, Gin, ThinkPHP — if it has APIs, it can connect. The AI understands user intent, calls the right tools automatically, and pauses for human confirmation on sensitive operations.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Language-agnostic, but we'll use Spring Boot as the main example.&lt;/strong&gt; The principles are fully universal — for other frameworks, just swap the Controller code in Step 3 for your language of choice. The Baize config file stays exactly the same.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;To follow along, you'll need two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A backend service&lt;/strong&gt; (we'll use a Spring Boot ticket system as an example, but feel free to use your own project)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Baize binary&lt;/strong&gt; (a single executable, zero dependencies)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can clone the repo which includes a demo ticket system and Baize configs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/rebornace/baize.git
&lt;span class="nb"&gt;cd &lt;/span&gt;baize
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The Spring Boot code examples in this tutorial use Java 17 + Spring Boot 3.x. Readers using other languages or frameworks can focus on the configuration and protocol sections, and map the code examples to their own stack.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 1: Download Baize and Start It with One Command
&lt;/h2&gt;

&lt;p&gt;Baize is a single static binary written in Go — no runtime to install.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: Download the Binary (Recommended)
&lt;/h3&gt;

&lt;p&gt;Grab the latest release for your platform from &lt;a href="https://github.com/rebornace/baize/releases" rel="noopener noreferrer"&gt;GitHub Releases&lt;/a&gt;, unzip it, and you're ready to go.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: Build from Source
&lt;/h3&gt;

&lt;p&gt;If you have Go 1.25+ locally, you can build it yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/rebornace/baize.git
&lt;span class="nb"&gt;cd &lt;/span&gt;baize
go build &lt;span class="nt"&gt;-o&lt;/span&gt; baize ./cmd/baize
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Start in Demo Mode
&lt;/h3&gt;

&lt;p&gt;Baize comes with a &lt;strong&gt;demo mode&lt;/strong&gt; that uses a Mock LLM (no API key needed) and a built-in demo ticket system — perfect for getting the full flow working first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Windows&lt;/span&gt;
.&lt;span class="se"&gt;\b&lt;/span&gt;aize demo

&lt;span class="c"&gt;# macOS / Linux&lt;/span&gt;
./baize demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you see output like this, it's running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;baize demo listening on :8080
mock ticket server listening on :18080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open your browser and go to &lt;strong&gt;&lt;a href="http://localhost:8080/ui" rel="noopener noreferrer"&gt;http://localhost:8080/ui&lt;/a&gt;&lt;/strong&gt; — you'll see Baize's web dashboard.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For production, use &lt;code&gt;baize start&lt;/code&gt; with a config file and a real LLM API key. We'll cover that later.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  No Config File? No Problem — Configure via the Web UI
&lt;/h3&gt;

&lt;p&gt;Baize supports &lt;strong&gt;zero-config startup&lt;/strong&gt; — just run &lt;code&gt;baize start&lt;/code&gt; without any config file, then open the Web UI and set everything up interactively: models, connectors, tools, approval rules — all of it. Great if you prefer a graphical interface over editing YAML by hand.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Windows PowerShell&lt;/span&gt;
&lt;span class="nv"&gt;$env&lt;/span&gt;:BAIZE_API_KEY &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"your-api-key"&lt;/span&gt;
.&lt;span class="se"&gt;\b&lt;/span&gt;aize start

&lt;span class="c"&gt;# macOS / Linux&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;BAIZE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-api-key"&lt;/span&gt;
./baize start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once it's running, go to &lt;a href="http://localhost:8080/ui" rel="noopener noreferrer"&gt;http://localhost:8080/ui&lt;/a&gt; → &lt;strong&gt;Settings&lt;/strong&gt; in the sidebar. You can configure your LLM provider, add connectors, and manage tools directly from the page. Changes take effect immediately — no restart needed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This tutorial uses YAML config files for clarity in the step-by-step flow. Both approaches produce the same result — pick whichever you prefer.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 2: Declare Tools in a Config File
&lt;/h2&gt;

&lt;p&gt;The core of how Baize connects to business systems is the &lt;strong&gt;Connector&lt;/strong&gt;. The most common approach is the &lt;strong&gt;OpenAPI Connector&lt;/strong&gt; — if you have an OpenAPI/Swagger spec, Baize automatically turns every endpoint into a tool the AI can use.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.1 Prepare the OpenAPI Spec
&lt;/h3&gt;

&lt;p&gt;Let's say your Spring Boot ticket system has these endpoints:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;operationId&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/tickets&lt;/td&gt;
&lt;td&gt;List all tickets&lt;/td&gt;
&lt;td&gt;&lt;code&gt;list_tickets&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;/tickets&lt;/td&gt;
&lt;td&gt;Create a ticket&lt;/td&gt;
&lt;td&gt;&lt;code&gt;create_ticket&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/tickets/{id}&lt;/td&gt;
&lt;td&gt;Get ticket details&lt;/td&gt;
&lt;td&gt;&lt;code&gt;get_ticket&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PATCH&lt;/td&gt;
&lt;td&gt;/tickets/{id}&lt;/td&gt;
&lt;td&gt;Update ticket status&lt;/td&gt;
&lt;td&gt;&lt;code&gt;update_ticket_status&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here's the corresponding OpenAPI spec (&lt;code&gt;ticket-api.yaml&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;openapi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3.0.3&lt;/span&gt;
&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ticket System API&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.0.0&lt;/span&gt;
&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;/tickets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;list_tickets&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;List all tickets&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;List of tickets&lt;/span&gt;
    &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;create_ticket&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create a ticket&lt;/span&gt;
      &lt;span class="na"&gt;requestBody&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
              &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
              &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;Ticket title&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
                &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;Priority level&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;201"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Created successfully&lt;/span&gt;
  &lt;span class="s"&gt;/tickets/{id}&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;get_ticket&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get ticket details&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;id&lt;/span&gt;
          &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;path&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;string&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ticket details&lt;/span&gt;
    &lt;span class="na"&gt;patch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;update_ticket_status&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Update ticket status&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;id&lt;/span&gt;
          &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;path&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;string&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;requestBody&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
              &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
              &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;New status&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Updated successfully&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If your Spring Boot project already has springdoc-openapi or Swagger integrated, you can export the JSON directly from &lt;code&gt;/v3/api-docs&lt;/code&gt; — Baize understands both YAML and JSON.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2.2 Write the Baize Config File
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;baize-config.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;listen&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:8080"&lt;/span&gt;
&lt;span class="na"&gt;store&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sqlite&lt;/span&gt;
  &lt;span class="na"&gt;sqlite_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./data/baize.db&lt;/span&gt;
&lt;span class="na"&gt;ui&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;llm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openai_compatible&lt;/span&gt;
  &lt;span class="na"&gt;base_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://api.deepseek.com&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deepseek-chat&lt;/span&gt;
  &lt;span class="na"&gt;api_key_env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;BAIZE_API_KEY&lt;/span&gt;

&lt;span class="na"&gt;agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ticket-agent&lt;/span&gt;
  &lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;are&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ticket&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;assistant.&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;You&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;can&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;only&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;access&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ticket&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;through&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tools."&lt;/span&gt;

&lt;span class="na"&gt;connector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ticket-api&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openapi&lt;/span&gt;
  &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./ticket-api.yaml&lt;/span&gt;
  &lt;span class="na"&gt;base_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://localhost:8081&lt;/span&gt;   &lt;span class="c1"&gt;# Your Spring Boot service URL&lt;/span&gt;
  &lt;span class="na"&gt;require_approval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;create_ticket&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;update_ticket_status&lt;/span&gt;

&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;max_steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt;
  &lt;span class="na"&gt;tool_timeout_sec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key configuration points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;connector.type: openapi&lt;/code&gt;&lt;/strong&gt; — Auto-discovers tools from the OpenAPI spec&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;connector.spec&lt;/code&gt;&lt;/strong&gt; — Path to your OpenAPI spec file&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;connector.base_url&lt;/code&gt;&lt;/strong&gt; — Your backend service URL; the AI calls this address when using tools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;require_approval&lt;/code&gt;&lt;/strong&gt; — Tools listed here require human confirmation before execution (recommended for all write operations)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2.3 Start with the Config File
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Set your LLM API Key (DeepSeek example)&lt;/span&gt;
&lt;span class="c"&gt;# Windows PowerShell&lt;/span&gt;
&lt;span class="nv"&gt;$env&lt;/span&gt;:BAIZE_API_KEY &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"your-api-key"&lt;/span&gt;

&lt;span class="c"&gt;# macOS / Linux&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;BAIZE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-api-key"&lt;/span&gt;

&lt;span class="c"&gt;# Start&lt;/span&gt;
./baize start &lt;span class="nt"&gt;-c&lt;/span&gt; baize-config.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;a href="http://localhost:8080/ui" rel="noopener noreferrer"&gt;http://localhost:8080/ui&lt;/a&gt; → click "Tools" in the sidebar, and you'll see all 4 ticket endpoints listed as AI-usable tools.&lt;/p&gt;

&lt;p&gt;At this step, &lt;strong&gt;you haven't changed a single line of your business code&lt;/strong&gt; — Baize is just another caller hitting your APIs, no different from a frontend app.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Add a Callback Endpoint for Full Control (Spring Boot Example)
&lt;/h2&gt;

&lt;p&gt;The OpenAPI approach above works great when "the APIs already exist and the AI can call them directly." But sometimes you need more control:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You want to add custom logic before/after AI calls (audit logging, rate limiting, parameter validation)&lt;/li&gt;
&lt;li&gt;The tool logic is complex and not suitable for exposing as a direct API&lt;/li&gt;
&lt;li&gt;You don't want the AI touching internal services directly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where &lt;strong&gt;Execution Callback&lt;/strong&gt; mode comes in: instead of calling your business APIs directly, Baize sends a message saying "which tool to call and with what arguments" to an endpoint you specify. You decide how to execute it. The protocol is dead simple — any language that can serve HTTP can implement it.&lt;/p&gt;

&lt;p&gt;Below is a Spring Boot example; the same pattern applies to Django, Express, Gin, and any other framework.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1 Add an &lt;code&gt;/execute&lt;/code&gt; Endpoint (Spring Boot Version)
&lt;/h3&gt;

&lt;p&gt;Add one Controller with one method to your Spring Boot project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.example.ticket.controller&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.example.ticket.service.TicketService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.web.bind.annotation.*&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Map&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/baize"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BaizeCallbackController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;TicketService&lt;/span&gt; &lt;span class="n"&gt;ticketService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;BaizeCallbackController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TicketService&lt;/span&gt; &lt;span class="n"&gt;ticketService&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ticketService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ticketService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/execute"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nd"&gt;@RequestHeader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Baize-Protocol"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nd"&gt;@RequestBody&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="c1"&gt;// Protocol check&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="s"&gt;"v0"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"unsupported protocol"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
                &lt;span class="s"&gt;"is_error"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
            &lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tool"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nd"&gt;@SuppressWarnings&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unchecked"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"arguments"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Dispatch to your business logic based on tool name&lt;/span&gt;
        &lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"list_tickets"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ticketService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;listTickets&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"create_ticket"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ticketService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createTicket&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
                &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"priority"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"get_ticket"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ticketService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTicket&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"update_ticket_status"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ticketService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;updateStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
                &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;};&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"unknown tool: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
                &lt;span class="s"&gt;"is_error"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
            &lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"is_error"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it — one Controller, one method, one switch statement for dispatch. You're in full control of the execution logic. Add logging, add authorization checks, add caching — whatever you need.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.2 Update Baize Config to Point to the Callback URL
&lt;/h3&gt;

&lt;p&gt;Change the connector in &lt;code&gt;baize-config.yaml&lt;/code&gt; to callback mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;connector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ticket-api&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openapi&lt;/span&gt;
  &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./ticket-api.yaml&lt;/span&gt;
  &lt;span class="na"&gt;base_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;                           &lt;span class="c1"&gt;# Can be empty in callback mode&lt;/span&gt;
  &lt;span class="na"&gt;execution_callback_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://localhost:8081/baize/execute&lt;/span&gt;
  &lt;span class="na"&gt;require_approval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;create_ticket&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;update_ticket_status&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key addition is &lt;strong&gt;&lt;code&gt;execution_callback_url&lt;/code&gt;&lt;/strong&gt; — with this field set, Baize won't call &lt;code&gt;base_url&lt;/code&gt; directly. Instead, it sends tool invocation requests to this callback address.&lt;/p&gt;

&lt;p&gt;You still need the OpenAPI spec file because Baize uses it to discover what tools exist and what parameters they take — that's &lt;strong&gt;tool discovery&lt;/strong&gt;. The actual &lt;strong&gt;tool execution&lt;/strong&gt; is handled by your Spring Boot service.&lt;/p&gt;

&lt;p&gt;After restarting Baize, the user experience looks the same, but the execution path is completely different — every tool call now goes through your &lt;code&gt;/baize/execute&lt;/code&gt; endpoint.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Send a Message and Watch the AI Call Tools
&lt;/h2&gt;

&lt;p&gt;Once Baize is running, you can talk to the AI in two ways: via the Web UI or the HTTP API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: Web UI (Recommended for Getting Started)
&lt;/h3&gt;

&lt;p&gt;Open &lt;strong&gt;&lt;a href="http://localhost:8080/ui" rel="noopener noreferrer"&gt;http://localhost:8080/ui&lt;/a&gt;&lt;/strong&gt; and type in the chat box:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Show me all the tickets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see the AI's reasoning process step by step, including the full &lt;code&gt;list_tickets&lt;/code&gt; tool call — both the request parameters and the returned result.&lt;/p&gt;

&lt;p&gt;Try this next:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create a ticket with title "Login page loads slowly" and high priority
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because &lt;code&gt;create_ticket&lt;/code&gt; is in the &lt;code&gt;require_approval&lt;/code&gt; list, the AI will pause and wait for your confirmation. Click &lt;strong&gt;Approve&lt;/strong&gt; on the approval card that pops up, and only then will it actually execute the create operation. This is &lt;strong&gt;HITL (Human-in-the-Loop)&lt;/strong&gt; — sensitive operations are never executed blindly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: HTTP API (Great for Integration)
&lt;/h3&gt;

&lt;p&gt;Baize exposes a full REST control plane that you can call from scripts or other systems.&lt;/p&gt;

&lt;p&gt;Start a new conversation run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8080/v0/runs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "agent_id": "ticket-agent",
    "messages": [
      {
        "role": "user",
        "content": "Show me all the tickets"
      }
    ]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response includes a &lt;code&gt;run_id&lt;/code&gt;. Use it to check status and stream events:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Get run result&lt;/span&gt;
curl http://localhost:8080/v0/runs/&lt;span class="o"&gt;{&lt;/span&gt;run_id&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Stream events (SSE)&lt;/span&gt;
curl http://localhost:8080/v0/runs/&lt;span class="o"&gt;{&lt;/span&gt;run_id&lt;span class="o"&gt;}&lt;/span&gt;/stream
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Going Further
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Adding More Tools
&lt;/h3&gt;

&lt;p&gt;Tools come from the OpenAPI spec. Adding a tool = adding an endpoint to the spec, then restarting Baize (or hot-loading through the settings page).&lt;/p&gt;

&lt;p&gt;For example, if you want to add a "close ticket" tool, add this path to &lt;code&gt;ticket-api.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="s"&gt;/tickets/{id}/close&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;close_ticket&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Close a ticket&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;id&lt;/span&gt;
          &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;path&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;string&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Closed successfully&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add &lt;code&gt;close_ticket&lt;/code&gt; to &lt;code&gt;require_approval&lt;/code&gt;, and you're done.&lt;/p&gt;

&lt;h3&gt;
  
  
  Timeouts and Retries
&lt;/h3&gt;

&lt;p&gt;Baize has a default timeout for each tool call, which you can adjust in the config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;max_steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt;
  &lt;span class="na"&gt;tool_timeout_sec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;120&lt;/span&gt;    &lt;span class="c1"&gt;# Timeout per tool call, default is 60 seconds&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your business APIs are occasionally flaky, you can handle retries inside your Spring Boot callback endpoint — Spring Retry or Resilience4j both work great. Baize just "sends the request and waits for the result" — retry strategy is up to you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling Login / Auth
&lt;/h3&gt;

&lt;p&gt;If your system requires login before calling APIs, Baize has you covered. Configure &lt;code&gt;auth.capture&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;connector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ticket-api&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openapi&lt;/span&gt;
  &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./ticket-api.yaml&lt;/span&gt;
  &lt;span class="na"&gt;execution_callback_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://localhost:8081/baize/execute&lt;/span&gt;
  &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;passthrough&lt;/span&gt;
    &lt;span class="na"&gt;capture&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;tool_name_glob&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*login*"&lt;/span&gt;
      &lt;span class="na"&gt;token_json_paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accessToken"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data.accessToken"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;label_json_paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;header_template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{{token}}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the AI detects that login is required, it calls the login tool first, automatically extracts the token from the response, and attaches it to subsequent calls. Login info is stored in the session identity and persists across conversations.&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Q: Does Baize have to run in Kubernetes?
&lt;/h3&gt;

&lt;p&gt;No. Baize is a single binary — it runs on bare metal, VMs, Docker, and Kubernetes alike. Put it on the same internal network as your legacy system, or even deploy it on the public network and call back over VPN (security will be weaker, of course).&lt;/p&gt;

&lt;h3&gt;
  
  
  Q: What if my OpenAPI spec is outdated or doesn't cover all endpoints?
&lt;/h3&gt;

&lt;p&gt;Two options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use &lt;strong&gt;Execution Callback&lt;/strong&gt; mode — the OpenAPI spec just declares "what tools exist," and you write the actual execution logic yourself&lt;/li&gt;
&lt;li&gt;Use the &lt;strong&gt;HTTP Plugin v0&lt;/strong&gt; protocol — write a small standalone service to add capabilities your original system doesn't have&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Q: Could the AI call APIs randomly and mess up my data?
&lt;/h3&gt;

&lt;p&gt;No. Two layers of protection:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Human approval&lt;/strong&gt; — tools in the &lt;code&gt;require_approval&lt;/code&gt; list pause before execution and wait for someone to click approve in the dashboard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read-only first&lt;/strong&gt; — it's good practice to start with read-only endpoints open and put all write operations behind approval, then gradually open things up as you gain confidence&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Q: Which LLMs are supported?
&lt;/h3&gt;

&lt;p&gt;Any model compatible with the OpenAI API format works — including OpenAI, DeepSeek, Anthropic (via compatible gateways), local Ollama, and more. Just change &lt;code&gt;llm.base_url&lt;/code&gt; and &lt;code&gt;llm.model&lt;/code&gt; in the config.&lt;/p&gt;

&lt;h3&gt;
  
  
  Q: Can it connect to messaging platforms like Slack or Discord?
&lt;/h3&gt;

&lt;p&gt;Yes. Baize's channels are designed as an extensible capability. You can use the Webhook channel to adapt any platform. One assistant, one set of tools, one approval flow — reachable from multiple channels.&lt;/p&gt;

&lt;h3&gt;
  
  
  Q: What's the difference between demo mode and production mode?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Demo mode&lt;/strong&gt; (&lt;code&gt;baize demo&lt;/code&gt;): Uses Mock LLM, no API key needed, includes a built-in demo ticket system — great for trying it out quickly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production mode&lt;/strong&gt; (&lt;code&gt;baize start&lt;/code&gt;): Connects to a real LLM, works with real business systems — full feature set&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Let's recap the whole flow — really just four steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Download Baize&lt;/strong&gt; → single binary, zero dependencies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write an OpenAPI spec + configure the connector&lt;/strong&gt; → APIs become tools automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;(Optional) Add an &lt;code&gt;/execute&lt;/code&gt; endpoint&lt;/strong&gt; → full control over execution logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chat&lt;/strong&gt; → the AI calls tools automatically, with human approval for sensitive operations&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Baize's design philosophy is "sidecar deployment, clean removal" — it doesn't intrude on your business system, it just adds an intelligent caller. Turn it on when you want to try it, turn it off when you don't. Your legacy system stays untouched.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Open Source Info&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Baize&lt;/strong&gt; · MIT License · Go 1.25 · Zero C dependencies&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/rebornace/baize" rel="noopener noreferrer"&gt;https://github.com/rebornace/baize&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Core features: ReAct Agent · OpenAPI Connector · HTTP Plugin v0 · Execution Callback · MCP Bridge/Export · HITL (Human-in-the-Loop) · Built-in Web UI&lt;/p&gt;

&lt;p&gt;Supported LLMs: OpenAI / DeepSeek / any OpenAI-compatible API&lt;/p&gt;

&lt;p&gt;Stars, issues, and PRs are all welcome.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>go</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
