<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Go-Chat on PHP Boy Scout</title><link>https://phpboyscout.uk/categories/go-chat/</link><description>Recent content in Go-Chat on PHP Boy Scout</description><generator>Hugo -- gohugo.io</generator><language>en-gb</language><copyright>Matt Cockayne</copyright><lastBuildDate>Thu, 17 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://phpboyscout.uk/categories/go-chat/index.xml" rel="self" type="application/rss+xml"/><item><title>A client you can still use when you configured it wrong</title><link>https://phpboyscout.uk/a-client-you-can-still-use-when-you-configured-it-wrong/</link><pubDate>Thu, 17 Sep 2026 00:00:00 +0000</pubDate><guid>https://phpboyscout.uk/a-client-you-can-still-use-when-you-configured-it-wrong/</guid><description>&lt;img src="https://phpboyscout.uk/a-client-you-can-still-use-when-you-configured-it-wrong/cover-a-client-you-can-still-use-when-you-configured-it-wrong.png" alt="Featured image of post A client you can still use when you configured it wrong" /&gt;&lt;p&gt;There&amp;rsquo;s a question that turns up every time you wire a chat client together, and it&amp;rsquo;s this. You set a temperature. The model you picked doesn&amp;rsquo;t do temperature.&lt;/p&gt;
&lt;p&gt;Now what?&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a fair question, and Go has a very firm opinion about it. I disagree with Go.&lt;/p&gt;
&lt;h2 id="go-wants-a-yes-or-a-no"&gt;Go wants a yes or a no
&lt;/h2&gt;&lt;p&gt;The constructor convention is about as settled as conventions get: return &lt;code&gt;(T, error)&lt;/code&gt;, and on a non-nil error the &lt;code&gt;T&lt;/code&gt; is unusable so don&amp;rsquo;t touch it. Most people know it, most follow it, and it&amp;rsquo;s a good rule. It&amp;rsquo;s good because it assumes construction is one thing that either happened or didn&amp;rsquo;t: open a file, dial a socket, parse a document. Binary outcome, binary contract, no argument. A chat client isn&amp;rsquo;t one thing.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a provider, a model, credentials, a timeout, an endpoint, sampling controls, streaming, tool support, a fallback chain, and a fistful of knobs that only exist on some models. Building one is a dozen small decisions, and the interesting failures are always partial. Eleven of them worked. The twelfth (usually the one you cared about least) doesn&amp;rsquo;t apply here.&lt;/p&gt;
&lt;p&gt;The binary contract has no way to say that, so it forces the library into one of two bad answers, and worse, it forces the choice &lt;em&gt;once&lt;/em&gt;, globally, by whoever wrote the constructor, on behalf of every caller and every combination they&amp;rsquo;ll ever try. Bin a working client over a setting nobody would call essential, or keep quiet and let them think it applied.&lt;/p&gt;
&lt;h2 id="so-give-them-a-receipt"&gt;So give them a receipt
&lt;/h2&gt;&lt;p&gt;I broke it, deliberately. Build a client with an invalid combination and you get a client: the best working one I can assemble, fully usable, minus the bits that couldn&amp;rsquo;t apply, and with it an itemised list of what didn&amp;rsquo;t make it on. Here&amp;rsquo;s your client, and here&amp;rsquo;s the receipt: two true things instead of one lie.&lt;/p&gt;
&lt;p&gt;What makes that a design, rather than me being soft about it, is the exception. There&amp;rsquo;s a hard line between a setting that got &lt;em&gt;dropped&lt;/em&gt; and a construction that&amp;rsquo;s &lt;em&gt;impossible&lt;/em&gt;, and that line isn&amp;rsquo;t up for negotiation at runtime. Temperature on a model with no temperature is a degradation, so you get the client plus a line on the receipt; a missing credential is fatal, so you get nothing and the error says so through a sentinel called, without much ceremony, &lt;code&gt;ErrUnableToConstruct&lt;/code&gt;. Without that line the whole idea collapses into a library deciding your mistakes don&amp;rsquo;t matter much.&lt;/p&gt;
&lt;h2 id="a-receipt-nobody-can-read-is-just-litter"&gt;A receipt nobody can read is just litter
&lt;/h2&gt;&lt;p&gt;This pattern lives or dies on the quality of one error value, which is a nerve-wracking place to put a design. &amp;ldquo;Some settings were not applied&amp;rdquo; is worse than a hard failure: it tells you something&amp;rsquo;s wrong, hands you no way to act on it, and takes away the crash that would at least have pointed at a line number. So the line item has to carry enough to actually fix the thing:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// DroppedSetting names a Config field that could not be applied, and why.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DroppedSetting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// Fields names the Config fields that were not applied, e.g. &amp;#34;Temperature&amp;#34;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Fields&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// Capability is the capability the fields required.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Capability&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Capability&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// Reason is a short human-readable explanation.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Reason&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Which field, what capability it needed, and why it didn&amp;rsquo;t happen.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a fix in the history that exists purely because my first go at this wasn&amp;rsquo;t good enough: &lt;a class="link" href="https://gitlab.com/phpboyscout/go/chat/-/commit/781d131" target="_blank" rel="noopener"
 &gt;&lt;code&gt;fix(chat): name the default model in a dropped-setting error&lt;/code&gt;&lt;/a&gt;. The original said a setting had been dropped without saying which model it had been reasoning about, so if you hadn&amp;rsquo;t picked a model and were leaning on the default, the error told you something was wrong about a thing it then declined to name. That&amp;rsquo;s the very failure I&amp;rsquo;d spent two weeks describing as worse than crashing&amp;hellip; shipped by me, in the first cut of the feature designed to prevent it!&lt;/p&gt;
&lt;p&gt;It lasted about twelve hours.&lt;/p&gt;
&lt;p&gt;The whole receipt carries the same obligation, and itemises &lt;em&gt;everything&lt;/em&gt; rather than the first thing it trips over:&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;Construction reports every problem rather than the first, so a caller fixing three mistakes learns all three from one call instead of one round-trip at a time.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;It also does Go&amp;rsquo;s multi-error unwrapping, so &lt;code&gt;errors.Is&lt;/code&gt; reaches any member sentinel whether it was the only problem or one of four. Worth proving rather than assuming, because the project layers &lt;code&gt;cockroachdb/errors&lt;/code&gt; over the standard library and I wanted to watch &lt;code&gt;errors.Join&lt;/code&gt; and the wrapped values get along before building an API on top of them. &lt;a class="link" href="https://phpboyscout.uk/a-stack-trace-is-not-an-error-message/" &gt;A stack trace is not an error message&lt;/a&gt; is the same instinct pointed at logs. This is it pointed at a return value.&lt;/p&gt;
&lt;h2 id="where-this-falls-down"&gt;Where this falls down
&lt;/h2&gt;&lt;p&gt;There is a decent case against. A caller who ignores that receipt is holding a client that isn&amp;rsquo;t doing what they configured, which is the failure the strict contract exists to prevent. Plenty of people will write &lt;code&gt;client, _ :=&lt;/code&gt; and get on with their day (I&amp;rsquo;ve done it myself, more than once); the strict version would have stopped them and mine won&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;But the information was &lt;em&gt;given&lt;/em&gt;. They got a complete, specific, actionable account of what didn&amp;rsquo;t apply, and they made a choice, which is a different thing entirely from a library that swallows the setting and says nothing, even if from the outside you can&amp;rsquo;t tell them apart. I&amp;rsquo;d be overselling it if I claimed the trade goes away. It doesn&amp;rsquo;t. I&amp;rsquo;ve moved a decision from the library to the caller, and some callers won&amp;rsquo;t make it.&lt;/p&gt;
&lt;p&gt;Still, the strict contract was never protecting them anyway. It protects you from a &lt;em&gt;whole broken client&lt;/em&gt;, and this was never a whole broken client. It was a perfectly good one with a temperature setting that went nowhere, and the old rules had one way of mentioning it: bin the lot, and hope you read the message on the way past. So somewhere out there, right now, there&amp;rsquo;s a &lt;code&gt;client, _ :=&lt;/code&gt; running along quite happily with a temperature that never happened.&lt;/p&gt;
&lt;p&gt;The receipt was in the bag all along. Whether anyone looks in the bag is a different matter.&lt;/p&gt;</description></item></channel></rss>