﻿<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
  <channel xmlns:media="http://search.yahoo.com/mrss/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
    <title>Dev Leader</title>
    <link>https://www.devleader.ca/</link>
    <description />
    <atom:link href="https://www.devleader.ca/feed" rel="self" type="application/rss+xml" />
    <item>
      <guid isPermaLink="false">704ddcc0-cb10-4ded-931c-11ccd73efd35</guid>
      <link>https://www.devleader.ca/2026/10/08/vercel-eve-sessions-and-streaming-in-c-recover-a-disconnected-client</link>
      <category>Vercel Eve sessions</category>
      <category>Vercel Eve streaming</category>
      <category>C#</category>
      <category>.NET</category>
      <category>durable conversation</category>
      <title>Vercel Eve Sessions and Streaming in C#: Recover a Disconnected Client</title>
      <pubDate>Thu, 08 Oct 2026 13:00:00 Z</pubDate>
      <description><![CDATA[<p><strong>Vercel Eve sessions</strong> separate a durable conversation from the C# process watching it: the client preserves a session identifier and a stream cursor so another handle can resume consumption, according to the <a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/sessions.md" :target="_blank" rel="noopener">tagged session documentation</a>. A disconnected screen does not necessarily mean the agent stopped. Recovery starts by finding the same conversation, not by sending the original prompt again.</p>
<p>Imagine a fictional maintenance assistant explaining an inspection note. Your application displays some output, loses its connection, and restarts. The reader's job is simple: recover what was missed without confusing another message with another read. This article follows that job from initial consumption to a bounded catch-up.</p>
<p>I maintain NexusLabs.Eve, the independent .NET client used in these examples. It is not an official Vercel SDK.</p>
<p>These are statically source-reviewed examples, not newly compiled or executed demonstrations. Each C# block is a separate console <code>Program.cs</code>, using .NET 10, C# 14, nullable enabled, and <code>NexusLabs.Eve</code> version <code>0.1.0-alpha-0014</code>, whose <a href="https://www.nuget.org/packages/NexusLabs.Eve/0.1.0-alpha-0014" :target="_blank" rel="noopener">package metadata</a> identifies the alpha package. The pinned public fixture uses Eve 0.63.0 and Node 24.x (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/test/fixtures/eve-agent/package.json" :target="_blank" rel="noopener">fixture manifest</a>). Replace the reserved example host with an accessible deployment; service credentials and deployment setup are outside this recovery explanation.</p>
<h2 id="vercel-eve-sessions-have-identity-and-read-progress">Vercel Eve Sessions Have Identity and Read Progress</h2>
<p>The client state contains <code>SessionId</code>, the runtime-owned conversation identifier, and <code>StreamIndex</code>, the absolute number of consumed events (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveSessionState.cs" :target="_blank" rel="noopener">state record</a>). Those fields answer different questions. Identity tells you which conversation to address. Progress tells you where this reader should begin.</p>
<p>For Vercel Eve sessions, persisting only identity preserves the ability to find the conversation, but not the reader's place. Persisting only a number is worse: event twelve has no useful meaning without its conversation. Store the pair as one application record, alongside whatever ownership information your application needs.</p>
<p>The same conversation can contain several turns; the pinned runtime describes turns as user-triggered work within a durable session (<a href="https://github.com/vercel/eve/blob/eve%400.63.0/docs/concepts/execution-model-and-durability.mdx" :target="_blank" rel="noopener">execution model</a>). A session cursor is therefore not a turn identifier. Nor is it a receipt proving that one specific user delivery was accepted.</p>
<p>The response's <code>DeliveryId</code> identifies an accepted message delivery and may be absent for initial creation or human-input responses (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveMessageResponse.cs" :target="_blank" rel="noopener">response source</a>). Keep delivery identity, turn identity, and reader progress conceptually separate. A screen reopening is a read operation; it should not invent another delivery.</p>
<p>A useful ownership rule is one consumption coordinator per saved checkpoint. Several screens may receive presentation updates from that coordinator. Letting each screen independently overwrite the same state record risks stale saves and ambiguous progress. This is an application design constraint, not a claim that the transport provides a distributed lock.</p>
<h2 id="consume-a-turn-before-saving-its-final-cursor">Consume a Turn Before Saving Its Final Cursor</h2>
<p><code>SendAsync</code> returns an accepted response whose event stream has one owner; <code>GetOutcomeAsync</code> aggregates that stream, while direct enumeration is an alternative, not a second pass (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveMessageResponse.cs" :target="_blank" rel="noopener">response implementation</a>). Decide which path owns a response before handing it to other application code.</p>
<p>For a small answer, aggregation is the simpler learning path. It gives you an explicit outcome without building a live display reducer. The tradeoff is that it retains the consumed events in memory, as the <a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveMessageResponse.cs" :target="_blank" rel="noopener">aggregation implementation</a> shows. It is not a constant-memory log processor.</p>
<p>This first example creates a conversation, saves the accepted identity, consumes its answer, and then saves the advanced state using the actual public record API (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveSession.cs" :target="_blank" rel="noopener">session source</a>, <a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveSessionState.cs" :target="_blank" rel="noopener">state source</a>).</p>
<div class="position-relative"><button class="btn btn-sm position-absolute top-0 end-0 m-2 border border-primary text-primary copy-btn"
        type="button"
        aria-label="Copy code"
        onclick="navigator.clipboard.writeText(this.parentElement.querySelector('pre code').textContent)">
        <i class="copy" aria-hidden="true"></i>
</button>
<pre><code class="language-csharp">using System;
using System.IO;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using NexusLabs.Eve;

using HttpClient transport = new();
EveClient client = new(
    transport, new EveClientOptions(&quot;https://agent.example.com&quot;));
EveSession session = client.CreateSession();
EveMessageResponse response = await session.SendAsync(
    &quot;Explain the fictional inspection note for pump-demo-01.&quot;,
    CancellationToken.None);

await File.WriteAllTextAsync(
    &quot;eve-session.json&quot;, JsonSerializer.Serialize(session.State));

EveTurnOutcome outcome = await response.GetOutcomeAsync(CancellationToken.None);

await File.WriteAllTextAsync(
    &quot;eve-session.json&quot;, JsonSerializer.Serialize(session.State));

if (outcome.Status == EveTurnStatus.Failed)
{
    Console.Error.WriteLine($&quot;Session {outcome.SessionId} failed.&quot;);
    foreach (EveStreamEvent streamEvent in outcome.Events)
    {
        if (streamEvent.Kind == EveStreamEventKind.SessionFailed)
        {
            Console.Error.WriteLine(streamEvent.Data.GetRawText());
        }
    }

    Environment.ExitCode = 1;
    return;
}

Console.WriteLine($&quot;Observed status: {outcome.Status}&quot;);
Console.WriteLine(outcome.Message ?? &quot;No completed message was observed.&quot;);
</code></pre>
</div>
<p>The JSON file is the persistence contract for this illustration. There is no undefined <code>SaveAsync</code> abstraction. Both programs below use the same file in their working directory. File errors surface rather than being disguised as successful recovery.</p>
<p>The first save reduces the window in which accepted identity exists only in memory. It does not remove that window: a process can stop between server acceptance and local persistence. The second save records read progress after aggregation. These are two different checkpoints with two different purposes.</p>
<p>Think through an interruption at each point. Before acceptance, your application may not know whether the server received anything. After acceptance but before the first save, the returned identity can be lost with the process. After that save, another process can at least locate the conversation. After consumption but before the final save, the older cursor may cause already observed events to be read again. This timeline is a conceptual analysis of the example's ordering, not a guarantee that every failure has an automatic remedy.</p>
<p>For Vercel Eve sessions, that last case is often acceptable for a diagnostic viewer: redisplaying historical information is inconvenient but does not itself change equipment records. It becomes a different problem if displaying an event also sends a notification or writes a business record. Keep the reader's checkpoint separate from those effects so a harmless replay does not acquire hidden consequences.</p>
<p>For production Vercel Eve sessions, ordinary file replacement is not a complete durable-store design. Protect access, use an appropriate crash-safe write strategy, and coordinate competing writers. A truncated file must produce a recovery error, not silently become a new conversation. The example exposes these responsibilities instead of pretending a serialization call solves them.</p>
<h2 id="resume-vercel-eve-sessions-without-resending-the-prompt">Resume Vercel Eve Sessions Without Resending the Prompt</h2>
<p><code>CreateSession(savedState)</code> restores a handle, whereas <code>AttachSession(sessionId, streamIndex)</code> constructs one from explicit identity and progress (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveClient.cs" :target="_blank" rel="noopener">client factories</a>). Neither method needs to send a new user message merely to recover a reader.</p>
<p>Use the saved-state route when your application stored the full checkpoint. The following program reads that checkpoint and follows events until its local time budget expires; <code>StreamAsync</code> reads the conversation rather than submitting another turn (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveSession.cs" :target="_blank" rel="noopener">session streaming API</a>).</p>
<div class="position-relative"><button class="btn btn-sm position-absolute top-0 end-0 m-2 border border-primary text-primary copy-btn"
        type="button"
        aria-label="Copy code"
        onclick="navigator.clipboard.writeText(this.parentElement.querySelector('pre code').textContent)">
        <i class="copy" aria-hidden="true"></i>
</button>
<pre><code class="language-csharp">using System;
using System.IO;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using NexusLabs.Eve;

EveSessionState saved = JsonSerializer.Deserialize&lt;EveSessionState&gt;(
    await File.ReadAllTextAsync(&quot;eve-session.json&quot;))
    ?? throw new InvalidDataException(&quot;Missing Eve session state.&quot;);

if (string.IsNullOrWhiteSpace(saved.SessionId))
{
    throw new InvalidDataException(&quot;The checkpoint has no remote session ID.&quot;);
}

using HttpClient transport = new();
EveClient client = new(
    transport, new EveClientOptions(&quot;https://agent.example.com&quot;));
EveSession resumed = client.CreateSession(saved);
using CancellationTokenSource readBudget = new(TimeSpan.FromSeconds(30));

try
{
    await foreach (EveStreamEvent streamEvent in
        resumed.StreamAsync(readBudget.Token))
    {
        Console.WriteLine($&quot;{streamEvent.Type}: {streamEvent.Data.GetRawText()}&quot;);
        if (streamEvent.Kind == EveStreamEventKind.SessionFailed)
        {
            Console.Error.WriteLine(&quot;The durable session reported failure.&quot;);
            Environment.ExitCode = 1;
        }
    }
}
catch (OperationCanceledException) when (readBudget.IsCancellationRequested)
{
    Console.Error.WriteLine(&quot;Local reading stopped; server work was not cancelled.&quot;);
}

await File.WriteAllTextAsync(
    &quot;eve-session.json&quot;, JsonSerializer.Serialize(resumed.State));
</code></pre>
</div>
<p>This deliberately displays raw diagnostic events rather than reconstructing a chat interface. Use synthetic data when doing that; production payloads may contain information that should not enter logs. The catch is limited to this caller's cancellation. HTTP failures, malformed protocol responses, and persistence failures remain visible.</p>
<p>The restored checkpoint belongs to a particular observer, not necessarily every observer of the same conversation. If an operator console and a background audit reader have different responsibilities, give each an explicit progress record rather than letting the faster reader decide what the slower one has seen. This is conceptual ownership guidance. The examples assume a single process writes the demonstration file and do not implement multi-reader coordination.</p>
<p>Vercel Eve sessions also require an honest recovery message. &quot;Reading stopped&quot; describes the local budget expiring. &quot;The session failed&quot; describes a durable event. &quot;Checkpoint could not be loaded&quot; describes local persistence. Keeping those statements distinct helps the next caller decide whether to retry reading, investigate the saved record, or inspect the agent's failure, rather than starting a replacement conversation blindly.</p>
<p>There is an important tradeoff for Vercel Eve sessions here. Following supports live progress, but it can wait for future work and needs a caller lifetime. A bounded backlog read gives control back after a fixed historical target. Choose according to whether the screen is observing a live conversation or rebuilding its initial view.</p>
<p>Do not send &quot;continue&quot; just because the connection dropped. If work is still active, a new message changes the conversation. The pinned client documents steering as the server default for overlapping sends, with <code>EveTurnPolicy.Queue</code> available when explicit queuing is intended (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/sessions.md" :target="_blank" rel="noopener">delivery policy</a>). Recovery reading and intentional follow-up messaging should remain separate actions.</p>
<h2 id="catch-up-to-a-fixed-durable-tail">Catch Up to a Fixed Durable Tail</h2>
<p>Setting <code>Follow = false</code> reads through the durable tail observed when the stream opens, then finishes instead of waiting for future events (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/streaming.md" :target="_blank" rel="noopener">bounded streaming contract</a>). This is a snapshot boundary for consumption, not a request to stop server execution.</p>
<p>Here is the alternative explicit-attachment path for Vercel Eve sessions. Supply a trusted session identifier and a nonnegative cursor as the two command-line arguments; <code>AttachSession</code> validates the handle's cursor contract (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveClient.cs" :target="_blank" rel="noopener">factory source</a>).</p>
<div class="position-relative"><button class="btn btn-sm position-absolute top-0 end-0 m-2 border border-primary text-primary copy-btn"
        type="button"
        aria-label="Copy code"
        onclick="navigator.clipboard.writeText(this.parentElement.querySelector('pre code').textContent)">
        <i class="copy" aria-hidden="true"></i>
</button>
<pre><code class="language-csharp">using System;
using System.IO;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using NexusLabs.Eve;

if (args.Length != 2 || string.IsNullOrWhiteSpace(args[0])
    || !int.TryParse(args[1], out int cursor) || cursor &lt; 0)
{
    throw new ArgumentException(&quot;Supply a session ID and a nonnegative cursor.&quot;);
}

using HttpClient transport = new();
EveClient client = new(
    transport, new EveClientOptions(&quot;https://agent.example.com&quot;));
EveSession session = client.AttachSession(args[0], streamIndex: cursor);

await foreach (EveStreamEvent streamEvent in session.StreamAsync(
    new EveStreamOptions { Follow = false },
    CancellationToken.None))
{
    Console.WriteLine($&quot;{streamEvent.Type}: {streamEvent.Data.GetRawText()}&quot;);
    if (streamEvent.Kind == EveStreamEventKind.SessionFailed)
    {
        Console.Error.WriteLine(&quot;Catch-up observed a failed session.&quot;);
        Environment.ExitCode = 1;
    }
}

await File.WriteAllTextAsync(
    &quot;eve-session.json&quot;, JsonSerializer.Serialize(session.State));
Console.WriteLine($&quot;Saved cursor: {session.State.StreamIndex}&quot;);
</code></pre>
</div>
<p>The first bounded request negotiates <code>x-eve-stream-tail-index</code>; reconnects keep that initial upper bound rather than chasing a moving tail (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/streaming.md" :target="_blank" rel="noopener">bounded read documentation</a>). Events recorded afterward belong to another read. That gives an application a meaningful point at which backlog reconstruction is complete.</p>
<p>A bounded read still needs a valid protocol response. Missing or malformed tail metadata throws <code>EveProtocolException</code>, and negative tail-relative starts cannot be combined with <code>Follow = false</code> (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/streaming.md" :target="_blank" rel="noopener">bounded read constraints</a>). &quot;Bounded&quot; describes the event target, not a guaranteed wall-clock completion time.</p>
<p>For Vercel Eve sessions in an application screen, the resulting checkpoint can mark &quot;caught up through the observed tail.&quot; It should not be labeled &quot;agent finished&quot; merely because the reader returned. Conversely, an empty catch-up may simply mean this reader was already current. Neither observation proves that a new user request succeeded or that a business action was performed.</p>
<h2 id="reconnection-is-transport-recovery-not-an-application-transaction">Reconnection Is Transport Recovery, Not an Application Transaction</h2>
<p>The client already reconnects from the next absolute event index, using its stream policy and a fifteen-second idle read deadline; attached reads have a finite idle reconnect budget, while active response consumption follows its turn until a boundary or caller cancellation (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/streaming.md" :target="_blank" rel="noopener">reconnection documentation</a>). Do not replace that mechanism with an unbounded custom retry loop.</p>
<p>For Vercel Eve sessions, distinguish three layers of progress: bytes received, complete events decoded, and application effects committed. The reconnect cursor follows fully consumed events, not arbitrary byte counts (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/streaming.md" :target="_blank" rel="noopener">stream recovery contract</a>). An interrupted partial NDJSON line is not a complete event checkpoint and can be encountered again after reconnection.</p>
<p>The public session source also shows a crucial implementation detail: direct <code>StreamAsync</code> advances handle state before yielding an event, whereas active response state merges its counted progress when enumeration unwinds (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveSession.cs" :target="_blank" rel="noopener">cursor implementation</a>). Therefore &quot;consumed&quot; must not be interpreted as &quot;my database transaction committed.&quot; Saving <code>session.State</code> inside a handler is not automatically an acknowledgement protocol.</p>
<p>Conceptually, a handler could write a notification and crash before saving its checkpoint. Replaying may duplicate that notification. Saving progress first creates the opposite risk: a crash may skip an effect that never completed. A business operation record, transaction boundary, or idempotency key must solve that problem at the application boundary.</p>
<p>This is also why adding another generic <a href="https://www.devleader.ca/2026/06/29/httpclient-resilience-in-net-10-timeout-retry-and-circuit-breaker-with-microsoftextensionshttpresilience">HttpClient resilience policy</a> deserves care. Retrying a read and repeating a message submission are not interchangeable. Decide which layer owns recovery before multiplying attempts.</p>
<p>Persisted stream events have stable <code>Metadata.Id</code> values when the protocol supports them, but older events may have no ID; retried execution emits new events rather than replaying the same identity (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/streaming.md" :target="_blank" rel="noopener">event identity documentation</a>). Event deduplication cannot establish exactly-once external effects.</p>
<p>The supplied <code>EveStreamEventDeduplicator</code> remembers an unbounded set so rewinds remain recognizable (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/streaming.md" :target="_blank" rel="noopener">deduplicator contract</a>). That has a memory cost over long-lived consumption. A bounded set saves memory but forgets older identities; a persisted effect ledger needs its own retention policy. None should be presented as a free, permanent guarantee.</p>
<h2 id="deltas-and-waiting-events-need-context">Deltas and Waiting Events Need Context</h2>
<p>The pinned stream carries NDJSON events, and text append payloads contain deltas rather than full accumulated state (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveProtocol.cs" :target="_blank" rel="noopener">protocol source</a>, <a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/streaming.md" :target="_blank" rel="noopener">delta contract</a>). General <a href="https://www.devleader.ca/2026/06/30/httpclient-streaming-in-c-httpcompletionoption-readasstreamasync-and-serversent-events">HttpClient streaming concepts</a> help explain incremental reading, but an SSE parser is not the Eve wire contract.</p>
<p>For Vercel Eve sessions, reopening midway through a message can therefore require presentation reconstruction. A suffix of append events is not a complete answer. Keep a compatible display checkpoint or rebuild from an appropriate earlier position, understanding the additional read and memory costs.</p>
<p>The pinned documentation requires contiguous text deltas with matching UTF-16 offsets and treats validated tool input as authoritative rather than an unfinished input preview (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/streaming.md" :target="_blank" rel="noopener">streaming guidance</a>). These examples avoid inventing a partial UI reducer. A raw event log and a finished chat bubble are different outputs.</p>
<p>Waiting also needs context: callback authorization may produce an interim <code>session.waiting</code> while the active response remains attached for completion (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/streaming.md" :target="_blank" rel="noopener">authorization parking</a>). This matters even if authentication is implemented elsewhere.</p>
<p>Do not manually break an active response loop on <code>IsCurrentTurnBoundary</code>: the property is context-free and remains true for that interim waiting event, while the response applies pending-authorization context (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/streaming.md" :target="_blank" rel="noopener">boundary warning</a>). Let the existing response owner decide when its stream ends.</p>
<h2 id="local-cancellation-and-remote-cancellation-are-different-decisions">Local Cancellation and Remote Cancellation Are Different Decisions</h2>
<p>Cancelling local consumption detaches the caller without itself stopping the durable turn (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/errors.md" :target="_blank" rel="noopener">error and cancellation contract</a>). A closed page and an explicit stop request therefore express different intentions for Vercel Eve sessions.</p>
<p>The final example intentionally requests remote cancellation, rather than simulating a disconnected client. Start the one outcome consumer first; response-level <code>CancelAsync</code> waits for its observed turn identity and sends a guard for that exact turn (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveMessageResponse.cs" :target="_blank" rel="noopener">cancellation implementation</a>).</p>
<div class="position-relative"><button class="btn btn-sm position-absolute top-0 end-0 m-2 border border-primary text-primary copy-btn"
        type="button"
        aria-label="Copy code"
        onclick="navigator.clipboard.writeText(this.parentElement.querySelector('pre code').textContent)">
        <i class="copy" aria-hidden="true"></i>
</button>
<pre><code class="language-csharp">using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using NexusLabs.Eve;

using HttpClient transport = new();
EveClient client = new(
    transport, new EveClientOptions(&quot;https://agent.example.com&quot;));
EveSession session = client.CreateSession();
EveMessageResponse response = await session.SendAsync(
    &quot;Explain the fictional pump inspection in detail.&quot;,
    CancellationToken.None);

Task&lt;EveTurnOutcome&gt; outcomeTask =
    response.GetOutcomeAsync(CancellationToken.None);

EveCancellationOutcome cancellation =
    await response.CancelAsync(CancellationToken.None);
EveTurnOutcome outcome = await outcomeTask;

Console.WriteLine(cancellation);
Console.WriteLine($&quot;Observed boundary status: {outcome.Status}&quot;);

if (outcome.Status == EveTurnStatus.Failed)
{
    Console.Error.WriteLine(&quot;The session failed while cancellation was observed.&quot;);
    Environment.ExitCode = 1;
}
</code></pre>
</div>
<p>Cancellation is cooperative, and a settled response returns <code>NoActiveTurn</code> without another cancellation request (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/streaming.md" :target="_blank" rel="noopener">response cancellation contract</a>). The example does not promise that cancellation wins a race with completion. It prints the observed result and keeps consuming through settlement.</p>
<p>When only an attached handle and an observed turn ID remain, the guarded <code>session.CancelAsync(turnId, cancellationToken)</code> overload is available (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveSession.cs" :target="_blank" rel="noopener">session cancellation API</a>). Do not substitute the session cursor for that guard or reuse a cancelled local-reading token when intending to send a new remote request.</p>
<p>Finally, <code>EveTurnStatus.Failed</code> represents a streamed failure, while unsuccessful HTTP calls throw <code>EveClientException</code> and malformed successful responses throw <code>EveProtocolException</code> (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/errors.md" :target="_blank" rel="noopener">error taxonomy</a>). Caller cancellation belongs to another branch. Reporting all four as &quot;no answer&quot; hides the recovery decision.</p>
<p>For operational visibility, <a href="https://www.devleader.ca/2026/07/05/serilog-in-net-complete-guide-to-structured-logging">structured logging in .NET</a> can record safe identifiers and observed failure categories. <a href="https://www.devleader.ca/2026/07/30/opentelemetry-httpclient-instrumentation-in-net-tracing-outbound-http-calls">Outbound HTTP tracing</a> adds transport context, but one request span is not the entire durable conversation.</p>
<h2 id="frequently-asked-questions">Frequently Asked Questions</h2>
<p>These questions focus on recovering a reader, not administering the runtime or changing its context.</p>
<h3 id="does-reconnecting-start-another-turn">Does reconnecting start another turn?</h3>
<p>Reading with <code>StreamAsync</code> attaches to session events, whereas <code>SendAsync</code> submits a message (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveSession.cs" :target="_blank" rel="noopener">session API</a>). Recover Vercel Eve sessions by reading first, not automatically resending.</p>
<h3 id="can-i-aggregate-a-response-after-enumerating-it">Can I aggregate a response after enumerating it?</h3>
<p>No. The response enforces single-use consumption and throws if consumed again (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveMessageResponse.cs" :target="_blank" rel="noopener">response source</a>). Choose aggregation or direct enumeration for that response.</p>
<h3 id="does-a-saved-cursor-prove-my-handler-committed">Does a saved cursor prove my handler committed?</h3>
<p>No. Direct session enumeration advances local state before yielding the event (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveSession.cs" :target="_blank" rel="noopener">cursor source</a>). Application effects need a separate commit and replay strategy.</p>
<h3 id="does-bounded-catch-up-stop-the-agent">Does bounded catch-up stop the agent?</h3>
<p>No. <code>Follow = false</code> fixes the read's tail boundary, not the server's execution lifetime (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/streaming.md" :target="_blank" rel="noopener">bounded reads</a>). Later events can be consumed separately.</p>
<h3 id="should-every-waiting-event-end-my-active-response-loop">Should every waiting event end my active response loop?</h3>
<p>No. Callback authorization can park an active response at an interim waiting event, and the client applies that context (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/streaming.md" :target="_blank" rel="noopener">waiting guidance</a>). A manual boundary break loses that behavior.</p>
<h3 id="does-cancelling-the-reader-cancel-the-server-turn">Does cancelling the reader cancel the server turn?</h3>
<p>No. Local cancellation detaches consumption; cooperative remote cancellation is separate (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/errors.md" :target="_blank" rel="noopener">cancellation distinction</a>). Preserve this distinction in both UI language and failure reporting.</p>
<h2 id="keep-the-conversation-and-the-reader-separate">Keep the Conversation and the Reader Separate</h2>
<p>Recovering Vercel Eve sessions is easier when identity, transport progress, presentation state, and business effects remain separate. Restore the conversation handle. Let the library recover transport interruptions. Choose live following or fixed-tail catch-up. Treat a cursor as read progress, not a guarantee about application transactions.</p>
<p>That mental model explains the central distinction: a reader can stop while durable work continues. Returning to that work should recover observation before changing the conversation.</p>
]]></description>
      <content:encoded><![CDATA[<p><strong>Vercel Eve sessions</strong> separate a durable conversation from the C# process watching it: the client preserves a session identifier and a stream cursor so another handle can resume consumption, according to the <a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/sessions.md" :target="_blank" rel="noopener">tagged session documentation</a>. A disconnected screen does not necessarily mean the agent stopped. Recovery starts by finding the same conversation, not by sending the original prompt again.</p>
<p>Imagine a fictional maintenance assistant explaining an inspection note. Your application displays some output, loses its connection, and restarts. The reader's job is simple: recover what was missed without confusing another message with another read. This article follows that job from initial consumption to a bounded catch-up.</p>
<p>I maintain NexusLabs.Eve, the independent .NET client used in these examples. It is not an official Vercel SDK.</p>
<p>These are statically source-reviewed examples, not newly compiled or executed demonstrations. Each C# block is a separate console <code>Program.cs</code>, using .NET 10, C# 14, nullable enabled, and <code>NexusLabs.Eve</code> version <code>0.1.0-alpha-0014</code>, whose <a href="https://www.nuget.org/packages/NexusLabs.Eve/0.1.0-alpha-0014" :target="_blank" rel="noopener">package metadata</a> identifies the alpha package. The pinned public fixture uses Eve 0.63.0 and Node 24.x (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/test/fixtures/eve-agent/package.json" :target="_blank" rel="noopener">fixture manifest</a>). Replace the reserved example host with an accessible deployment; service credentials and deployment setup are outside this recovery explanation.</p>
<h2 id="vercel-eve-sessions-have-identity-and-read-progress">Vercel Eve Sessions Have Identity and Read Progress</h2>
<p>The client state contains <code>SessionId</code>, the runtime-owned conversation identifier, and <code>StreamIndex</code>, the absolute number of consumed events (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveSessionState.cs" :target="_blank" rel="noopener">state record</a>). Those fields answer different questions. Identity tells you which conversation to address. Progress tells you where this reader should begin.</p>
<p>For Vercel Eve sessions, persisting only identity preserves the ability to find the conversation, but not the reader's place. Persisting only a number is worse: event twelve has no useful meaning without its conversation. Store the pair as one application record, alongside whatever ownership information your application needs.</p>
<p>The same conversation can contain several turns; the pinned runtime describes turns as user-triggered work within a durable session (<a href="https://github.com/vercel/eve/blob/eve%400.63.0/docs/concepts/execution-model-and-durability.mdx" :target="_blank" rel="noopener">execution model</a>). A session cursor is therefore not a turn identifier. Nor is it a receipt proving that one specific user delivery was accepted.</p>
<p>The response's <code>DeliveryId</code> identifies an accepted message delivery and may be absent for initial creation or human-input responses (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveMessageResponse.cs" :target="_blank" rel="noopener">response source</a>). Keep delivery identity, turn identity, and reader progress conceptually separate. A screen reopening is a read operation; it should not invent another delivery.</p>
<p>A useful ownership rule is one consumption coordinator per saved checkpoint. Several screens may receive presentation updates from that coordinator. Letting each screen independently overwrite the same state record risks stale saves and ambiguous progress. This is an application design constraint, not a claim that the transport provides a distributed lock.</p>
<h2 id="consume-a-turn-before-saving-its-final-cursor">Consume a Turn Before Saving Its Final Cursor</h2>
<p><code>SendAsync</code> returns an accepted response whose event stream has one owner; <code>GetOutcomeAsync</code> aggregates that stream, while direct enumeration is an alternative, not a second pass (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveMessageResponse.cs" :target="_blank" rel="noopener">response implementation</a>). Decide which path owns a response before handing it to other application code.</p>
<p>For a small answer, aggregation is the simpler learning path. It gives you an explicit outcome without building a live display reducer. The tradeoff is that it retains the consumed events in memory, as the <a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveMessageResponse.cs" :target="_blank" rel="noopener">aggregation implementation</a> shows. It is not a constant-memory log processor.</p>
<p>This first example creates a conversation, saves the accepted identity, consumes its answer, and then saves the advanced state using the actual public record API (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveSession.cs" :target="_blank" rel="noopener">session source</a>, <a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveSessionState.cs" :target="_blank" rel="noopener">state source</a>).</p>
<div class="position-relative"><button class="btn btn-sm position-absolute top-0 end-0 m-2 border border-primary text-primary copy-btn"
        type="button"
        aria-label="Copy code"
        onclick="navigator.clipboard.writeText(this.parentElement.querySelector('pre code').textContent)">
        <i class="copy" aria-hidden="true"></i>
</button>
<pre><code class="language-csharp">using System;
using System.IO;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using NexusLabs.Eve;

using HttpClient transport = new();
EveClient client = new(
    transport, new EveClientOptions(&quot;https://agent.example.com&quot;));
EveSession session = client.CreateSession();
EveMessageResponse response = await session.SendAsync(
    &quot;Explain the fictional inspection note for pump-demo-01.&quot;,
    CancellationToken.None);

await File.WriteAllTextAsync(
    &quot;eve-session.json&quot;, JsonSerializer.Serialize(session.State));

EveTurnOutcome outcome = await response.GetOutcomeAsync(CancellationToken.None);

await File.WriteAllTextAsync(
    &quot;eve-session.json&quot;, JsonSerializer.Serialize(session.State));

if (outcome.Status == EveTurnStatus.Failed)
{
    Console.Error.WriteLine($&quot;Session {outcome.SessionId} failed.&quot;);
    foreach (EveStreamEvent streamEvent in outcome.Events)
    {
        if (streamEvent.Kind == EveStreamEventKind.SessionFailed)
        {
            Console.Error.WriteLine(streamEvent.Data.GetRawText());
        }
    }

    Environment.ExitCode = 1;
    return;
}

Console.WriteLine($&quot;Observed status: {outcome.Status}&quot;);
Console.WriteLine(outcome.Message ?? &quot;No completed message was observed.&quot;);
</code></pre>
</div>
<p>The JSON file is the persistence contract for this illustration. There is no undefined <code>SaveAsync</code> abstraction. Both programs below use the same file in their working directory. File errors surface rather than being disguised as successful recovery.</p>
<p>The first save reduces the window in which accepted identity exists only in memory. It does not remove that window: a process can stop between server acceptance and local persistence. The second save records read progress after aggregation. These are two different checkpoints with two different purposes.</p>
<p>Think through an interruption at each point. Before acceptance, your application may not know whether the server received anything. After acceptance but before the first save, the returned identity can be lost with the process. After that save, another process can at least locate the conversation. After consumption but before the final save, the older cursor may cause already observed events to be read again. This timeline is a conceptual analysis of the example's ordering, not a guarantee that every failure has an automatic remedy.</p>
<p>For Vercel Eve sessions, that last case is often acceptable for a diagnostic viewer: redisplaying historical information is inconvenient but does not itself change equipment records. It becomes a different problem if displaying an event also sends a notification or writes a business record. Keep the reader's checkpoint separate from those effects so a harmless replay does not acquire hidden consequences.</p>
<p>For production Vercel Eve sessions, ordinary file replacement is not a complete durable-store design. Protect access, use an appropriate crash-safe write strategy, and coordinate competing writers. A truncated file must produce a recovery error, not silently become a new conversation. The example exposes these responsibilities instead of pretending a serialization call solves them.</p>
<h2 id="resume-vercel-eve-sessions-without-resending-the-prompt">Resume Vercel Eve Sessions Without Resending the Prompt</h2>
<p><code>CreateSession(savedState)</code> restores a handle, whereas <code>AttachSession(sessionId, streamIndex)</code> constructs one from explicit identity and progress (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveClient.cs" :target="_blank" rel="noopener">client factories</a>). Neither method needs to send a new user message merely to recover a reader.</p>
<p>Use the saved-state route when your application stored the full checkpoint. The following program reads that checkpoint and follows events until its local time budget expires; <code>StreamAsync</code> reads the conversation rather than submitting another turn (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveSession.cs" :target="_blank" rel="noopener">session streaming API</a>).</p>
<div class="position-relative"><button class="btn btn-sm position-absolute top-0 end-0 m-2 border border-primary text-primary copy-btn"
        type="button"
        aria-label="Copy code"
        onclick="navigator.clipboard.writeText(this.parentElement.querySelector('pre code').textContent)">
        <i class="copy" aria-hidden="true"></i>
</button>
<pre><code class="language-csharp">using System;
using System.IO;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using NexusLabs.Eve;

EveSessionState saved = JsonSerializer.Deserialize&lt;EveSessionState&gt;(
    await File.ReadAllTextAsync(&quot;eve-session.json&quot;))
    ?? throw new InvalidDataException(&quot;Missing Eve session state.&quot;);

if (string.IsNullOrWhiteSpace(saved.SessionId))
{
    throw new InvalidDataException(&quot;The checkpoint has no remote session ID.&quot;);
}

using HttpClient transport = new();
EveClient client = new(
    transport, new EveClientOptions(&quot;https://agent.example.com&quot;));
EveSession resumed = client.CreateSession(saved);
using CancellationTokenSource readBudget = new(TimeSpan.FromSeconds(30));

try
{
    await foreach (EveStreamEvent streamEvent in
        resumed.StreamAsync(readBudget.Token))
    {
        Console.WriteLine($&quot;{streamEvent.Type}: {streamEvent.Data.GetRawText()}&quot;);
        if (streamEvent.Kind == EveStreamEventKind.SessionFailed)
        {
            Console.Error.WriteLine(&quot;The durable session reported failure.&quot;);
            Environment.ExitCode = 1;
        }
    }
}
catch (OperationCanceledException) when (readBudget.IsCancellationRequested)
{
    Console.Error.WriteLine(&quot;Local reading stopped; server work was not cancelled.&quot;);
}

await File.WriteAllTextAsync(
    &quot;eve-session.json&quot;, JsonSerializer.Serialize(resumed.State));
</code></pre>
</div>
<p>This deliberately displays raw diagnostic events rather than reconstructing a chat interface. Use synthetic data when doing that; production payloads may contain information that should not enter logs. The catch is limited to this caller's cancellation. HTTP failures, malformed protocol responses, and persistence failures remain visible.</p>
<p>The restored checkpoint belongs to a particular observer, not necessarily every observer of the same conversation. If an operator console and a background audit reader have different responsibilities, give each an explicit progress record rather than letting the faster reader decide what the slower one has seen. This is conceptual ownership guidance. The examples assume a single process writes the demonstration file and do not implement multi-reader coordination.</p>
<p>Vercel Eve sessions also require an honest recovery message. &quot;Reading stopped&quot; describes the local budget expiring. &quot;The session failed&quot; describes a durable event. &quot;Checkpoint could not be loaded&quot; describes local persistence. Keeping those statements distinct helps the next caller decide whether to retry reading, investigate the saved record, or inspect the agent's failure, rather than starting a replacement conversation blindly.</p>
<p>There is an important tradeoff for Vercel Eve sessions here. Following supports live progress, but it can wait for future work and needs a caller lifetime. A bounded backlog read gives control back after a fixed historical target. Choose according to whether the screen is observing a live conversation or rebuilding its initial view.</p>
<p>Do not send &quot;continue&quot; just because the connection dropped. If work is still active, a new message changes the conversation. The pinned client documents steering as the server default for overlapping sends, with <code>EveTurnPolicy.Queue</code> available when explicit queuing is intended (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/sessions.md" :target="_blank" rel="noopener">delivery policy</a>). Recovery reading and intentional follow-up messaging should remain separate actions.</p>
<h2 id="catch-up-to-a-fixed-durable-tail">Catch Up to a Fixed Durable Tail</h2>
<p>Setting <code>Follow = false</code> reads through the durable tail observed when the stream opens, then finishes instead of waiting for future events (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/streaming.md" :target="_blank" rel="noopener">bounded streaming contract</a>). This is a snapshot boundary for consumption, not a request to stop server execution.</p>
<p>Here is the alternative explicit-attachment path for Vercel Eve sessions. Supply a trusted session identifier and a nonnegative cursor as the two command-line arguments; <code>AttachSession</code> validates the handle's cursor contract (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveClient.cs" :target="_blank" rel="noopener">factory source</a>).</p>
<div class="position-relative"><button class="btn btn-sm position-absolute top-0 end-0 m-2 border border-primary text-primary copy-btn"
        type="button"
        aria-label="Copy code"
        onclick="navigator.clipboard.writeText(this.parentElement.querySelector('pre code').textContent)">
        <i class="copy" aria-hidden="true"></i>
</button>
<pre><code class="language-csharp">using System;
using System.IO;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using NexusLabs.Eve;

if (args.Length != 2 || string.IsNullOrWhiteSpace(args[0])
    || !int.TryParse(args[1], out int cursor) || cursor &lt; 0)
{
    throw new ArgumentException(&quot;Supply a session ID and a nonnegative cursor.&quot;);
}

using HttpClient transport = new();
EveClient client = new(
    transport, new EveClientOptions(&quot;https://agent.example.com&quot;));
EveSession session = client.AttachSession(args[0], streamIndex: cursor);

await foreach (EveStreamEvent streamEvent in session.StreamAsync(
    new EveStreamOptions { Follow = false },
    CancellationToken.None))
{
    Console.WriteLine($&quot;{streamEvent.Type}: {streamEvent.Data.GetRawText()}&quot;);
    if (streamEvent.Kind == EveStreamEventKind.SessionFailed)
    {
        Console.Error.WriteLine(&quot;Catch-up observed a failed session.&quot;);
        Environment.ExitCode = 1;
    }
}

await File.WriteAllTextAsync(
    &quot;eve-session.json&quot;, JsonSerializer.Serialize(session.State));
Console.WriteLine($&quot;Saved cursor: {session.State.StreamIndex}&quot;);
</code></pre>
</div>
<p>The first bounded request negotiates <code>x-eve-stream-tail-index</code>; reconnects keep that initial upper bound rather than chasing a moving tail (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/streaming.md" :target="_blank" rel="noopener">bounded read documentation</a>). Events recorded afterward belong to another read. That gives an application a meaningful point at which backlog reconstruction is complete.</p>
<p>A bounded read still needs a valid protocol response. Missing or malformed tail metadata throws <code>EveProtocolException</code>, and negative tail-relative starts cannot be combined with <code>Follow = false</code> (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/streaming.md" :target="_blank" rel="noopener">bounded read constraints</a>). &quot;Bounded&quot; describes the event target, not a guaranteed wall-clock completion time.</p>
<p>For Vercel Eve sessions in an application screen, the resulting checkpoint can mark &quot;caught up through the observed tail.&quot; It should not be labeled &quot;agent finished&quot; merely because the reader returned. Conversely, an empty catch-up may simply mean this reader was already current. Neither observation proves that a new user request succeeded or that a business action was performed.</p>
<h2 id="reconnection-is-transport-recovery-not-an-application-transaction">Reconnection Is Transport Recovery, Not an Application Transaction</h2>
<p>The client already reconnects from the next absolute event index, using its stream policy and a fifteen-second idle read deadline; attached reads have a finite idle reconnect budget, while active response consumption follows its turn until a boundary or caller cancellation (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/streaming.md" :target="_blank" rel="noopener">reconnection documentation</a>). Do not replace that mechanism with an unbounded custom retry loop.</p>
<p>For Vercel Eve sessions, distinguish three layers of progress: bytes received, complete events decoded, and application effects committed. The reconnect cursor follows fully consumed events, not arbitrary byte counts (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/streaming.md" :target="_blank" rel="noopener">stream recovery contract</a>). An interrupted partial NDJSON line is not a complete event checkpoint and can be encountered again after reconnection.</p>
<p>The public session source also shows a crucial implementation detail: direct <code>StreamAsync</code> advances handle state before yielding an event, whereas active response state merges its counted progress when enumeration unwinds (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveSession.cs" :target="_blank" rel="noopener">cursor implementation</a>). Therefore &quot;consumed&quot; must not be interpreted as &quot;my database transaction committed.&quot; Saving <code>session.State</code> inside a handler is not automatically an acknowledgement protocol.</p>
<p>Conceptually, a handler could write a notification and crash before saving its checkpoint. Replaying may duplicate that notification. Saving progress first creates the opposite risk: a crash may skip an effect that never completed. A business operation record, transaction boundary, or idempotency key must solve that problem at the application boundary.</p>
<p>This is also why adding another generic <a href="https://www.devleader.ca/2026/06/29/httpclient-resilience-in-net-10-timeout-retry-and-circuit-breaker-with-microsoftextensionshttpresilience">HttpClient resilience policy</a> deserves care. Retrying a read and repeating a message submission are not interchangeable. Decide which layer owns recovery before multiplying attempts.</p>
<p>Persisted stream events have stable <code>Metadata.Id</code> values when the protocol supports them, but older events may have no ID; retried execution emits new events rather than replaying the same identity (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/streaming.md" :target="_blank" rel="noopener">event identity documentation</a>). Event deduplication cannot establish exactly-once external effects.</p>
<p>The supplied <code>EveStreamEventDeduplicator</code> remembers an unbounded set so rewinds remain recognizable (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/streaming.md" :target="_blank" rel="noopener">deduplicator contract</a>). That has a memory cost over long-lived consumption. A bounded set saves memory but forgets older identities; a persisted effect ledger needs its own retention policy. None should be presented as a free, permanent guarantee.</p>
<h2 id="deltas-and-waiting-events-need-context">Deltas and Waiting Events Need Context</h2>
<p>The pinned stream carries NDJSON events, and text append payloads contain deltas rather than full accumulated state (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveProtocol.cs" :target="_blank" rel="noopener">protocol source</a>, <a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/streaming.md" :target="_blank" rel="noopener">delta contract</a>). General <a href="https://www.devleader.ca/2026/06/30/httpclient-streaming-in-c-httpcompletionoption-readasstreamasync-and-serversent-events">HttpClient streaming concepts</a> help explain incremental reading, but an SSE parser is not the Eve wire contract.</p>
<p>For Vercel Eve sessions, reopening midway through a message can therefore require presentation reconstruction. A suffix of append events is not a complete answer. Keep a compatible display checkpoint or rebuild from an appropriate earlier position, understanding the additional read and memory costs.</p>
<p>The pinned documentation requires contiguous text deltas with matching UTF-16 offsets and treats validated tool input as authoritative rather than an unfinished input preview (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/streaming.md" :target="_blank" rel="noopener">streaming guidance</a>). These examples avoid inventing a partial UI reducer. A raw event log and a finished chat bubble are different outputs.</p>
<p>Waiting also needs context: callback authorization may produce an interim <code>session.waiting</code> while the active response remains attached for completion (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/streaming.md" :target="_blank" rel="noopener">authorization parking</a>). This matters even if authentication is implemented elsewhere.</p>
<p>Do not manually break an active response loop on <code>IsCurrentTurnBoundary</code>: the property is context-free and remains true for that interim waiting event, while the response applies pending-authorization context (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/streaming.md" :target="_blank" rel="noopener">boundary warning</a>). Let the existing response owner decide when its stream ends.</p>
<h2 id="local-cancellation-and-remote-cancellation-are-different-decisions">Local Cancellation and Remote Cancellation Are Different Decisions</h2>
<p>Cancelling local consumption detaches the caller without itself stopping the durable turn (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/errors.md" :target="_blank" rel="noopener">error and cancellation contract</a>). A closed page and an explicit stop request therefore express different intentions for Vercel Eve sessions.</p>
<p>The final example intentionally requests remote cancellation, rather than simulating a disconnected client. Start the one outcome consumer first; response-level <code>CancelAsync</code> waits for its observed turn identity and sends a guard for that exact turn (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveMessageResponse.cs" :target="_blank" rel="noopener">cancellation implementation</a>).</p>
<div class="position-relative"><button class="btn btn-sm position-absolute top-0 end-0 m-2 border border-primary text-primary copy-btn"
        type="button"
        aria-label="Copy code"
        onclick="navigator.clipboard.writeText(this.parentElement.querySelector('pre code').textContent)">
        <i class="copy" aria-hidden="true"></i>
</button>
<pre><code class="language-csharp">using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using NexusLabs.Eve;

using HttpClient transport = new();
EveClient client = new(
    transport, new EveClientOptions(&quot;https://agent.example.com&quot;));
EveSession session = client.CreateSession();
EveMessageResponse response = await session.SendAsync(
    &quot;Explain the fictional pump inspection in detail.&quot;,
    CancellationToken.None);

Task&lt;EveTurnOutcome&gt; outcomeTask =
    response.GetOutcomeAsync(CancellationToken.None);

EveCancellationOutcome cancellation =
    await response.CancelAsync(CancellationToken.None);
EveTurnOutcome outcome = await outcomeTask;

Console.WriteLine(cancellation);
Console.WriteLine($&quot;Observed boundary status: {outcome.Status}&quot;);

if (outcome.Status == EveTurnStatus.Failed)
{
    Console.Error.WriteLine(&quot;The session failed while cancellation was observed.&quot;);
    Environment.ExitCode = 1;
}
</code></pre>
</div>
<p>Cancellation is cooperative, and a settled response returns <code>NoActiveTurn</code> without another cancellation request (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/streaming.md" :target="_blank" rel="noopener">response cancellation contract</a>). The example does not promise that cancellation wins a race with completion. It prints the observed result and keeps consuming through settlement.</p>
<p>When only an attached handle and an observed turn ID remain, the guarded <code>session.CancelAsync(turnId, cancellationToken)</code> overload is available (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveSession.cs" :target="_blank" rel="noopener">session cancellation API</a>). Do not substitute the session cursor for that guard or reuse a cancelled local-reading token when intending to send a new remote request.</p>
<p>Finally, <code>EveTurnStatus.Failed</code> represents a streamed failure, while unsuccessful HTTP calls throw <code>EveClientException</code> and malformed successful responses throw <code>EveProtocolException</code> (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/errors.md" :target="_blank" rel="noopener">error taxonomy</a>). Caller cancellation belongs to another branch. Reporting all four as &quot;no answer&quot; hides the recovery decision.</p>
<p>For operational visibility, <a href="https://www.devleader.ca/2026/07/05/serilog-in-net-complete-guide-to-structured-logging">structured logging in .NET</a> can record safe identifiers and observed failure categories. <a href="https://www.devleader.ca/2026/07/30/opentelemetry-httpclient-instrumentation-in-net-tracing-outbound-http-calls">Outbound HTTP tracing</a> adds transport context, but one request span is not the entire durable conversation.</p>
<h2 id="frequently-asked-questions">Frequently Asked Questions</h2>
<p>These questions focus on recovering a reader, not administering the runtime or changing its context.</p>
<h3 id="does-reconnecting-start-another-turn">Does reconnecting start another turn?</h3>
<p>Reading with <code>StreamAsync</code> attaches to session events, whereas <code>SendAsync</code> submits a message (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveSession.cs" :target="_blank" rel="noopener">session API</a>). Recover Vercel Eve sessions by reading first, not automatically resending.</p>
<h3 id="can-i-aggregate-a-response-after-enumerating-it">Can I aggregate a response after enumerating it?</h3>
<p>No. The response enforces single-use consumption and throws if consumed again (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveMessageResponse.cs" :target="_blank" rel="noopener">response source</a>). Choose aggregation or direct enumeration for that response.</p>
<h3 id="does-a-saved-cursor-prove-my-handler-committed">Does a saved cursor prove my handler committed?</h3>
<p>No. Direct session enumeration advances local state before yielding the event (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveSession.cs" :target="_blank" rel="noopener">cursor source</a>). Application effects need a separate commit and replay strategy.</p>
<h3 id="does-bounded-catch-up-stop-the-agent">Does bounded catch-up stop the agent?</h3>
<p>No. <code>Follow = false</code> fixes the read's tail boundary, not the server's execution lifetime (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/streaming.md" :target="_blank" rel="noopener">bounded reads</a>). Later events can be consumed separately.</p>
<h3 id="should-every-waiting-event-end-my-active-response-loop">Should every waiting event end my active response loop?</h3>
<p>No. Callback authorization can park an active response at an interim waiting event, and the client applies that context (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/streaming.md" :target="_blank" rel="noopener">waiting guidance</a>). A manual boundary break loses that behavior.</p>
<h3 id="does-cancelling-the-reader-cancel-the-server-turn">Does cancelling the reader cancel the server turn?</h3>
<p>No. Local cancellation detaches consumption; cooperative remote cancellation is separate (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/errors.md" :target="_blank" rel="noopener">cancellation distinction</a>). Preserve this distinction in both UI language and failure reporting.</p>
<h2 id="keep-the-conversation-and-the-reader-separate">Keep the Conversation and the Reader Separate</h2>
<p>Recovering Vercel Eve sessions is easier when identity, transport progress, presentation state, and business effects remain separate. Restore the conversation handle. Let the library recover transport interruptions. Choose live following or fixed-tail catch-up. Treat a cursor as read progress, not a guarantee about application transactions.</p>
<p>That mental model explains the central distinction: a reader can stop while durable work continues. Returning to that work should recover observation before changing the conversation.</p>
]]></content:encoded>
      <media:content url="https://devleader-d2f9ggbjfpdqcka7.z01.azurefd.net/media/02-vercel-eve-sessions-streaming-csharp-recovery-hero-20261001.webp" />
    </item>
    <item>
      <guid isPermaLink="false">61b1fe3d-a6b1-427b-8796-87f8a02daa34</guid>
      <link>https://www.devleader.ca/2026/10/07/what-to-talk-about-in-oneonones-with-your-manager</link>
      <category>one-on-one topics</category>
      <category>employee development</category>
      <category>engineering management</category>
      <category>manager conversations</category>
      <title>What to Talk About in One-on-Ones With Your Manager</title>
      <pubDate>Wed, 07 Oct 2026 12:00:00 Z</pubDate>
      <description><![CDATA[<p>Choosing one-on-one topics can feel surprisingly difficult when nothing is obviously wrong. Your work is moving. You have already shared your updates. Now there is time with your manager, and you are not sure what would be worth discussing.</p>
<p>You do not need to manufacture a problem or arrive with an impressive question. A useful starting point is something you would understand better, approach differently, or feel better supported with after a conversation. That might be a technical question. It might be uncertainty about priorities. It might be a recent interaction you are still thinking about.</p>
<p>There is no single right answer. In <a href="https://www.youtube.com/watch?v=yuvatLN9nlo&amp;t=123s" :target="_blank" rel="noopener">my Code Commute discussion about what to bring to a one-on-one</a>, I explain why the person's needs and current situation matter more than a universal menu. The question here is narrower: how do you notice which topic is useful for you at that point?</p>
<h2 id="find-one-on-one-topics-in-what-is-still-unclear">Find One-on-One Topics in What Is Still Unclear</h2>
<p>When nothing feels worth bringing, it helps to distinguish having nothing new to report from having nothing useful to discuss. Those are not the same thing. A project can be progressing while you remain unclear about why a decision matters or what your manager expects from you.</p>
<p>Look back at the work you have actually been doing. Not to assemble a report, but to notice where you have been filling in missing information yourself. Perhaps you understand the assignment but not how it connects to another team's work. Perhaps a discussion ended without you knowing which concern should drive the next decision.</p>
<p>These are possible topics because a conversation could change your understanding. You are not looking for something dramatic. You are looking for a gap that matters.</p>
<p>For a hypothetical example, imagine that you can complete an assigned change but do not understand why it takes priority over another request. Both requests seem reasonable. Discussing the priority could help you make similar choices later, rather than only resolve the current task ordering.</p>
<p>That does not mean every uncertainty belongs with your manager. Different sources can help in different ways:</p>
<ul>
<li>A teammate might be closer to the details.</li>
<li>Existing documentation might already answer the question.</li>
<li>Your manager might have context, authority, or a perspective that would add something.</li>
</ul>
<p>Consider which would help with your uncertainty. This is a way to choose a useful conversation, not a requirement to investigate everything before asking.</p>
<p>If your uncertainty is simply that you do not know what these meetings can cover, that is worth saying too. You and your manager can discuss what would make the time useful. Not knowing the format is not a character flaw or proof that you are disengaged.</p>
<h2 id="choose-one-on-one-topics-by-the-help-you-want">Choose One-on-One Topics by the Help You Want</h2>
<p>A topic names the subject. It does not necessarily explain what you need from the conversation. Two employees could both bring up a project delay and want very different things.</p>
<p>One may need background on a dependency. Another may want help deciding what to do next. Someone else may need the manager to act on a coordination problem. If you only name the delay, your manager has to guess which conversation you intended.</p>
<p>You do not have to know the exact answer in advance. It can help to consider a few distinctions:</p>
<ul>
<li>Do you need information or context you are missing?</li>
<li>Do you want to examine your thinking with someone?</li>
<li>Is there a decision or action that needs your manager's involvement?</li>
</ul>
<p>These are not mandatory choices on a form. Your answer might change as you discuss the situation. Their purpose is to help you notice why you want this particular conversation.</p>
<p>For example, a hypothetical concern about repeated interruptions could be about understanding priorities, considering how to respond, or asking for help resolving conflicting requests. The same surface issue can lead to different outcomes. Being clear about the uncertainty makes it easier to start in the right place.</p>
<p>This also leaves room for a sounding-board conversation. You might understand the facts and still want to explore your options rather than immediately receive instructions. My earlier piece on <a href="https://www.devleader.ca/2013/08/05/listen-first-the-human-sounding-board">listening as a human sounding board</a> discusses that broader leadership idea. It is a historical perspective, not a prescribed meeting process.</p>
<p>You are allowed to raise a concern before you have solved it. Deciding what help might be useful is different from being required to bring a finished solution.</p>
<h2 id="keep-technical-questions-when-they-serve-a-real-need">Keep Technical Questions When They Serve a Real Need</h2>
<p>Technical questions are not automatically a misuse of your one-on-one. If your manager understands the system or its history, asking for context can be a perfectly reasonable use of the time.</p>
<p>The important distinction is why you are asking. Possible reasons include:</p>
<ul>
<li>Understanding a part of the system connected to your responsibilities.</li>
<li>Connecting a local decision to the wider architecture.</li>
<li>Reaching for a familiar technical subject because nothing else comes to mind.</li>
</ul>
<p>Even the last possibility does not make the question wrong. It just gives you something to reflect on. A useful technical discussion and a default habit can look similar from the outside.</p>
<p>In the Code Commute discussion linked above, I describe architecture conversations as valuable at a particular point in joining a team. The need changed as understanding improved. That is the point to carry forward: a topic can be useful now without becoming the permanent purpose of every meeting.</p>
<p>Imagine, hypothetically, that you understand how a component works but not why your team owns it. A conversation about ownership and history might be more useful than another walkthrough of its implementation. Alternatively, if the implementation is what you genuinely need help understanding, there is no reason to disguise that as a career question.</p>
<p>Do not wait for the recurring meeting if a question is blocking work and you have an appropriate way to ask sooner. The meeting is an opportunity for support, not a rule that support is available only then. Your manager might also help identify someone better placed to answer.</p>
<h2 id="let-recent-work-tell-you-whether-feedback-is-timely">Let Recent Work Tell You Whether Feedback Is Timely</h2>
<p>Feedback is a possible topic, but you do not need a fresh verdict on your entire performance every week. If very little has changed, another broad progress question may produce very little new information.</p>
<p>Recent work gives you something concrete to consider. For example, you may have:</p>
<ul>
<li>Explained a decision.</li>
<li>Coordinated an activity.</li>
<li>Tried a different approach to communicating.</li>
</ul>
<p>If you are uncertain about that experience, feedback may be the most useful subject for the upcoming conversation.</p>
<p>The topic-selection question is not simply, &quot;Should I ask for feedback?&quot; It is, &quot;What recent experience am I trying to understand?&quot; That helps distinguish a timely discussion from a request made only because feedback sounds like the correct thing to talk about.</p>
<p>For a hypothetical example, suppose you left a planning discussion unsure whether people understood the decision you were proposing. Feedback on that interaction has a clear purpose: understanding the communication. It does not need to become a discussion about your whole career.</p>
<p>Your manager may need time or more context to offer something useful. Choosing the topic ahead of the meeting can help them prepare, but it does not guarantee a good answer. Nor does it remove their responsibility to offer relevant feedback proactively.</p>
<p>There is also no requirement to turn every completed task into a feedback request. Choose an experience where learning more could affect what you do next. That keeps feedback connected to a real question rather than making it another recurring obligation.</p>
<h2 id="make-room-for-direction-without-assuming-promotion-is-the-goal">Make Room for Direction Without Assuming Promotion Is the Goal</h2>
<p>Sometimes nothing feels urgent because the uncertainty is further ahead. You may be comfortable with current work but unsure what kinds of responsibilities you want to explore. That is different from needing help with the current assignment.</p>
<p>Most people I work with are interested in promotion or career advancement. That is my experience, not a rule about you. Your topic should reflect your goals rather than an assumption that everyone wants the same next step.</p>
<p>Your interests might include:</p>
<ul>
<li>Deepening your expertise.</li>
<li>Understanding a different area.</li>
<li>Finding out what broader responsibility involves.</li>
</ul>
<p>If you are interested in advancement, that belongs in the conversation too. The starting point is what you want to understand, not a prescribed ambition.</p>
<p>For someone curious about leading beyond their assigned work, <a href="https://www.devleader.ca/2024/01/28/informal-leadership-in-software-engineering-what-you-need-to-progress">informal leadership in software engineering</a> provides related context. It is relevant when that interest exists. It is not a requirement that everyone pursue leadership.</p>
<p>A direction conversation is timely when you have a real question about what comes next or whether current opportunities fit your interests. It does not need to recur unchanged every week. If you have already established a direction and nothing relevant has changed, another topic may be more useful in that meeting.</p>
<p>You also do not need to know your long-term destination before discussing it. Exploring what a responsibility involves can help you decide whether it interests you. Understanding the possibilities is an outcome in its own right.</p>
<h2 id="consider-learning-and-business-context-not-just-problems">Consider Learning and Business Context, Not Just Problems</h2>
<p>A meeting does not need to begin with something going badly. Curiosity can produce a worthwhile topic when it helps you understand the work or an area you would like to learn about.</p>
<p>Learning opportunities and business direction can also be useful subjects, even when you have no blocker. Understanding why the team is investing in a particular area may help connect your immediate work to a larger purpose.</p>
<p>Imagine, hypothetically, that a responsibility outside your current assignment interests you. You might want to understand what that work involves and whether there are ways to learn through the team's activities. That is a different conversation from requesting an immediate role change.</p>
<p>It is worth separating an interest from an assumption about where the learning must happen. <a href="https://www.devleader.ca/2023/09/27/workplace-based-learning-how-to-balance-learning-at-work-vs-home">Learning at work versus learning at home</a> explores that distinction. The conversation can concern opportunities within your work, not only extra activities after hours.</p>
<p>Your manager may not have all the answers, and opportunities may be limited. A useful result could be identifying who has the context or understanding a constraint. The topic remains worthwhile because it answers a real question, not because it guarantees access to something.</p>
<h2 id="pick-what-matters-now-not-what-sounds-most-impressive">Pick What Matters Now, Not What Sounds Most Impressive</h2>
<p>After noticing several possibilities, you still have to choose. A practical way to think about that choice is to ask what would remain unclear or unsupported if you left the subject untouched.</p>
<p>That brings the decision back to your situation. A modest question about expectations may matter more than a sophisticated question about strategy. A concern affecting your work may deserve attention before a less timely discussion about possible future responsibilities.</p>
<p>You do not need a scoring system, a fixed number of topics, or a rotation through categories. Choose based on what matters in your situation.</p>
<p>Some useful contrasts are:</p>
<ul>
<li>Familiar does not mean useless; a technical topic may meet a real need.</li>
<li>New does not mean important; novelty alone does not make a conversation valuable.</li>
<li>Unresolved does not mean urgent; some questions can wait while others need support sooner.</li>
</ul>
<p>Status can belong here too. During a demanding project, the work may genuinely be top of mind. The distinction is between discussing what you need around that work and merely repeating an update because it is the easiest available subject.</p>
<p>If the work is all you can think about, consider what part deserves the conversation. Is there missing context, a concern, or a decision? You do not have to reject the topic. You can be more deliberate about what you hope to get from it.</p>
<h2 id="if-nothing-surfaces-be-honest-about-that">If Nothing Surfaces, Be Honest About That</h2>
<p>You may reflect and still have no particular topic. That does not mean you should invent one to demonstrate effort. Your manager may have observations, feedback, or context to bring.</p>
<p>Employee agency and manager accountability belong together. You can identify what matters to you without becoming solely responsible for the meeting's value. Your manager has a responsibility to support you and make space, not simply wait for a perfect question.</p>
<p>The balance is relevant to <a href="https://www.devleader.ca/2023/03/13/handson-management-how-to-lead-engineers-to-success">hands-on management</a>: support involves understanding where involvement helps. A conversation about what you need can be part of that understanding, but the authority and responsibilities are not equal on both sides.</p>
<p>Personal information is not the missing topic you owe the meeting. Sharing is optional. You can have a useful working relationship without discussing your private life. Recording and AI notes are also off by default in my one-on-ones, with extremely rare mutual opt-in exceptions to help both people focus.</p>
<p>One quiet week is not the same as a recurring pattern of unhelpful conversations. If you repeatedly leave without useful context or support, the usefulness of the meeting itself may be worth discussing. Better topic selection can help, but it cannot guarantee that your manager will respond well.</p>
<h2 id="frequently-asked-questions">Frequently Asked Questions</h2>
<p>The answers below are starting points for choosing a conversation, not rules for performing a meeting correctly.</p>
<h3 id="what-should-i-talk-about-when-i-have-no-updates">What should I talk about when I have no updates?</h3>
<p>Look for something you still want to understand: context behind work, expectations, a recent experience, or an area you would like to explore. Having no update does not mean there is nothing useful to discuss. Your manager can also bring relevant topics.</p>
<h3 id="are-technical-questions-appropriate-one-on-one-topics">Are technical questions appropriate one-on-one topics?</h3>
<p>Yes, when they meet a real need and your manager has useful context. Their value depends on your situation. Avoid waiting for the meeting if you need help sooner, and reconsider the topic as your understanding changes rather than keeping it by habit.</p>
<h3 id="do-i-need-to-bring-a-prepared-agenda">Do I need to bring a prepared agenda?</h3>
<p>No universal agenda is required. Thinking about a useful subject can help, but a fixed format or number of talking points is not the goal. Your manager should not make support depend on you arriving with a polished plan.</p>
<h3 id="should-i-ask-about-career-progression-every-week">Should I ask about career progression every week?</h3>
<p>Not necessarily. A broader direction discussion may offer little new information when nothing relevant has changed. Timely feedback on a particular experience is different. Choose based on your actual question, and do not assume promotion must be your goal.</p>
<h3 id="can-i-bring-a-problem-without-knowing-the-solution">Can I bring a problem without knowing the solution?</h3>
<p>Yes. You can discuss a challenge while still figuring out what help would be useful. Thinking together, receiving context, and asking for involvement are different possibilities. None requires you to arrive with the problem already solved.</p>
<h3 id="do-i-have-to-discuss-something-personal">Do I have to discuss something personal?</h3>
<p>No. Personal sharing is optional, not a requirement for making the time worthwhile. If you choose to share, that information should be treated as private. You do not need to disclose personal details to justify a work-related question or support need.</p>
<h2 id="a-useful-topic-answers-a-real-need">A Useful Topic Answers a Real Need</h2>
<p>The most useful one-on-one topics are not necessarily the most ambitious or unusual. They are subjects where the conversation can help you understand something, examine your thinking, or identify support you need.</p>
<p>You do not have to cover every category. You do not have to make every week about advancement. Start with what matters now and why talking about it would help. That gives the conversation a purpose without forcing it into someone else's formula.</p>
]]></description>
      <content:encoded><![CDATA[<p>Choosing one-on-one topics can feel surprisingly difficult when nothing is obviously wrong. Your work is moving. You have already shared your updates. Now there is time with your manager, and you are not sure what would be worth discussing.</p>
<p>You do not need to manufacture a problem or arrive with an impressive question. A useful starting point is something you would understand better, approach differently, or feel better supported with after a conversation. That might be a technical question. It might be uncertainty about priorities. It might be a recent interaction you are still thinking about.</p>
<p>There is no single right answer. In <a href="https://www.youtube.com/watch?v=yuvatLN9nlo&amp;t=123s" :target="_blank" rel="noopener">my Code Commute discussion about what to bring to a one-on-one</a>, I explain why the person's needs and current situation matter more than a universal menu. The question here is narrower: how do you notice which topic is useful for you at that point?</p>
<h2 id="find-one-on-one-topics-in-what-is-still-unclear">Find One-on-One Topics in What Is Still Unclear</h2>
<p>When nothing feels worth bringing, it helps to distinguish having nothing new to report from having nothing useful to discuss. Those are not the same thing. A project can be progressing while you remain unclear about why a decision matters or what your manager expects from you.</p>
<p>Look back at the work you have actually been doing. Not to assemble a report, but to notice where you have been filling in missing information yourself. Perhaps you understand the assignment but not how it connects to another team's work. Perhaps a discussion ended without you knowing which concern should drive the next decision.</p>
<p>These are possible topics because a conversation could change your understanding. You are not looking for something dramatic. You are looking for a gap that matters.</p>
<p>For a hypothetical example, imagine that you can complete an assigned change but do not understand why it takes priority over another request. Both requests seem reasonable. Discussing the priority could help you make similar choices later, rather than only resolve the current task ordering.</p>
<p>That does not mean every uncertainty belongs with your manager. Different sources can help in different ways:</p>
<ul>
<li>A teammate might be closer to the details.</li>
<li>Existing documentation might already answer the question.</li>
<li>Your manager might have context, authority, or a perspective that would add something.</li>
</ul>
<p>Consider which would help with your uncertainty. This is a way to choose a useful conversation, not a requirement to investigate everything before asking.</p>
<p>If your uncertainty is simply that you do not know what these meetings can cover, that is worth saying too. You and your manager can discuss what would make the time useful. Not knowing the format is not a character flaw or proof that you are disengaged.</p>
<h2 id="choose-one-on-one-topics-by-the-help-you-want">Choose One-on-One Topics by the Help You Want</h2>
<p>A topic names the subject. It does not necessarily explain what you need from the conversation. Two employees could both bring up a project delay and want very different things.</p>
<p>One may need background on a dependency. Another may want help deciding what to do next. Someone else may need the manager to act on a coordination problem. If you only name the delay, your manager has to guess which conversation you intended.</p>
<p>You do not have to know the exact answer in advance. It can help to consider a few distinctions:</p>
<ul>
<li>Do you need information or context you are missing?</li>
<li>Do you want to examine your thinking with someone?</li>
<li>Is there a decision or action that needs your manager's involvement?</li>
</ul>
<p>These are not mandatory choices on a form. Your answer might change as you discuss the situation. Their purpose is to help you notice why you want this particular conversation.</p>
<p>For example, a hypothetical concern about repeated interruptions could be about understanding priorities, considering how to respond, or asking for help resolving conflicting requests. The same surface issue can lead to different outcomes. Being clear about the uncertainty makes it easier to start in the right place.</p>
<p>This also leaves room for a sounding-board conversation. You might understand the facts and still want to explore your options rather than immediately receive instructions. My earlier piece on <a href="https://www.devleader.ca/2013/08/05/listen-first-the-human-sounding-board">listening as a human sounding board</a> discusses that broader leadership idea. It is a historical perspective, not a prescribed meeting process.</p>
<p>You are allowed to raise a concern before you have solved it. Deciding what help might be useful is different from being required to bring a finished solution.</p>
<h2 id="keep-technical-questions-when-they-serve-a-real-need">Keep Technical Questions When They Serve a Real Need</h2>
<p>Technical questions are not automatically a misuse of your one-on-one. If your manager understands the system or its history, asking for context can be a perfectly reasonable use of the time.</p>
<p>The important distinction is why you are asking. Possible reasons include:</p>
<ul>
<li>Understanding a part of the system connected to your responsibilities.</li>
<li>Connecting a local decision to the wider architecture.</li>
<li>Reaching for a familiar technical subject because nothing else comes to mind.</li>
</ul>
<p>Even the last possibility does not make the question wrong. It just gives you something to reflect on. A useful technical discussion and a default habit can look similar from the outside.</p>
<p>In the Code Commute discussion linked above, I describe architecture conversations as valuable at a particular point in joining a team. The need changed as understanding improved. That is the point to carry forward: a topic can be useful now without becoming the permanent purpose of every meeting.</p>
<p>Imagine, hypothetically, that you understand how a component works but not why your team owns it. A conversation about ownership and history might be more useful than another walkthrough of its implementation. Alternatively, if the implementation is what you genuinely need help understanding, there is no reason to disguise that as a career question.</p>
<p>Do not wait for the recurring meeting if a question is blocking work and you have an appropriate way to ask sooner. The meeting is an opportunity for support, not a rule that support is available only then. Your manager might also help identify someone better placed to answer.</p>
<h2 id="let-recent-work-tell-you-whether-feedback-is-timely">Let Recent Work Tell You Whether Feedback Is Timely</h2>
<p>Feedback is a possible topic, but you do not need a fresh verdict on your entire performance every week. If very little has changed, another broad progress question may produce very little new information.</p>
<p>Recent work gives you something concrete to consider. For example, you may have:</p>
<ul>
<li>Explained a decision.</li>
<li>Coordinated an activity.</li>
<li>Tried a different approach to communicating.</li>
</ul>
<p>If you are uncertain about that experience, feedback may be the most useful subject for the upcoming conversation.</p>
<p>The topic-selection question is not simply, &quot;Should I ask for feedback?&quot; It is, &quot;What recent experience am I trying to understand?&quot; That helps distinguish a timely discussion from a request made only because feedback sounds like the correct thing to talk about.</p>
<p>For a hypothetical example, suppose you left a planning discussion unsure whether people understood the decision you were proposing. Feedback on that interaction has a clear purpose: understanding the communication. It does not need to become a discussion about your whole career.</p>
<p>Your manager may need time or more context to offer something useful. Choosing the topic ahead of the meeting can help them prepare, but it does not guarantee a good answer. Nor does it remove their responsibility to offer relevant feedback proactively.</p>
<p>There is also no requirement to turn every completed task into a feedback request. Choose an experience where learning more could affect what you do next. That keeps feedback connected to a real question rather than making it another recurring obligation.</p>
<h2 id="make-room-for-direction-without-assuming-promotion-is-the-goal">Make Room for Direction Without Assuming Promotion Is the Goal</h2>
<p>Sometimes nothing feels urgent because the uncertainty is further ahead. You may be comfortable with current work but unsure what kinds of responsibilities you want to explore. That is different from needing help with the current assignment.</p>
<p>Most people I work with are interested in promotion or career advancement. That is my experience, not a rule about you. Your topic should reflect your goals rather than an assumption that everyone wants the same next step.</p>
<p>Your interests might include:</p>
<ul>
<li>Deepening your expertise.</li>
<li>Understanding a different area.</li>
<li>Finding out what broader responsibility involves.</li>
</ul>
<p>If you are interested in advancement, that belongs in the conversation too. The starting point is what you want to understand, not a prescribed ambition.</p>
<p>For someone curious about leading beyond their assigned work, <a href="https://www.devleader.ca/2024/01/28/informal-leadership-in-software-engineering-what-you-need-to-progress">informal leadership in software engineering</a> provides related context. It is relevant when that interest exists. It is not a requirement that everyone pursue leadership.</p>
<p>A direction conversation is timely when you have a real question about what comes next or whether current opportunities fit your interests. It does not need to recur unchanged every week. If you have already established a direction and nothing relevant has changed, another topic may be more useful in that meeting.</p>
<p>You also do not need to know your long-term destination before discussing it. Exploring what a responsibility involves can help you decide whether it interests you. Understanding the possibilities is an outcome in its own right.</p>
<h2 id="consider-learning-and-business-context-not-just-problems">Consider Learning and Business Context, Not Just Problems</h2>
<p>A meeting does not need to begin with something going badly. Curiosity can produce a worthwhile topic when it helps you understand the work or an area you would like to learn about.</p>
<p>Learning opportunities and business direction can also be useful subjects, even when you have no blocker. Understanding why the team is investing in a particular area may help connect your immediate work to a larger purpose.</p>
<p>Imagine, hypothetically, that a responsibility outside your current assignment interests you. You might want to understand what that work involves and whether there are ways to learn through the team's activities. That is a different conversation from requesting an immediate role change.</p>
<p>It is worth separating an interest from an assumption about where the learning must happen. <a href="https://www.devleader.ca/2023/09/27/workplace-based-learning-how-to-balance-learning-at-work-vs-home">Learning at work versus learning at home</a> explores that distinction. The conversation can concern opportunities within your work, not only extra activities after hours.</p>
<p>Your manager may not have all the answers, and opportunities may be limited. A useful result could be identifying who has the context or understanding a constraint. The topic remains worthwhile because it answers a real question, not because it guarantees access to something.</p>
<h2 id="pick-what-matters-now-not-what-sounds-most-impressive">Pick What Matters Now, Not What Sounds Most Impressive</h2>
<p>After noticing several possibilities, you still have to choose. A practical way to think about that choice is to ask what would remain unclear or unsupported if you left the subject untouched.</p>
<p>That brings the decision back to your situation. A modest question about expectations may matter more than a sophisticated question about strategy. A concern affecting your work may deserve attention before a less timely discussion about possible future responsibilities.</p>
<p>You do not need a scoring system, a fixed number of topics, or a rotation through categories. Choose based on what matters in your situation.</p>
<p>Some useful contrasts are:</p>
<ul>
<li>Familiar does not mean useless; a technical topic may meet a real need.</li>
<li>New does not mean important; novelty alone does not make a conversation valuable.</li>
<li>Unresolved does not mean urgent; some questions can wait while others need support sooner.</li>
</ul>
<p>Status can belong here too. During a demanding project, the work may genuinely be top of mind. The distinction is between discussing what you need around that work and merely repeating an update because it is the easiest available subject.</p>
<p>If the work is all you can think about, consider what part deserves the conversation. Is there missing context, a concern, or a decision? You do not have to reject the topic. You can be more deliberate about what you hope to get from it.</p>
<h2 id="if-nothing-surfaces-be-honest-about-that">If Nothing Surfaces, Be Honest About That</h2>
<p>You may reflect and still have no particular topic. That does not mean you should invent one to demonstrate effort. Your manager may have observations, feedback, or context to bring.</p>
<p>Employee agency and manager accountability belong together. You can identify what matters to you without becoming solely responsible for the meeting's value. Your manager has a responsibility to support you and make space, not simply wait for a perfect question.</p>
<p>The balance is relevant to <a href="https://www.devleader.ca/2023/03/13/handson-management-how-to-lead-engineers-to-success">hands-on management</a>: support involves understanding where involvement helps. A conversation about what you need can be part of that understanding, but the authority and responsibilities are not equal on both sides.</p>
<p>Personal information is not the missing topic you owe the meeting. Sharing is optional. You can have a useful working relationship without discussing your private life. Recording and AI notes are also off by default in my one-on-ones, with extremely rare mutual opt-in exceptions to help both people focus.</p>
<p>One quiet week is not the same as a recurring pattern of unhelpful conversations. If you repeatedly leave without useful context or support, the usefulness of the meeting itself may be worth discussing. Better topic selection can help, but it cannot guarantee that your manager will respond well.</p>
<h2 id="frequently-asked-questions">Frequently Asked Questions</h2>
<p>The answers below are starting points for choosing a conversation, not rules for performing a meeting correctly.</p>
<h3 id="what-should-i-talk-about-when-i-have-no-updates">What should I talk about when I have no updates?</h3>
<p>Look for something you still want to understand: context behind work, expectations, a recent experience, or an area you would like to explore. Having no update does not mean there is nothing useful to discuss. Your manager can also bring relevant topics.</p>
<h3 id="are-technical-questions-appropriate-one-on-one-topics">Are technical questions appropriate one-on-one topics?</h3>
<p>Yes, when they meet a real need and your manager has useful context. Their value depends on your situation. Avoid waiting for the meeting if you need help sooner, and reconsider the topic as your understanding changes rather than keeping it by habit.</p>
<h3 id="do-i-need-to-bring-a-prepared-agenda">Do I need to bring a prepared agenda?</h3>
<p>No universal agenda is required. Thinking about a useful subject can help, but a fixed format or number of talking points is not the goal. Your manager should not make support depend on you arriving with a polished plan.</p>
<h3 id="should-i-ask-about-career-progression-every-week">Should I ask about career progression every week?</h3>
<p>Not necessarily. A broader direction discussion may offer little new information when nothing relevant has changed. Timely feedback on a particular experience is different. Choose based on your actual question, and do not assume promotion must be your goal.</p>
<h3 id="can-i-bring-a-problem-without-knowing-the-solution">Can I bring a problem without knowing the solution?</h3>
<p>Yes. You can discuss a challenge while still figuring out what help would be useful. Thinking together, receiving context, and asking for involvement are different possibilities. None requires you to arrive with the problem already solved.</p>
<h3 id="do-i-have-to-discuss-something-personal">Do I have to discuss something personal?</h3>
<p>No. Personal sharing is optional, not a requirement for making the time worthwhile. If you choose to share, that information should be treated as private. You do not need to disclose personal details to justify a work-related question or support need.</p>
<h2 id="a-useful-topic-answers-a-real-need">A Useful Topic Answers a Real Need</h2>
<p>The most useful one-on-one topics are not necessarily the most ambitious or unusual. They are subjects where the conversation can help you understand something, examine your thinking, or identify support you need.</p>
<p>You do not have to cover every category. You do not have to make every week about advancement. Start with what matters now and why talking about it would help. That gives the conversation a purpose without forcing it into someone else's formula.</p>
]]></content:encoded>
      <media:content url="https://devleader-d2f9ggbjfpdqcka7.z01.azurefd.net/media/01-what-to-talk-about-one-on-ones-manager-hero-20261003.webp" />
    </item>
    <item>
      <guid isPermaLink="false">50059b15-4338-468f-bdb0-3c1d1538b033</guid>
      <link>https://www.devleader.ca/2026/10/05/vercel-eve-tutorial-for-c-developers-build-an-aspnet-core-agent-client</link>
      <category>Vercel Eve tutorial</category>
      <category>C#</category>
      <category>ASP.NET Core</category>
      <category>.NET 10</category>
      <category>structured output</category>
      <title>Vercel Eve Tutorial for C# Developers: Build an ASP.NET Core Agent Client</title>
      <pubDate>Mon, 05 Oct 2026 13:00:00 Z</pubDate>
      <description><![CDATA[<p>This <strong>Vercel Eve tutorial</strong> builds a small ASP.NET Core application that asks a separately hosted agent to explain a fictional equipment inspection. The interesting part is not the pump. It is connecting two runtimes without losing track of transport ownership, response consumption, or the difference between receiving JSON and receiving a valid application result.</p>
<p>The application will accept only one synthetic equipment identifier. It will request a structured summary, preserve the agent session identifier in its response, and report downstream failures instead of manufacturing an empty successful answer. There are no maintenance commands, notifications, or production records in this example.</p>
<p>I maintain NexusLabs.Eve, the independent .NET client used in these examples. It is not an official Vercel SDK.</p>
<p>These examples were reviewed against public, tagged source. They have not been compiled or executed for this article. Model access, server startup, and end-to-end execution still need verification in your own validation environment.</p>
<h2 id="vercel-eve-tutorial-establish-the-version-and-service-boundary">Vercel Eve Tutorial: Establish the Version and Service Boundary</h2>
<p>For this Vercel Eve tutorial, use .NET 10 with C# 14 and <code>NexusLabs.Eve</code> version <code>0.1.0-alpha-0014</code>, whose published package targets .NET 10 (<a href="https://www.nuget.org/packages/NexusLabs.Eve/0.1.0-alpha-0014" :target="_blank" rel="noopener">package metadata</a>). The reviewed public client source is tagged <code>v0.1.0-alpha.14</code>, and its compatibility reference is Eve 0.63.0 (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/compatibility.md" :target="_blank" rel="noopener">pinned compatibility documentation</a>).</p>
<p>Do not substitute a floating server version just because the installation command permits it. The client declares accepted agent-info schemas 1 through 4, which is a bounded protocol contract rather than unrestricted future compatibility (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveProtocol.cs" :target="_blank" rel="noopener">protocol source</a>). Keeping the server and client pins together makes the tutorial's API references coherent.</p>
<p>There are two projects. The Eve project contains the agent's instructions and configuration. The ASP.NET Core project receives an application request and calls that agent over HTTP. The .NET client does not move Eve's Node runtime into your web process (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/README.md" :target="_blank" rel="noopener">client scope</a>).</p>
<p>That separation is useful, but it has a cost. A single-process application has fewer deployment boundaries; a remote agent makes its authoring and execution ownership explicit but introduces network failure and version coordination. This walkthrough demonstrates the second shape without arguing that every model request needs it.</p>
<p>Create a directory named <code>EquipmentWeb</code> and save this complete project file as <code>EquipmentWeb.csproj</code>:</p>
<div class="position-relative"><button class="btn btn-sm position-absolute top-0 end-0 m-2 border border-primary text-primary copy-btn"
        type="button"
        aria-label="Copy code"
        onclick="navigator.clipboard.writeText(this.parentElement.querySelector('pre code').textContent)">
        <i class="copy" aria-hidden="true"></i>
</button>
<pre><code class="language-xml">&lt;Project Sdk=&quot;Microsoft.NET.Sdk.Web&quot;&gt;
  &lt;PropertyGroup&gt;
    &lt;TargetFramework&gt;net10.0&lt;/TargetFramework&gt;
    &lt;LangVersion&gt;14.0&lt;/LangVersion&gt;
    &lt;Nullable&gt;enable&lt;/Nullable&gt;
    &lt;ImplicitUsings&gt;enable&lt;/ImplicitUsings&gt;
  &lt;/PropertyGroup&gt;
  &lt;ItemGroup&gt;
    &lt;PackageReference Include=&quot;NexusLabs.Eve&quot; Version=&quot;0.1.0-alpha-0014&quot; /&gt;
  &lt;/ItemGroup&gt;
&lt;/Project&gt;
</code></pre>
</div>
<p>The web SDK supplies the ASP.NET Core application environment. The explicit language and package versions document the assumptions under which the following files are written. There is no additional application framework, generated adapter package, or hidden helper library.</p>
<p>Think of the web project as a translation boundary. An HTTP caller asks for a summary of one known item; the adapter expresses that request to the agent; the endpoint returns a small application-owned response. The caller does not need to understand the agent's event stream to use that endpoint. The cost is that the adapter must define what counts as an acceptable answer rather than forwarding every downstream payload indiscriminately.</p>
<p>Keep the TypeScript project in a separate directory named <code>EquipmentAgent</code>. The remaining examples identify their destination files individually. Neither project should receive the other's source files.</p>
<h2 id="create-a-public-synthetic-read-only-agent">Create a Public, Synthetic Read-Only Agent</h2>
<p>The public client fixture pins Eve 0.63.0, AI SDK 7.0.105, <code>just-bash</code> 3.1.0, and Node 24.x (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/test/fixtures/eve-agent/package.json" :target="_blank" rel="noopener">fixture manifest</a>). This Vercel Eve tutorial uses those dependency pins as its baseline, not the fixture's execution results as proof that our newly authored agent works.</p>
<p>Save this complete file as <code>EquipmentAgent/package.json</code>:</p>
<div class="position-relative"><button class="btn btn-sm position-absolute top-0 end-0 m-2 border border-primary text-primary copy-btn"
        type="button"
        aria-label="Copy code"
        onclick="navigator.clipboard.writeText(this.parentElement.querySelector('pre code').textContent)">
        <i class="copy" aria-hidden="true"></i>
</button>
<pre><code class="language-json">{
  &quot;name&quot;: &quot;synthetic-equipment-agent&quot;,
  &quot;private&quot;: true,
  &quot;type&quot;: &quot;module&quot;,
  &quot;scripts&quot;: {
    &quot;build&quot;: &quot;eve build&quot;,
    &quot;start&quot;: &quot;eve start&quot;
  },
  &quot;dependencies&quot;: {
    &quot;ai&quot;: &quot;7.0.105&quot;,
    &quot;eve&quot;: &quot;0.63.0&quot;,
    &quot;just-bash&quot;: &quot;3.1.0&quot;
  },
  &quot;engines&quot;: {
    &quot;node&quot;: &quot;24.x&quot;
  }
}
</code></pre>
</div>
<p>Here, <code>private</code> prevents accidental npm publication; it does not describe confidential data. All scenario content below is independently authored and fictional. No real equipment service or external account is called to retrieve an inspection.</p>
<p>Eve 0.63.0 uses <code>agent/instructions.md</code> for required instructions and supports <code>agent/agent.ts</code> for optional model configuration (<a href="https://github.com/vercel/eve/blob/eve%400.63.0/README.md" :target="_blank" rel="noopener">pinned authoring layout</a>). Create the <code>agent</code> subdirectory and save the following complete file as <code>EquipmentAgent/agent/agent.ts</code>:</p>
<div class="position-relative"><button class="btn btn-sm position-absolute top-0 end-0 m-2 border border-primary text-primary copy-btn"
        type="button"
        aria-label="Copy code"
        onclick="navigator.clipboard.writeText(this.parentElement.querySelector('pre code').textContent)">
        <i class="copy" aria-hidden="true"></i>
</button>
<pre><code class="language-typescript">import { defineAgent } from &quot;eve&quot;;

export default defineAgent({
  model: &quot;openai/gpt-5.6-luna-fast&quot;,
});
</code></pre>
</div>
<p>The model identifier follows the pinned upstream example, not a claim that this model is available to every account (<a href="https://github.com/vercel/eve/blob/eve%400.63.0/README.md" :target="_blank" rel="noopener">configuration example</a>). Provider or gateway credentials must be configured separately using the server's documented mechanism. Do not copy credentials into these files.</p>
<p>Save the following paragraph, without the quotation marker, as the entire contents of <code>EquipmentAgent/agent/instructions.md</code>:</p>
<blockquote class="blockquote">
<p>You explain synthetic equipment inspection notes. The only equipment is pump-demo-01. Its fictional inspection note says: a protective cover was reported loose. The recommendation is: ask a qualified technician to assess the equipment. Always identify these observations as fictional. Do not infer operating safety, perform actions, access external records, or invent additional equipment. When a structured result is requested, return equipmentId as pump-demo-01, mocked as true, observation as the fictional inspection note, and recommendation as the technician recommendation.</p>
</blockquote>
<p>This is intentionally an instructions-only read-only sample. Adding a database lookup tool would introduce another contract before the HTTP integration is understood. The tradeoff is that the data is fixed, not retrieved from a live inventory. That is appropriate for learning the boundary, but not evidence of a maintenance system.</p>
<p>The manifest provides build and start commands for a validation environment. Dependency restoration and a model-access configuration are prerequisites before using them. Start the pinned server there and use its reported listening address; this article does not assert an unverified default port or a successful startup transcript.</p>
<p>For application architecture background, <a href="https://www.devleader.ca/2026/03/03/building-real-apps-with-github-copilot-sdk-in-c-endtoend-patterns-and-architecture">building agent-backed ASP.NET Core applications</a> illustrates the broader separation between an application endpoint and an agent integration. The protocol used here remains Eve's, not that article's SDK.</p>
<h2 id="request-typed-output-without-inventing-a-success-result">Request Typed Output Without Inventing a Success Result</h2>
<p>The typed result in this Vercel Eve tutorial is <code>EquipmentSummary</code>. It has four fields: an equipment identifier, a marker that the data is mocked, the observation, and the recommendation. These names must agree across the instructions, JSON Schema, and C# serialization metadata.</p>
<p>The client accepts a JSON Schema through <code>EveTurnOptions.OutputSchema</code> on the message turn, with the server remaining authoritative for validation (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/structured-output.md" :target="_blank" rel="noopener">structured-output contract</a>). A C# record is therefore not a substitute for sending a schema, and deserialization alone is not a safety guarantee.</p>
<p>Save this complete file as <code>EquipmentWeb/EquipmentAgent.cs</code>:</p>
<div class="position-relative"><button class="btn btn-sm position-absolute top-0 end-0 m-2 border border-primary text-primary copy-btn"
        type="button"
        aria-label="Copy code"
        onclick="navigator.clipboard.writeText(this.parentElement.querySelector('pre code').textContent)">
        <i class="copy" aria-hidden="true"></i>
</button>
<pre><code class="language-csharp">using System.Text.Json;
using System.Text.Json.Serialization;
using NexusLabs.Eve;

namespace EquipmentWeb;

public sealed record EquipmentSummary(
    string EquipmentId,
    bool Mocked,
    string Observation,
    string Recommendation);

public sealed record EquipmentAnswer(
    string SessionId,
    EquipmentSummary Summary);

[JsonSourceGenerationOptions(
    PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
[JsonSerializable(typeof(EquipmentSummary))]
[JsonSerializable(typeof(EquipmentAnswer))]
internal partial class EquipmentJsonContext : JsonSerializerContext
{
}

public sealed class EquipmentAgent(
    IHttpClientFactory transportFactory,
    IConfiguration configuration)
{
    public async Task&lt;EquipmentAnswer&gt; ReadAsync(
        CancellationToken cancellationToken)
    {
        string host = configuration[&quot;Eve:Host&quot;]
            ?? throw new InvalidOperationException(&quot;Eve:Host is required.&quot;);

        using HttpClient transport = transportFactory.CreateClient(&quot;eve&quot;);
        EveClient client = new(transport, new EveClientOptions(host));
        EveSession session = client.CreateSession();

        using JsonDocument schema = JsonDocument.Parse(&quot;&quot;&quot;
            {
              &quot;type&quot;: &quot;object&quot;,
              &quot;additionalProperties&quot;: false,
              &quot;properties&quot;: {
                &quot;equipmentId&quot;: {
                  &quot;type&quot;: &quot;string&quot;,
                  &quot;enum&quot;: [&quot;pump-demo-01&quot;]
                },
                &quot;mocked&quot;: {
                  &quot;type&quot;: &quot;boolean&quot;,
                  &quot;enum&quot;: [true]
                },
                &quot;observation&quot;: { &quot;type&quot;: &quot;string&quot;, &quot;minLength&quot;: 1 },
                &quot;recommendation&quot;: { &quot;type&quot;: &quot;string&quot;, &quot;minLength&quot;: 1 }
              },
              &quot;required&quot;: [
                &quot;equipmentId&quot;, &quot;mocked&quot;, &quot;observation&quot;, &quot;recommendation&quot;
              ]
            }
            &quot;&quot;&quot;);

        EveMessageResponse response = await session.SendAsync(
            EveMessageContent.FromText(
                &quot;Explain the fictional inspection for pump-demo-01. &quot; +
                &quot;Return the requested structured summary.&quot;),
            new EveTurnOptions { OutputSchema = schema.RootElement.Clone() },
            cancellationToken);

        EveTurnOutcome outcome =
            await response.GetOutcomeAsync(cancellationToken);

        if (outcome.Status == EveTurnStatus.Failed)
        {
            throw new InvalidOperationException(
                $&quot;Agent session {outcome.SessionId} failed; &quot; +
                $&quot;received {outcome.Events.Count} events.&quot;);
        }

        if (outcome.InputRequests.Count != 0)
        {
            throw new InvalidOperationException(
                $&quot;Agent session {outcome.SessionId} requested unexpected human input.&quot;);
        }

        EquipmentSummary summary = outcome.DeserializeData(
            EquipmentJsonContext.Default.EquipmentSummary)
            ?? throw new InvalidOperationException(
                $&quot;Agent session {outcome.SessionId} emitted no typed result.&quot;);

        if (summary.EquipmentId != &quot;pump-demo-01&quot; ||
            !summary.Mocked ||
            string.IsNullOrWhiteSpace(summary.Observation) ||
            string.IsNullOrWhiteSpace(summary.Recommendation))
        {
            throw new InvalidOperationException(
                $&quot;Agent session {outcome.SessionId} returned invalid demo data.&quot;);
        }

        return new EquipmentAnswer(outcome.SessionId, summary);
    }
}
</code></pre>
</div>
<p>The source-generated context supplies <code>EquipmentJsonContext.Default.EquipmentSummary</code> to the exact <code>DeserializeData</code> API exposed by the tagged outcome class (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveTurnOutcome.cs" :target="_blank" rel="noopener">outcome source</a>). The configured <a href="https://learn.microsoft.com/en-us/dotnet/api/system.text.json.serialization.jsonsourcegenerationoptionsattribute.propertynamingpolicy?view=net-10.0" :target="_blank" rel="noopener">camel-case property naming policy</a> maps JSON <code>equipmentId</code> to the record's <code>EquipmentId</code> contract without asking the agent to emit .NET property casing.</p>
<p>The schema constrains <code>equipmentId</code> and <code>mocked</code> to this scenario, rather than merely declaring their primitive types. The application then checks those values again. That duplication is deliberate: the application's acceptance rule should remain visible even when the downstream contract is violated.</p>
<p>There are three distinct checks here. The schema describes the requested wire shape. Source-generated metadata describes how JSON becomes a .NET value. The final conditional describes what this particular application accepts. Keeping them separate prevents a common misunderstanding: a deserialized object can still be inappropriate for the requested operation. A nonempty recommendation is also not evidence that a real machine is safe. The fictional-data marker belongs in the returned value so downstream consumers can preserve that distinction.</p>
<p>The <code>EquipmentAnswer</code> wrapper intentionally keeps the session identifier outside the summary. The identifier describes the agent interaction, while the summary describes the fictional inspection. This organization lets an application associate diagnostics with the request without asking the model to invent or echo the server's identity. It also avoids placing transport metadata into the equipment description itself.</p>
<p>A streamed <code>session.failed</code> produces <code>EveTurnStatus.Failed</code> in the aggregated outcome instead of necessarily throwing a transport exception (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/errors.md" :target="_blank" rel="noopener">failure documentation</a>). Checking status before reading typed data avoids presenting an earlier partial result from a failed turn as success.</p>
<p>The status enum also includes <code>Waiting</code>, so this example does not equate a conversation waiting for another message with a terminal failure (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveTurnStatus.cs" :target="_blank" rel="noopener">status source</a>). It requires an actual structured value and rejects any observed human-input request in this deliberately input-free scenario. It does not implement an approval workflow.</p>
<p>Missing data is not replaced with a default record. The deserializer returns a default value when no structured result was emitted, which makes the explicit null check important (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveTurnOutcome.cs" :target="_blank" rel="noopener">deserialization implementation</a>). Malformed JSON or incompatible values remain exceptions, not fictional successful summaries.</p>
<h2 id="vercel-eve-tutorial-wire-the-asp.net-core-endpoint">Vercel Eve Tutorial: Wire the ASP.NET Core Endpoint</h2>
<p>The adapter keeps its <code>HttpClient</code> alive through the awaited outcome aggregation, matching the caller-owned transport requirement (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/getting-started.md" :target="_blank" rel="noopener">getting-started ownership contract</a>). Disposing transport immediately after <code>SendAsync</code> would put an active response at risk.</p>
<p>The named-client registration keeps HTTP policy in application startup. For the underlying lifecycle concepts, see <a href="https://www.devleader.ca/2026/06/27/ihttpclientfactory-in-net-named-clients-typed-clients-and-di-patterns">IHttpClientFactory named clients and DI patterns</a>. We do not store a request-specific transport in a singleton or return an unconsumed response beyond its owning scope.</p>
<p>Save this complete file as <code>EquipmentWeb/Program.cs</code>:</p>
<div class="position-relative"><button class="btn btn-sm position-absolute top-0 end-0 m-2 border border-primary text-primary copy-btn"
        type="button"
        aria-label="Copy code"
        onclick="navigator.clipboard.writeText(this.parentElement.querySelector('pre code').textContent)">
        <i class="copy" aria-hidden="true"></i>
</button>
<pre><code class="language-csharp">using EquipmentWeb;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpClient(&quot;eve&quot;, transport =&gt;
{
    transport.Timeout = TimeSpan.FromSeconds(90);
});
builder.Services.AddScoped&lt;EquipmentAgent&gt;();
builder.Services.AddProblemDetails();
builder.Services.ConfigureHttpJsonOptions(options =&gt;
{
    options.SerializerOptions.TypeInfoResolverChain.Insert(
        0, EquipmentJsonContext.Default);
});

WebApplication app = builder.Build();
app.UseExceptionHandler();

app.MapPost(
    &quot;/equipment/{equipmentId}/summary&quot;,
    async Task&lt;IResult&gt; (
        string equipmentId,
        EquipmentAgent agent,
        CancellationToken cancellationToken) =&gt;
    {
        if (equipmentId != &quot;pump-demo-01&quot;)
        {
            return Results.Problem(
                statusCode: StatusCodes.Status400BadRequest,
                title: &quot;Only pump-demo-01 is supported by this synthetic demo.&quot;);
        }

        EquipmentAnswer answer = await agent.ReadAsync(cancellationToken);
        return Results.Json(
            answer, EquipmentJsonContext.Default.EquipmentAnswer);
    });

app.Run();
</code></pre>
</div>
<p>Configure <code>Eve:Host</code> before starting the web application. For a local validation environment, the .NET configuration argument can be passed as <code>--Eve:Host=http://localhost:PORT</code>, replacing <code>PORT</code> with the actual agent listener. It is a server-controlled setting, not a URL supplied by an incoming user request.</p>
<p>After both services are running in that environment, send an empty-body POST to <code>/equipment/pump-demo-01/summary</code> on the web application's reported address. The route supplies the complete application input, so there is no request DTO or JSON body to create. Inspect the returned <code>summary</code> object rather than assuming an assistant's conversational text contains the expected fields. A successful response should remain clearly labeled as mock data.</p>
<p>The endpoint uses POST because asking the agent starts work, even though the sample's equipment data is read-only. The input route permits only the fictional identifier. The application does not accept arbitrary prompts, arbitrary equipment names, or arbitrary destination hosts.</p>
<p>Minimal APIs keep this example compact; controllers provide another organizational option when application structure warrants them. <a href="https://www.devleader.ca/2026/06/02/minimal-api-vs-controllers-in-aspnet-core-which-should-you-use">Minimal APIs versus controllers</a> provides that architectural context without changing the adapter's Eve contract.</p>
<p>Unhandled downstream failures reach the exception middleware rather than a <code>200</code> response containing an invented summary. This teaching example intentionally does not map every upstream status to a public error taxonomy. Production error handling should preserve diagnostic evidence internally while withholding raw downstream bodies from users.</p>
<p>The 90-second <code>HttpClient</code> timeout is an illustrative per-HTTP-operation setting, not an overall endpoint or agent-turn deadline; with <a href="https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpcompletionoption?view=net-10.0" :target="_blank" rel="noopener">ResponseHeadersRead</a>, subsequent stream consumption needs a separate cancellation deadline. This example passes the inbound request cancellation token but does not configure an overall timed deadline. Cancelling that token controls local consumption; it does not itself stop Eve's durable turn (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/errors.md" :target="_blank" rel="noopener">local cancellation contract</a>). Server cancellation is a separate concern outside this walkthrough.</p>
<h2 id="read-the-result-once-and-verify-the-boundary">Read the Result Once and Verify the Boundary</h2>
<p>In this Vercel Eve tutorial, one response has one consumer. <code>GetOutcomeAsync</code> aggregates the response stream, while direct enumeration is an alternative single-use path, not an additional pass over the same response (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveMessageResponse.cs" :target="_blank" rel="noopener">response implementation</a>).</p>
<p>That ownership rule affects logging too. A logger should not independently enumerate the response before the adapter reads it. If you need to inspect collected events afterward, use the outcome already obtained rather than trying to consume the network stream again.</p>
<p>The stream protocol is NDJSON, not Server-Sent Events (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveProtocol.cs" :target="_blank" rel="noopener">protocol constants</a>). General <a href="https://www.devleader.ca/2026/06/30/httpclient-streaming-in-c-httpcompletionoption-readasstreamasync-and-serversent-events">HttpClient streaming concepts</a> are useful background, but an SSE parser is not an interchangeable implementation here.</p>
<p>For validation, exercise the real endpoint with <code>pump-demo-01</code> and inspect the four summary fields plus <code>sessionId</code>. Then use an unsupported identifier and verify an explicit client error. Finally, make the agent unavailable and verify that the application does not return a successful fabricated equipment answer.</p>
<p>Those are suggested checks, not reported test results. A successful HTTP connection alone would still leave structured output, model configuration, and application acceptance behavior unproven. Conversely, a source review can catch a wrong overload without demonstrating a running server.</p>
<p>Use a small verification matrix rather than one screenshot of a happy answer. Check an unknown identifier before the outbound call, absence of typed data after consumption, a malformed result, and a downstream failure. These cases distinguish the application's guardrails from the model's instruction-following. Record the exact client and server versions with any execution evidence; otherwise a later dependency upgrade can make an old result difficult to interpret.</p>
<p>The source review for this Vercel Eve tutorial checks the named overload, the JSON Schema property type, the outcome deserializer, and the single-consumer rule. It does not establish network reachability, successful model generation, or a passing deployment. Those require an actual running pair. Keeping that evidence boundary explicit is more useful than presenting a source-compatible snippet as a verified production integration.</p>
<p>Observe the two processes separately. The .NET endpoint owns the inbound request; the agent owns its execution. A disconnected browser, an ASP.NET timeout, and an agent failure are different events. This distinction prevents a useful tutorial from quietly turning into a misleading promise about recovery.</p>
<p>Before adding automatic retries, think about what another send means. <a href="https://www.devleader.ca/2026/06/29/httpclient-resilience-in-net-10-timeout-retry-and-circuit-breaker-with-microsoftextensionshttpresilience">HttpClient retry and resilience design</a> supplies background for that decision. This example adds no custom retry or reconnect implementation and performs no equipment mutations.</p>
<h2 id="frequently-asked-questions">Frequently Asked Questions</h2>
<p>These questions clarify the boundaries of the read-only example. The answers refer to the pinned API rather than assuming newer preview versions behave identically.</p>
<h3 id="does-this-vercel-eve-tutorial-run-the-agent-inside-asp.net-core">Does this Vercel Eve tutorial run the agent inside ASP.NET Core?</h3>
<p>No. The .NET library consumes Eve's HTTP surface rather than porting its Node runtime (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/README.md" :target="_blank" rel="noopener">client scope</a>). The web application and TypeScript agent remain separately configured services.</p>
<h3 id="is-nexuslabs.eve-an-official-vercel-sdk">Is NexusLabs.Eve an official Vercel SDK?</h3>
<p>No. I maintain the independent client used here. Its public project describes a framework-neutral .NET HTTP client, not an official Vercel runtime distribution (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/README.md" :target="_blank" rel="noopener">project overview</a>).</p>
<h3 id="does-a-c-record-automatically-create-the-output-schema">Does a C# record automatically create the output schema?</h3>
<p>Not in this example. The schema is explicitly passed through <code>EveTurnOptions.OutputSchema</code>, and source-generated metadata is separately supplied for deserialization (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/structured-output.md" :target="_blank" rel="noopener">structured-output documentation</a>). Matching property names is part of the application contract.</p>
<h3 id="can-i-read-live-events-and-then-call-getoutcomeasync">Can I read live events and then call GetOutcomeAsync?</h3>
<p>Not on the same response. The response enforces single-use consumption (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveMessageResponse.cs" :target="_blank" rel="noopener">enumerator source</a>). Choose aggregation or direct enumeration and retain whatever application result that path produces.</p>
<h3 id="why-check-failed-before-deserializing-the-result">Why check Failed before deserializing the result?</h3>
<p>The failure can arrive as a protocol event, with <code>GetOutcomeAsync</code> returning a failed status and preserving events (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/errors.md" :target="_blank" rel="noopener">error contract</a>). Receiving data is not proof that the turn succeeded.</p>
<h3 id="is-this-endpoint-ready-for-public-deployment">Is this endpoint ready for public deployment?</h3>
<p>No. It is a synthetic instructional boundary, not a complete access-control design. Eve is beta and its APIs can change (<a href="https://github.com/vercel/eve/blob/eve%400.63.0/README.md" :target="_blank" rel="noopener">pinned beta notice</a>). Authentication, authorization, deployment configuration, and execution validation remain separate requirements.</p>
<h2 id="keep-the-first-integration-small">Keep the First Integration Small</h2>
<p>The useful result of this Vercel Eve tutorial is a clear division of responsibility. TypeScript configures a fictional agent. ASP.NET Core owns the application endpoint and transport scope. A per-turn schema requests structured output, while application checks decide whether that output is acceptable.</p>
<p>Do not mistake these boundaries for a production safety certification. The examples teach a complete request path without claiming they were executed or expanding into access policy, approval, or recovery. Keeping the first scenario read-only makes those remaining responsibilities easier to see.</p>
]]></description>
      <content:encoded><![CDATA[<p>This <strong>Vercel Eve tutorial</strong> builds a small ASP.NET Core application that asks a separately hosted agent to explain a fictional equipment inspection. The interesting part is not the pump. It is connecting two runtimes without losing track of transport ownership, response consumption, or the difference between receiving JSON and receiving a valid application result.</p>
<p>The application will accept only one synthetic equipment identifier. It will request a structured summary, preserve the agent session identifier in its response, and report downstream failures instead of manufacturing an empty successful answer. There are no maintenance commands, notifications, or production records in this example.</p>
<p>I maintain NexusLabs.Eve, the independent .NET client used in these examples. It is not an official Vercel SDK.</p>
<p>These examples were reviewed against public, tagged source. They have not been compiled or executed for this article. Model access, server startup, and end-to-end execution still need verification in your own validation environment.</p>
<h2 id="vercel-eve-tutorial-establish-the-version-and-service-boundary">Vercel Eve Tutorial: Establish the Version and Service Boundary</h2>
<p>For this Vercel Eve tutorial, use .NET 10 with C# 14 and <code>NexusLabs.Eve</code> version <code>0.1.0-alpha-0014</code>, whose published package targets .NET 10 (<a href="https://www.nuget.org/packages/NexusLabs.Eve/0.1.0-alpha-0014" :target="_blank" rel="noopener">package metadata</a>). The reviewed public client source is tagged <code>v0.1.0-alpha.14</code>, and its compatibility reference is Eve 0.63.0 (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/compatibility.md" :target="_blank" rel="noopener">pinned compatibility documentation</a>).</p>
<p>Do not substitute a floating server version just because the installation command permits it. The client declares accepted agent-info schemas 1 through 4, which is a bounded protocol contract rather than unrestricted future compatibility (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveProtocol.cs" :target="_blank" rel="noopener">protocol source</a>). Keeping the server and client pins together makes the tutorial's API references coherent.</p>
<p>There are two projects. The Eve project contains the agent's instructions and configuration. The ASP.NET Core project receives an application request and calls that agent over HTTP. The .NET client does not move Eve's Node runtime into your web process (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/README.md" :target="_blank" rel="noopener">client scope</a>).</p>
<p>That separation is useful, but it has a cost. A single-process application has fewer deployment boundaries; a remote agent makes its authoring and execution ownership explicit but introduces network failure and version coordination. This walkthrough demonstrates the second shape without arguing that every model request needs it.</p>
<p>Create a directory named <code>EquipmentWeb</code> and save this complete project file as <code>EquipmentWeb.csproj</code>:</p>
<div class="position-relative"><button class="btn btn-sm position-absolute top-0 end-0 m-2 border border-primary text-primary copy-btn"
        type="button"
        aria-label="Copy code"
        onclick="navigator.clipboard.writeText(this.parentElement.querySelector('pre code').textContent)">
        <i class="copy" aria-hidden="true"></i>
</button>
<pre><code class="language-xml">&lt;Project Sdk=&quot;Microsoft.NET.Sdk.Web&quot;&gt;
  &lt;PropertyGroup&gt;
    &lt;TargetFramework&gt;net10.0&lt;/TargetFramework&gt;
    &lt;LangVersion&gt;14.0&lt;/LangVersion&gt;
    &lt;Nullable&gt;enable&lt;/Nullable&gt;
    &lt;ImplicitUsings&gt;enable&lt;/ImplicitUsings&gt;
  &lt;/PropertyGroup&gt;
  &lt;ItemGroup&gt;
    &lt;PackageReference Include=&quot;NexusLabs.Eve&quot; Version=&quot;0.1.0-alpha-0014&quot; /&gt;
  &lt;/ItemGroup&gt;
&lt;/Project&gt;
</code></pre>
</div>
<p>The web SDK supplies the ASP.NET Core application environment. The explicit language and package versions document the assumptions under which the following files are written. There is no additional application framework, generated adapter package, or hidden helper library.</p>
<p>Think of the web project as a translation boundary. An HTTP caller asks for a summary of one known item; the adapter expresses that request to the agent; the endpoint returns a small application-owned response. The caller does not need to understand the agent's event stream to use that endpoint. The cost is that the adapter must define what counts as an acceptable answer rather than forwarding every downstream payload indiscriminately.</p>
<p>Keep the TypeScript project in a separate directory named <code>EquipmentAgent</code>. The remaining examples identify their destination files individually. Neither project should receive the other's source files.</p>
<h2 id="create-a-public-synthetic-read-only-agent">Create a Public, Synthetic Read-Only Agent</h2>
<p>The public client fixture pins Eve 0.63.0, AI SDK 7.0.105, <code>just-bash</code> 3.1.0, and Node 24.x (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/test/fixtures/eve-agent/package.json" :target="_blank" rel="noopener">fixture manifest</a>). This Vercel Eve tutorial uses those dependency pins as its baseline, not the fixture's execution results as proof that our newly authored agent works.</p>
<p>Save this complete file as <code>EquipmentAgent/package.json</code>:</p>
<div class="position-relative"><button class="btn btn-sm position-absolute top-0 end-0 m-2 border border-primary text-primary copy-btn"
        type="button"
        aria-label="Copy code"
        onclick="navigator.clipboard.writeText(this.parentElement.querySelector('pre code').textContent)">
        <i class="copy" aria-hidden="true"></i>
</button>
<pre><code class="language-json">{
  &quot;name&quot;: &quot;synthetic-equipment-agent&quot;,
  &quot;private&quot;: true,
  &quot;type&quot;: &quot;module&quot;,
  &quot;scripts&quot;: {
    &quot;build&quot;: &quot;eve build&quot;,
    &quot;start&quot;: &quot;eve start&quot;
  },
  &quot;dependencies&quot;: {
    &quot;ai&quot;: &quot;7.0.105&quot;,
    &quot;eve&quot;: &quot;0.63.0&quot;,
    &quot;just-bash&quot;: &quot;3.1.0&quot;
  },
  &quot;engines&quot;: {
    &quot;node&quot;: &quot;24.x&quot;
  }
}
</code></pre>
</div>
<p>Here, <code>private</code> prevents accidental npm publication; it does not describe confidential data. All scenario content below is independently authored and fictional. No real equipment service or external account is called to retrieve an inspection.</p>
<p>Eve 0.63.0 uses <code>agent/instructions.md</code> for required instructions and supports <code>agent/agent.ts</code> for optional model configuration (<a href="https://github.com/vercel/eve/blob/eve%400.63.0/README.md" :target="_blank" rel="noopener">pinned authoring layout</a>). Create the <code>agent</code> subdirectory and save the following complete file as <code>EquipmentAgent/agent/agent.ts</code>:</p>
<div class="position-relative"><button class="btn btn-sm position-absolute top-0 end-0 m-2 border border-primary text-primary copy-btn"
        type="button"
        aria-label="Copy code"
        onclick="navigator.clipboard.writeText(this.parentElement.querySelector('pre code').textContent)">
        <i class="copy" aria-hidden="true"></i>
</button>
<pre><code class="language-typescript">import { defineAgent } from &quot;eve&quot;;

export default defineAgent({
  model: &quot;openai/gpt-5.6-luna-fast&quot;,
});
</code></pre>
</div>
<p>The model identifier follows the pinned upstream example, not a claim that this model is available to every account (<a href="https://github.com/vercel/eve/blob/eve%400.63.0/README.md" :target="_blank" rel="noopener">configuration example</a>). Provider or gateway credentials must be configured separately using the server's documented mechanism. Do not copy credentials into these files.</p>
<p>Save the following paragraph, without the quotation marker, as the entire contents of <code>EquipmentAgent/agent/instructions.md</code>:</p>
<blockquote class="blockquote">
<p>You explain synthetic equipment inspection notes. The only equipment is pump-demo-01. Its fictional inspection note says: a protective cover was reported loose. The recommendation is: ask a qualified technician to assess the equipment. Always identify these observations as fictional. Do not infer operating safety, perform actions, access external records, or invent additional equipment. When a structured result is requested, return equipmentId as pump-demo-01, mocked as true, observation as the fictional inspection note, and recommendation as the technician recommendation.</p>
</blockquote>
<p>This is intentionally an instructions-only read-only sample. Adding a database lookup tool would introduce another contract before the HTTP integration is understood. The tradeoff is that the data is fixed, not retrieved from a live inventory. That is appropriate for learning the boundary, but not evidence of a maintenance system.</p>
<p>The manifest provides build and start commands for a validation environment. Dependency restoration and a model-access configuration are prerequisites before using them. Start the pinned server there and use its reported listening address; this article does not assert an unverified default port or a successful startup transcript.</p>
<p>For application architecture background, <a href="https://www.devleader.ca/2026/03/03/building-real-apps-with-github-copilot-sdk-in-c-endtoend-patterns-and-architecture">building agent-backed ASP.NET Core applications</a> illustrates the broader separation between an application endpoint and an agent integration. The protocol used here remains Eve's, not that article's SDK.</p>
<h2 id="request-typed-output-without-inventing-a-success-result">Request Typed Output Without Inventing a Success Result</h2>
<p>The typed result in this Vercel Eve tutorial is <code>EquipmentSummary</code>. It has four fields: an equipment identifier, a marker that the data is mocked, the observation, and the recommendation. These names must agree across the instructions, JSON Schema, and C# serialization metadata.</p>
<p>The client accepts a JSON Schema through <code>EveTurnOptions.OutputSchema</code> on the message turn, with the server remaining authoritative for validation (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/structured-output.md" :target="_blank" rel="noopener">structured-output contract</a>). A C# record is therefore not a substitute for sending a schema, and deserialization alone is not a safety guarantee.</p>
<p>Save this complete file as <code>EquipmentWeb/EquipmentAgent.cs</code>:</p>
<div class="position-relative"><button class="btn btn-sm position-absolute top-0 end-0 m-2 border border-primary text-primary copy-btn"
        type="button"
        aria-label="Copy code"
        onclick="navigator.clipboard.writeText(this.parentElement.querySelector('pre code').textContent)">
        <i class="copy" aria-hidden="true"></i>
</button>
<pre><code class="language-csharp">using System.Text.Json;
using System.Text.Json.Serialization;
using NexusLabs.Eve;

namespace EquipmentWeb;

public sealed record EquipmentSummary(
    string EquipmentId,
    bool Mocked,
    string Observation,
    string Recommendation);

public sealed record EquipmentAnswer(
    string SessionId,
    EquipmentSummary Summary);

[JsonSourceGenerationOptions(
    PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
[JsonSerializable(typeof(EquipmentSummary))]
[JsonSerializable(typeof(EquipmentAnswer))]
internal partial class EquipmentJsonContext : JsonSerializerContext
{
}

public sealed class EquipmentAgent(
    IHttpClientFactory transportFactory,
    IConfiguration configuration)
{
    public async Task&lt;EquipmentAnswer&gt; ReadAsync(
        CancellationToken cancellationToken)
    {
        string host = configuration[&quot;Eve:Host&quot;]
            ?? throw new InvalidOperationException(&quot;Eve:Host is required.&quot;);

        using HttpClient transport = transportFactory.CreateClient(&quot;eve&quot;);
        EveClient client = new(transport, new EveClientOptions(host));
        EveSession session = client.CreateSession();

        using JsonDocument schema = JsonDocument.Parse(&quot;&quot;&quot;
            {
              &quot;type&quot;: &quot;object&quot;,
              &quot;additionalProperties&quot;: false,
              &quot;properties&quot;: {
                &quot;equipmentId&quot;: {
                  &quot;type&quot;: &quot;string&quot;,
                  &quot;enum&quot;: [&quot;pump-demo-01&quot;]
                },
                &quot;mocked&quot;: {
                  &quot;type&quot;: &quot;boolean&quot;,
                  &quot;enum&quot;: [true]
                },
                &quot;observation&quot;: { &quot;type&quot;: &quot;string&quot;, &quot;minLength&quot;: 1 },
                &quot;recommendation&quot;: { &quot;type&quot;: &quot;string&quot;, &quot;minLength&quot;: 1 }
              },
              &quot;required&quot;: [
                &quot;equipmentId&quot;, &quot;mocked&quot;, &quot;observation&quot;, &quot;recommendation&quot;
              ]
            }
            &quot;&quot;&quot;);

        EveMessageResponse response = await session.SendAsync(
            EveMessageContent.FromText(
                &quot;Explain the fictional inspection for pump-demo-01. &quot; +
                &quot;Return the requested structured summary.&quot;),
            new EveTurnOptions { OutputSchema = schema.RootElement.Clone() },
            cancellationToken);

        EveTurnOutcome outcome =
            await response.GetOutcomeAsync(cancellationToken);

        if (outcome.Status == EveTurnStatus.Failed)
        {
            throw new InvalidOperationException(
                $&quot;Agent session {outcome.SessionId} failed; &quot; +
                $&quot;received {outcome.Events.Count} events.&quot;);
        }

        if (outcome.InputRequests.Count != 0)
        {
            throw new InvalidOperationException(
                $&quot;Agent session {outcome.SessionId} requested unexpected human input.&quot;);
        }

        EquipmentSummary summary = outcome.DeserializeData(
            EquipmentJsonContext.Default.EquipmentSummary)
            ?? throw new InvalidOperationException(
                $&quot;Agent session {outcome.SessionId} emitted no typed result.&quot;);

        if (summary.EquipmentId != &quot;pump-demo-01&quot; ||
            !summary.Mocked ||
            string.IsNullOrWhiteSpace(summary.Observation) ||
            string.IsNullOrWhiteSpace(summary.Recommendation))
        {
            throw new InvalidOperationException(
                $&quot;Agent session {outcome.SessionId} returned invalid demo data.&quot;);
        }

        return new EquipmentAnswer(outcome.SessionId, summary);
    }
}
</code></pre>
</div>
<p>The source-generated context supplies <code>EquipmentJsonContext.Default.EquipmentSummary</code> to the exact <code>DeserializeData</code> API exposed by the tagged outcome class (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveTurnOutcome.cs" :target="_blank" rel="noopener">outcome source</a>). The configured <a href="https://learn.microsoft.com/en-us/dotnet/api/system.text.json.serialization.jsonsourcegenerationoptionsattribute.propertynamingpolicy?view=net-10.0" :target="_blank" rel="noopener">camel-case property naming policy</a> maps JSON <code>equipmentId</code> to the record's <code>EquipmentId</code> contract without asking the agent to emit .NET property casing.</p>
<p>The schema constrains <code>equipmentId</code> and <code>mocked</code> to this scenario, rather than merely declaring their primitive types. The application then checks those values again. That duplication is deliberate: the application's acceptance rule should remain visible even when the downstream contract is violated.</p>
<p>There are three distinct checks here. The schema describes the requested wire shape. Source-generated metadata describes how JSON becomes a .NET value. The final conditional describes what this particular application accepts. Keeping them separate prevents a common misunderstanding: a deserialized object can still be inappropriate for the requested operation. A nonempty recommendation is also not evidence that a real machine is safe. The fictional-data marker belongs in the returned value so downstream consumers can preserve that distinction.</p>
<p>The <code>EquipmentAnswer</code> wrapper intentionally keeps the session identifier outside the summary. The identifier describes the agent interaction, while the summary describes the fictional inspection. This organization lets an application associate diagnostics with the request without asking the model to invent or echo the server's identity. It also avoids placing transport metadata into the equipment description itself.</p>
<p>A streamed <code>session.failed</code> produces <code>EveTurnStatus.Failed</code> in the aggregated outcome instead of necessarily throwing a transport exception (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/errors.md" :target="_blank" rel="noopener">failure documentation</a>). Checking status before reading typed data avoids presenting an earlier partial result from a failed turn as success.</p>
<p>The status enum also includes <code>Waiting</code>, so this example does not equate a conversation waiting for another message with a terminal failure (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveTurnStatus.cs" :target="_blank" rel="noopener">status source</a>). It requires an actual structured value and rejects any observed human-input request in this deliberately input-free scenario. It does not implement an approval workflow.</p>
<p>Missing data is not replaced with a default record. The deserializer returns a default value when no structured result was emitted, which makes the explicit null check important (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveTurnOutcome.cs" :target="_blank" rel="noopener">deserialization implementation</a>). Malformed JSON or incompatible values remain exceptions, not fictional successful summaries.</p>
<h2 id="vercel-eve-tutorial-wire-the-asp.net-core-endpoint">Vercel Eve Tutorial: Wire the ASP.NET Core Endpoint</h2>
<p>The adapter keeps its <code>HttpClient</code> alive through the awaited outcome aggregation, matching the caller-owned transport requirement (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/getting-started.md" :target="_blank" rel="noopener">getting-started ownership contract</a>). Disposing transport immediately after <code>SendAsync</code> would put an active response at risk.</p>
<p>The named-client registration keeps HTTP policy in application startup. For the underlying lifecycle concepts, see <a href="https://www.devleader.ca/2026/06/27/ihttpclientfactory-in-net-named-clients-typed-clients-and-di-patterns">IHttpClientFactory named clients and DI patterns</a>. We do not store a request-specific transport in a singleton or return an unconsumed response beyond its owning scope.</p>
<p>Save this complete file as <code>EquipmentWeb/Program.cs</code>:</p>
<div class="position-relative"><button class="btn btn-sm position-absolute top-0 end-0 m-2 border border-primary text-primary copy-btn"
        type="button"
        aria-label="Copy code"
        onclick="navigator.clipboard.writeText(this.parentElement.querySelector('pre code').textContent)">
        <i class="copy" aria-hidden="true"></i>
</button>
<pre><code class="language-csharp">using EquipmentWeb;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpClient(&quot;eve&quot;, transport =&gt;
{
    transport.Timeout = TimeSpan.FromSeconds(90);
});
builder.Services.AddScoped&lt;EquipmentAgent&gt;();
builder.Services.AddProblemDetails();
builder.Services.ConfigureHttpJsonOptions(options =&gt;
{
    options.SerializerOptions.TypeInfoResolverChain.Insert(
        0, EquipmentJsonContext.Default);
});

WebApplication app = builder.Build();
app.UseExceptionHandler();

app.MapPost(
    &quot;/equipment/{equipmentId}/summary&quot;,
    async Task&lt;IResult&gt; (
        string equipmentId,
        EquipmentAgent agent,
        CancellationToken cancellationToken) =&gt;
    {
        if (equipmentId != &quot;pump-demo-01&quot;)
        {
            return Results.Problem(
                statusCode: StatusCodes.Status400BadRequest,
                title: &quot;Only pump-demo-01 is supported by this synthetic demo.&quot;);
        }

        EquipmentAnswer answer = await agent.ReadAsync(cancellationToken);
        return Results.Json(
            answer, EquipmentJsonContext.Default.EquipmentAnswer);
    });

app.Run();
</code></pre>
</div>
<p>Configure <code>Eve:Host</code> before starting the web application. For a local validation environment, the .NET configuration argument can be passed as <code>--Eve:Host=http://localhost:PORT</code>, replacing <code>PORT</code> with the actual agent listener. It is a server-controlled setting, not a URL supplied by an incoming user request.</p>
<p>After both services are running in that environment, send an empty-body POST to <code>/equipment/pump-demo-01/summary</code> on the web application's reported address. The route supplies the complete application input, so there is no request DTO or JSON body to create. Inspect the returned <code>summary</code> object rather than assuming an assistant's conversational text contains the expected fields. A successful response should remain clearly labeled as mock data.</p>
<p>The endpoint uses POST because asking the agent starts work, even though the sample's equipment data is read-only. The input route permits only the fictional identifier. The application does not accept arbitrary prompts, arbitrary equipment names, or arbitrary destination hosts.</p>
<p>Minimal APIs keep this example compact; controllers provide another organizational option when application structure warrants them. <a href="https://www.devleader.ca/2026/06/02/minimal-api-vs-controllers-in-aspnet-core-which-should-you-use">Minimal APIs versus controllers</a> provides that architectural context without changing the adapter's Eve contract.</p>
<p>Unhandled downstream failures reach the exception middleware rather than a <code>200</code> response containing an invented summary. This teaching example intentionally does not map every upstream status to a public error taxonomy. Production error handling should preserve diagnostic evidence internally while withholding raw downstream bodies from users.</p>
<p>The 90-second <code>HttpClient</code> timeout is an illustrative per-HTTP-operation setting, not an overall endpoint or agent-turn deadline; with <a href="https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpcompletionoption?view=net-10.0" :target="_blank" rel="noopener">ResponseHeadersRead</a>, subsequent stream consumption needs a separate cancellation deadline. This example passes the inbound request cancellation token but does not configure an overall timed deadline. Cancelling that token controls local consumption; it does not itself stop Eve's durable turn (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/errors.md" :target="_blank" rel="noopener">local cancellation contract</a>). Server cancellation is a separate concern outside this walkthrough.</p>
<h2 id="read-the-result-once-and-verify-the-boundary">Read the Result Once and Verify the Boundary</h2>
<p>In this Vercel Eve tutorial, one response has one consumer. <code>GetOutcomeAsync</code> aggregates the response stream, while direct enumeration is an alternative single-use path, not an additional pass over the same response (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveMessageResponse.cs" :target="_blank" rel="noopener">response implementation</a>).</p>
<p>That ownership rule affects logging too. A logger should not independently enumerate the response before the adapter reads it. If you need to inspect collected events afterward, use the outcome already obtained rather than trying to consume the network stream again.</p>
<p>The stream protocol is NDJSON, not Server-Sent Events (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveProtocol.cs" :target="_blank" rel="noopener">protocol constants</a>). General <a href="https://www.devleader.ca/2026/06/30/httpclient-streaming-in-c-httpcompletionoption-readasstreamasync-and-serversent-events">HttpClient streaming concepts</a> are useful background, but an SSE parser is not an interchangeable implementation here.</p>
<p>For validation, exercise the real endpoint with <code>pump-demo-01</code> and inspect the four summary fields plus <code>sessionId</code>. Then use an unsupported identifier and verify an explicit client error. Finally, make the agent unavailable and verify that the application does not return a successful fabricated equipment answer.</p>
<p>Those are suggested checks, not reported test results. A successful HTTP connection alone would still leave structured output, model configuration, and application acceptance behavior unproven. Conversely, a source review can catch a wrong overload without demonstrating a running server.</p>
<p>Use a small verification matrix rather than one screenshot of a happy answer. Check an unknown identifier before the outbound call, absence of typed data after consumption, a malformed result, and a downstream failure. These cases distinguish the application's guardrails from the model's instruction-following. Record the exact client and server versions with any execution evidence; otherwise a later dependency upgrade can make an old result difficult to interpret.</p>
<p>The source review for this Vercel Eve tutorial checks the named overload, the JSON Schema property type, the outcome deserializer, and the single-consumer rule. It does not establish network reachability, successful model generation, or a passing deployment. Those require an actual running pair. Keeping that evidence boundary explicit is more useful than presenting a source-compatible snippet as a verified production integration.</p>
<p>Observe the two processes separately. The .NET endpoint owns the inbound request; the agent owns its execution. A disconnected browser, an ASP.NET timeout, and an agent failure are different events. This distinction prevents a useful tutorial from quietly turning into a misleading promise about recovery.</p>
<p>Before adding automatic retries, think about what another send means. <a href="https://www.devleader.ca/2026/06/29/httpclient-resilience-in-net-10-timeout-retry-and-circuit-breaker-with-microsoftextensionshttpresilience">HttpClient retry and resilience design</a> supplies background for that decision. This example adds no custom retry or reconnect implementation and performs no equipment mutations.</p>
<h2 id="frequently-asked-questions">Frequently Asked Questions</h2>
<p>These questions clarify the boundaries of the read-only example. The answers refer to the pinned API rather than assuming newer preview versions behave identically.</p>
<h3 id="does-this-vercel-eve-tutorial-run-the-agent-inside-asp.net-core">Does this Vercel Eve tutorial run the agent inside ASP.NET Core?</h3>
<p>No. The .NET library consumes Eve's HTTP surface rather than porting its Node runtime (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/README.md" :target="_blank" rel="noopener">client scope</a>). The web application and TypeScript agent remain separately configured services.</p>
<h3 id="is-nexuslabs.eve-an-official-vercel-sdk">Is NexusLabs.Eve an official Vercel SDK?</h3>
<p>No. I maintain the independent client used here. Its public project describes a framework-neutral .NET HTTP client, not an official Vercel runtime distribution (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/README.md" :target="_blank" rel="noopener">project overview</a>).</p>
<h3 id="does-a-c-record-automatically-create-the-output-schema">Does a C# record automatically create the output schema?</h3>
<p>Not in this example. The schema is explicitly passed through <code>EveTurnOptions.OutputSchema</code>, and source-generated metadata is separately supplied for deserialization (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/structured-output.md" :target="_blank" rel="noopener">structured-output documentation</a>). Matching property names is part of the application contract.</p>
<h3 id="can-i-read-live-events-and-then-call-getoutcomeasync">Can I read live events and then call GetOutcomeAsync?</h3>
<p>Not on the same response. The response enforces single-use consumption (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/src/NexusLabs.Eve/EveMessageResponse.cs" :target="_blank" rel="noopener">enumerator source</a>). Choose aggregation or direct enumeration and retain whatever application result that path produces.</p>
<h3 id="why-check-failed-before-deserializing-the-result">Why check Failed before deserializing the result?</h3>
<p>The failure can arrive as a protocol event, with <code>GetOutcomeAsync</code> returning a failed status and preserving events (<a href="https://github.com/ncosentino/eve-client/blob/v0.1.0-alpha.14/docs/errors.md" :target="_blank" rel="noopener">error contract</a>). Receiving data is not proof that the turn succeeded.</p>
<h3 id="is-this-endpoint-ready-for-public-deployment">Is this endpoint ready for public deployment?</h3>
<p>No. It is a synthetic instructional boundary, not a complete access-control design. Eve is beta and its APIs can change (<a href="https://github.com/vercel/eve/blob/eve%400.63.0/README.md" :target="_blank" rel="noopener">pinned beta notice</a>). Authentication, authorization, deployment configuration, and execution validation remain separate requirements.</p>
<h2 id="keep-the-first-integration-small">Keep the First Integration Small</h2>
<p>The useful result of this Vercel Eve tutorial is a clear division of responsibility. TypeScript configures a fictional agent. ASP.NET Core owns the application endpoint and transport scope. A per-turn schema requests structured output, while application checks decide whether that output is acceptable.</p>
<p>Do not mistake these boundaries for a production safety certification. The examples teach a complete request path without claiming they were executed or expanding into access policy, approval, or recovery. Keeping the first scenario read-only makes those remaining responsibilities easier to see.</p>
]]></content:encoded>
      <media:content url="https://devleader-d2f9ggbjfpdqcka7.z01.azurefd.net/media/01-vercel-eve-tutorial-csharp-aspnet-core-client-hero-20261001-v2.webp" />
    </item>
    <item>
      <guid isPermaLink="false">6583d54d-d722-4f28-b75f-5ec6b42cc45f</guid>
      <link>https://www.devleader.ca/2026/10/04/oneonone-meetings-a-practical-guide-for-engineering-managers</link>
      <category>one-on-one meetings</category>
      <category>engineering management</category>
      <category>leadership</category>
      <category>career conversations</category>
      <title>One-on-One Meetings: A Practical Guide for Engineering Managers</title>
      <pubDate>Sun, 04 Oct 2026 12:00:00 Z</pubDate>
      <description><![CDATA[<p>One-on-one meetings are recurring conversations between a manager and an employee that create room for individual support, feedback, and direction. For engineering managers, they are an opportunity to understand the person behind the work -- not just the state of a project. But putting a meeting on the calendar does not tell either person how to make that time useful.</p>
<p>A new team member might need context. Someone else might want to think through a difficult interaction. Another person might want clearer expectations about what comes next. These are different needs, and treating them as interchangeable is where a supposedly helpful meeting can start feeling like another obligation.</p>
<p>My approach is to give people room to steer while staying accountable for supporting them. That means listening, noticing what we have neglected, and following through. It does not mean requiring everyone to arrive with the same agenda or asking people to disclose their private lives.</p>
<h2 id="what-one-on-one-meetings-are-for">What One-on-One Meetings Are For</h2>
<p>The purpose is broader than collecting information. An engineering manager needs to understand what is happening across a team, but team-level awareness does not automatically translate into individual support. Knowing that a project is progressing tells you little about whether someone understands their responsibilities, needs feedback, or feels stuck working with another team.</p>
<p>One-on-one meetings give that individual context somewhere to go. They can help you understand what the person needs from you and what you need to clarify together. The useful outcome might be a decision, a better understanding of a problem, or agreement about something to revisit. It need not be a new task every time.</p>
<p>I discuss this flexibility in <a href="https://www.youtube.com/watch?v=yuvatLN9nlo&amp;t=123s" :target="_blank" rel="noopener">my Code Commute episode about using one-on-one time</a>. There is no single correct conversation for every person at every point in their career. A meeting can be useful without resembling the previous week's discussion.</p>
<p>That is not an argument against preparation. It is an argument against confusing preparation with compliance. Look at what the discussion makes possible for that individual:</p>
<ul>
<li>A tidy agenda is not proof that someone received support.</li>
<li>An informal conversation is not proof that time was wasted.</li>
</ul>
<p>The conversation's value is not determined by how polished it looks from the outside.</p>
<h2 id="share-the-conversation-not-the-managers-accountability">Share the Conversation, Not the Manager's Accountability</h2>
<p>I like opening the floor to what is top of mind for the employee. That leaves room for something I might not have anticipated instead of making my priorities the only available topics. Someone closest to a problem may see an important detail that never appeared in a team update.</p>
<p>Some people bring notes and clear topics. Others do not. In <a href="https://www.youtube.com/watch?v=2H5kT5w2XuM&amp;t=272s" :target="_blank" rel="noopener">my discussion about supporting new direct reports</a>, I explain why I would not build a system that depends on every person bringing that structure. What works well with one employee may not work with the next.</p>
<p>Employee agency still matters. People can identify concerns, ask for context, or say that the current conversation is not helping. But the manager has authority and responsibilities the employee does not share equally. Calling it an employee-led meeting should not become a way to blame them when feedback or support never arrives.</p>
<p>As the manager, you can notice that a subject has disappeared and make room for it. Two responsibilities remain yours even when the employee is steering:</p>
<ul>
<li>Bring relevant observations rather than wait indefinitely for a request.</li>
<li>Notice neglected subjects instead of assuming silence means they do not matter.</li>
</ul>
<p>With a quieter person, that is particularly important: a short answer does not establish that they are disengaged or that they have no needs.</p>
<p>This connects to a broader question in <a href="https://www.devleader.ca/2023/03/13/handson-management-how-to-lead-engineers-to-success">hands-on management</a>: how do you stay close enough to help without making everything depend on your intervention? In the meeting, give people room to explain their situation before deciding what your involvement should look like.</p>
<h2 id="let-topics-change-as-needs-change">Let Topics Change as Needs Change</h2>
<p>A helpful conversation is responsive to the person and the moment. Someone joining a team may need an explanation of how work fits together. Someone comfortable in the role may care more about a collaboration problem or a responsibility they would like to explore. The same person can need different things a month later.</p>
<p>That is why I do not treat technical or work-related questions as automatically inappropriate. If the manager has relevant context, asking them can be a sensible use of time. The question is whether the conversation addresses a real need or is simply filling the space because neither person knows what else is possible.</p>
<p>Consider two hypothetical situations where different conversations could be useful:</p>
<ul>
<li>An engineer understands their assignment but not why it matters to the business.</li>
<li>An engineer understands the business purpose but needs clarity about their own responsibilities.</li>
</ul>
<p>Talking through the missing context could help either person make decisions. Neither situation is a reason to require every meeting to include a business briefing.</p>
<p>Learning can also be a useful subject. A person may want to understand an area outside their current work or discuss how to develop a skill through actual responsibilities. The distinction between <a href="https://www.devleader.ca/2023/09/27/workplace-based-learning-how-to-balance-learning-at-work-vs-home">learning at work and learning at home</a> is relevant here: development does not have to be framed entirely as something the employee does after hours.</p>
<p>The goal is not to rotate through a compulsory menu. It is to avoid letting habit decide every conversation. Notice what is useful now and what has been absent long enough to deserve attention.</p>
<h2 id="keep-one-on-one-meetings-from-becoming-only-status-updates">Keep One-on-One Meetings From Becoming Only Status Updates</h2>
<p>There is nothing inherently wrong with discussing project progress. During an intense period, the project may genuinely be what someone wants to talk about. A blanket prohibition on status can make you dismiss the very thing occupying their attention.</p>
<p>The problem is the recurring pattern where status becomes the entire relationship. If each conversation repeats information available elsewhere and ends there, feedback, concerns, and future direction may never get space. You know what shipped, but not what support the person needed along the way.</p>
<p>A hypothetical example helps show the distinction. An employee describing a delay might just be reporting a date. They might instead be trying to explain that two teams have incompatible expectations. The first could belong in a routine update; the second may deserve a conversation about context and support. Listen before sorting it into a category.</p>
<p>Where routine updates already have a suitable channel, you can avoid duplicating them in full. Where they do not, that is a communication gap to understand rather than a reason to reprimand the employee. The manager should help establish a workable alternative.</p>
<p>You can also acknowledge the current pressure while agreeing to revisit a neglected subject. That keeps flexibility from becoming permanent drift. Keep the distinction clear:</p>
<ul>
<li>Work discussion can address a real need, even when it includes status.</li>
<li>Repeated status-only discussion can leave other needs unaddressed.</li>
</ul>
<p>The issue is not whether a particular week included status; it is whether the recurring conversation still serves the person.</p>
<h2 id="listen-before-turning-every-concern-into-an-answer">Listen Before Turning Every Concern Into an Answer</h2>
<p>As engineering managers, we can be eager to help by offering solutions. I know I need to practice listening more and asking questions instead of immediately jumping to answers. Being able to respond quickly is not the same as understanding what someone needs.</p>
<p>If you consume the discussion explaining your response, the employee has little room left to examine their own thinking. Sometimes helping them consider options is more useful than handing over your preferred answer. Sometimes they need information or a decision that only you can provide. Neither approach should become an automatic rule.</p>
<p>Consider a hypothetical disagreement about ownership. Before proposing a resolution, you might ask what each side believes they own and where that understanding diverged. That gives you a better starting point than assuming the issue is reluctance to collaborate.</p>
<p>My earlier article on <a href="https://www.devleader.ca/2013/08/05/listen-first-the-human-sounding-board">being a human sounding board</a> explores listening as a leadership practice. It is an earlier perspective, not a prescribed meeting format. The useful connection is making room for someone else's thinking rather than treating your response as the centerpiece.</p>
<p>Helping also requires clarity about the request. Different needs can sit behind the same concern:</p>
<ul>
<li>Understanding the situation without deciding on an intervention yet.</li>
<li>Thinking through options together.</li>
<li>Taking an action that needs the manager's involvement.</li>
</ul>
<p>These are distinctions to explore, not choices the employee must settle before speaking. They do not need a complete solution before raising a problem. You can work together to clarify what help would mean.</p>
<h2 id="discuss-feedback-and-direction-without-making-every-week-a-verdict">Discuss Feedback and Direction Without Making Every Week a Verdict</h2>
<p>Feedback and career direction belong in the relationship, but they are not the same conversation. Feedback can concern something recent and specific. Direction asks whether the work and opportunities fit where the person wants to go.</p>
<p>A recurring meeting does not require a fresh overall assessment every week. If little has changed, asking for a global verdict repeatedly can produce the same reassurance without much insight. A discussion tied to a particular piece of work has something more concrete to examine.</p>
<p>Managers should not wait for every employee to request that discussion. If you have a relevant observation, bring it. If you need time to think before responding usefully, say so and follow up rather than disguising an incomplete answer as a complete one.</p>
<p>Most people I work with are interested in promotion or career advancement. That is my experience, not a rule about everyone. Start by understanding the individual's goals. Someone may want broader responsibility; someone else may want to deepen their expertise without pursuing the same path.</p>
<p>For example, a manager could ask, &quot;Looking ahead, what kinds of responsibilities would you like to explore?&quot; This is one possible question, not a required script. Leave room for the person's own answer. <a href="https://www.devleader.ca/2024/01/28/informal-leadership-in-software-engineering-what-you-need-to-progress">Informal leadership in software engineering</a> can be relevant when someone is interested in contributing beyond their current assignments, but it should not be imposed as everyone's goal.</p>
<p>Be clear about what you can support and what remains uncertain. Helping someone understand expectations is different from guaranteeing a promotion. Useful guidance should not depend on promising an outcome you cannot control.</p>
<h2 id="protect-the-time-while-allowing-flexibility">Protect the Time While Allowing Flexibility</h2>
<p>Weekly is my usual cadence. The appropriate balance is something the manager and employee work out together, not a universal requirement. The important distinction is between having reliable access to support and merely having a recurring invitation.</p>
<p>Managers should try to protect that time. Exceptions happen, and moving a meeting is not automatically a failure. If you cancel, ideally reschedule or reach a mutual understanding of the circumstances instead of allowing the conversation to disappear without explanation.</p>
<p>Repeated moves can signal that the meeting is not valued, even if that was not your intention. A manager's workload may explain the pressure, but it does not remove the need to support people. The pattern deserves attention rather than another assumption that next week will be easier.</p>
<p>Reliability also includes remembering what was discussed. If each meeting starts from scratch because previous concerns have been forgotten, an unchanged calendar will not solve the problem. Continuity comes from your attention between conversations as well as your presence during them.</p>
<h2 id="remember-commitments-without-documenting-everything">Remember Commitments Without Documenting Everything</h2>
<p>My notes are ad hoc. I use Obsidian or Supernote for my own notes, and agreed shared actions may go in the Teams chat. That describes my practice, not a recommendation to adopt particular products or a standardized system.</p>
<p>The purpose is to remember what matters without losing the conversation while capturing it. A brief reminder about an agreed action can be more useful than extensive notes that are never revisited. If you committed to something, you need a way to remember it that works for you.</p>
<p>Shared actions and private notes are not interchangeable. Not everything belongs in a shared record, especially personal information someone volunteered. Agreeing on a next step does not mean every detail of the discussion needs to be collected alongside it.</p>
<p>I have written about <a href="https://www.devleader.ca/2015/04/21/one-on-one-evolution">how my one-on-one approach evolved</a> in an earlier period. That historical account is not my current notes process. The continuing lesson is to examine whether the approach serves the relationship rather than preserve it simply because you once adopted it.</p>
<p>Follow-through need not mean every problem was solved. It can mean:</p>
<ul>
<li>Reporting what you learned.</li>
<li>Explaining a constraint that affects the next step.</li>
<li>Acknowledging that a commitment is still outstanding.</li>
</ul>
<p>Leaving people to guess what happened is different from being honest about an unresolved issue. That distinction matters when the answer is incomplete, not only when you have good news.</p>
<h2 id="build-connection-without-requiring-personal-disclosure">Build Connection Without Requiring Personal Disclosure</h2>
<p>People are individuals, not interchangeable roles. Personal connection can matter, but it does not have to happen in one specific meeting or through discussion of someone's private life. You can show interest and respect without requiring a personal update.</p>
<p>Sharing is always optional. Trust, respect, and safety may lead someone to volunteer context, but disclosure is not evidence they are participating correctly. If someone chooses not to share, do not interpret that as a relationship problem they must fix.</p>
<p>Treat volunteered personal information as private. Do not turn it into a routine team update or an unnecessary shared note. Respecting that boundary is part of how you behave, not a legal guarantee of confidentiality.</p>
<p>Recording and AI notes are off by default in my one-on-ones. Extremely rare exceptions require both sides opting in because note capture would help them focus on the discussion. Convenience for the manager alone is not a reason to make recording routine. Nor should participation in the meeting be treated as consent to capture it.</p>
<h2 id="frequently-asked-questions">Frequently Asked Questions</h2>
<p>These common questions reflect the same principle: adapt the conversation while keeping support and boundaries intact.</p>
<h3 id="how-often-should-engineering-managers-hold-one-on-one-meetings">How often should engineering managers hold one-on-one meetings?</h3>
<p>Weekly is my usual cadence, but manager and employee should find a workable balance. Protect the agreed time while allowing exceptions. The aim is dependable support, not satisfying a universal frequency rule.</p>
<h3 id="does-every-one-on-one-need-a-prepared-agenda">Does every one-on-one need a prepared agenda?</h3>
<p>No. Preparation can help, and some employees prefer bringing topics or notes. A manager should not rely on everyone doing that. Bring relevant observations yourself and leave room for what matters to the person.</p>
<h3 id="are-status-updates-a-bad-use-of-one-on-one-meetings">Are status updates a bad use of one-on-one meetings?</h3>
<p>Not necessarily. Work can be genuinely top of mind, and discussing it can be useful. Watch for a repeated status-only pattern that crowds out support, feedback, and direction, especially when routine updates already have another suitable channel.</p>
<h3 id="what-if-an-employee-has-very-little-to-say">What if an employee has very little to say?</h3>
<p>Do not equate quietness with disengagement. Make space, offer relevant feedback or context, and learn what works for that individual. The manager's responsibility does not disappear because the employee did not arrive with a topic.</p>
<h3 id="should-employees-discuss-personal-issues-with-their-manager">Should employees discuss personal issues with their manager?</h3>
<p>Only if they choose to. Personal sharing is never mandatory, and a working relationship does not require disclosure. If someone volunteers personal context, treat it as private and avoid collecting or distributing unnecessary details.</p>
<h3 id="should-one-on-ones-be-recorded-or-summarized-with-ai">Should one-on-ones be recorded or summarized with AI?</h3>
<p>My default is no recording or AI notes. Extremely rare mutual opt-in exceptions may help both people focus, but they are not a routine practice, a product recommendation, or a confidentiality guarantee.</p>
<h2 id="a-useful-conversation-can-look-different-each-week">A Useful Conversation Can Look Different Each Week</h2>
<p>One-on-one meetings work best as an adaptable part of the working relationship, not a formula everyone must perform. Give the individual room, stay accountable as the manager, and pay attention to whether your conversations are still meeting real needs.</p>
<p>You do not need a perfect agenda or an answer to every problem. You need enough attention to understand what matters, enough clarity to establish what happens next, and enough continuity that the next conversation builds on the last.</p>
]]></description>
      <content:encoded><![CDATA[<p>One-on-one meetings are recurring conversations between a manager and an employee that create room for individual support, feedback, and direction. For engineering managers, they are an opportunity to understand the person behind the work -- not just the state of a project. But putting a meeting on the calendar does not tell either person how to make that time useful.</p>
<p>A new team member might need context. Someone else might want to think through a difficult interaction. Another person might want clearer expectations about what comes next. These are different needs, and treating them as interchangeable is where a supposedly helpful meeting can start feeling like another obligation.</p>
<p>My approach is to give people room to steer while staying accountable for supporting them. That means listening, noticing what we have neglected, and following through. It does not mean requiring everyone to arrive with the same agenda or asking people to disclose their private lives.</p>
<h2 id="what-one-on-one-meetings-are-for">What One-on-One Meetings Are For</h2>
<p>The purpose is broader than collecting information. An engineering manager needs to understand what is happening across a team, but team-level awareness does not automatically translate into individual support. Knowing that a project is progressing tells you little about whether someone understands their responsibilities, needs feedback, or feels stuck working with another team.</p>
<p>One-on-one meetings give that individual context somewhere to go. They can help you understand what the person needs from you and what you need to clarify together. The useful outcome might be a decision, a better understanding of a problem, or agreement about something to revisit. It need not be a new task every time.</p>
<p>I discuss this flexibility in <a href="https://www.youtube.com/watch?v=yuvatLN9nlo&amp;t=123s" :target="_blank" rel="noopener">my Code Commute episode about using one-on-one time</a>. There is no single correct conversation for every person at every point in their career. A meeting can be useful without resembling the previous week's discussion.</p>
<p>That is not an argument against preparation. It is an argument against confusing preparation with compliance. Look at what the discussion makes possible for that individual:</p>
<ul>
<li>A tidy agenda is not proof that someone received support.</li>
<li>An informal conversation is not proof that time was wasted.</li>
</ul>
<p>The conversation's value is not determined by how polished it looks from the outside.</p>
<h2 id="share-the-conversation-not-the-managers-accountability">Share the Conversation, Not the Manager's Accountability</h2>
<p>I like opening the floor to what is top of mind for the employee. That leaves room for something I might not have anticipated instead of making my priorities the only available topics. Someone closest to a problem may see an important detail that never appeared in a team update.</p>
<p>Some people bring notes and clear topics. Others do not. In <a href="https://www.youtube.com/watch?v=2H5kT5w2XuM&amp;t=272s" :target="_blank" rel="noopener">my discussion about supporting new direct reports</a>, I explain why I would not build a system that depends on every person bringing that structure. What works well with one employee may not work with the next.</p>
<p>Employee agency still matters. People can identify concerns, ask for context, or say that the current conversation is not helping. But the manager has authority and responsibilities the employee does not share equally. Calling it an employee-led meeting should not become a way to blame them when feedback or support never arrives.</p>
<p>As the manager, you can notice that a subject has disappeared and make room for it. Two responsibilities remain yours even when the employee is steering:</p>
<ul>
<li>Bring relevant observations rather than wait indefinitely for a request.</li>
<li>Notice neglected subjects instead of assuming silence means they do not matter.</li>
</ul>
<p>With a quieter person, that is particularly important: a short answer does not establish that they are disengaged or that they have no needs.</p>
<p>This connects to a broader question in <a href="https://www.devleader.ca/2023/03/13/handson-management-how-to-lead-engineers-to-success">hands-on management</a>: how do you stay close enough to help without making everything depend on your intervention? In the meeting, give people room to explain their situation before deciding what your involvement should look like.</p>
<h2 id="let-topics-change-as-needs-change">Let Topics Change as Needs Change</h2>
<p>A helpful conversation is responsive to the person and the moment. Someone joining a team may need an explanation of how work fits together. Someone comfortable in the role may care more about a collaboration problem or a responsibility they would like to explore. The same person can need different things a month later.</p>
<p>That is why I do not treat technical or work-related questions as automatically inappropriate. If the manager has relevant context, asking them can be a sensible use of time. The question is whether the conversation addresses a real need or is simply filling the space because neither person knows what else is possible.</p>
<p>Consider two hypothetical situations where different conversations could be useful:</p>
<ul>
<li>An engineer understands their assignment but not why it matters to the business.</li>
<li>An engineer understands the business purpose but needs clarity about their own responsibilities.</li>
</ul>
<p>Talking through the missing context could help either person make decisions. Neither situation is a reason to require every meeting to include a business briefing.</p>
<p>Learning can also be a useful subject. A person may want to understand an area outside their current work or discuss how to develop a skill through actual responsibilities. The distinction between <a href="https://www.devleader.ca/2023/09/27/workplace-based-learning-how-to-balance-learning-at-work-vs-home">learning at work and learning at home</a> is relevant here: development does not have to be framed entirely as something the employee does after hours.</p>
<p>The goal is not to rotate through a compulsory menu. It is to avoid letting habit decide every conversation. Notice what is useful now and what has been absent long enough to deserve attention.</p>
<h2 id="keep-one-on-one-meetings-from-becoming-only-status-updates">Keep One-on-One Meetings From Becoming Only Status Updates</h2>
<p>There is nothing inherently wrong with discussing project progress. During an intense period, the project may genuinely be what someone wants to talk about. A blanket prohibition on status can make you dismiss the very thing occupying their attention.</p>
<p>The problem is the recurring pattern where status becomes the entire relationship. If each conversation repeats information available elsewhere and ends there, feedback, concerns, and future direction may never get space. You know what shipped, but not what support the person needed along the way.</p>
<p>A hypothetical example helps show the distinction. An employee describing a delay might just be reporting a date. They might instead be trying to explain that two teams have incompatible expectations. The first could belong in a routine update; the second may deserve a conversation about context and support. Listen before sorting it into a category.</p>
<p>Where routine updates already have a suitable channel, you can avoid duplicating them in full. Where they do not, that is a communication gap to understand rather than a reason to reprimand the employee. The manager should help establish a workable alternative.</p>
<p>You can also acknowledge the current pressure while agreeing to revisit a neglected subject. That keeps flexibility from becoming permanent drift. Keep the distinction clear:</p>
<ul>
<li>Work discussion can address a real need, even when it includes status.</li>
<li>Repeated status-only discussion can leave other needs unaddressed.</li>
</ul>
<p>The issue is not whether a particular week included status; it is whether the recurring conversation still serves the person.</p>
<h2 id="listen-before-turning-every-concern-into-an-answer">Listen Before Turning Every Concern Into an Answer</h2>
<p>As engineering managers, we can be eager to help by offering solutions. I know I need to practice listening more and asking questions instead of immediately jumping to answers. Being able to respond quickly is not the same as understanding what someone needs.</p>
<p>If you consume the discussion explaining your response, the employee has little room left to examine their own thinking. Sometimes helping them consider options is more useful than handing over your preferred answer. Sometimes they need information or a decision that only you can provide. Neither approach should become an automatic rule.</p>
<p>Consider a hypothetical disagreement about ownership. Before proposing a resolution, you might ask what each side believes they own and where that understanding diverged. That gives you a better starting point than assuming the issue is reluctance to collaborate.</p>
<p>My earlier article on <a href="https://www.devleader.ca/2013/08/05/listen-first-the-human-sounding-board">being a human sounding board</a> explores listening as a leadership practice. It is an earlier perspective, not a prescribed meeting format. The useful connection is making room for someone else's thinking rather than treating your response as the centerpiece.</p>
<p>Helping also requires clarity about the request. Different needs can sit behind the same concern:</p>
<ul>
<li>Understanding the situation without deciding on an intervention yet.</li>
<li>Thinking through options together.</li>
<li>Taking an action that needs the manager's involvement.</li>
</ul>
<p>These are distinctions to explore, not choices the employee must settle before speaking. They do not need a complete solution before raising a problem. You can work together to clarify what help would mean.</p>
<h2 id="discuss-feedback-and-direction-without-making-every-week-a-verdict">Discuss Feedback and Direction Without Making Every Week a Verdict</h2>
<p>Feedback and career direction belong in the relationship, but they are not the same conversation. Feedback can concern something recent and specific. Direction asks whether the work and opportunities fit where the person wants to go.</p>
<p>A recurring meeting does not require a fresh overall assessment every week. If little has changed, asking for a global verdict repeatedly can produce the same reassurance without much insight. A discussion tied to a particular piece of work has something more concrete to examine.</p>
<p>Managers should not wait for every employee to request that discussion. If you have a relevant observation, bring it. If you need time to think before responding usefully, say so and follow up rather than disguising an incomplete answer as a complete one.</p>
<p>Most people I work with are interested in promotion or career advancement. That is my experience, not a rule about everyone. Start by understanding the individual's goals. Someone may want broader responsibility; someone else may want to deepen their expertise without pursuing the same path.</p>
<p>For example, a manager could ask, &quot;Looking ahead, what kinds of responsibilities would you like to explore?&quot; This is one possible question, not a required script. Leave room for the person's own answer. <a href="https://www.devleader.ca/2024/01/28/informal-leadership-in-software-engineering-what-you-need-to-progress">Informal leadership in software engineering</a> can be relevant when someone is interested in contributing beyond their current assignments, but it should not be imposed as everyone's goal.</p>
<p>Be clear about what you can support and what remains uncertain. Helping someone understand expectations is different from guaranteeing a promotion. Useful guidance should not depend on promising an outcome you cannot control.</p>
<h2 id="protect-the-time-while-allowing-flexibility">Protect the Time While Allowing Flexibility</h2>
<p>Weekly is my usual cadence. The appropriate balance is something the manager and employee work out together, not a universal requirement. The important distinction is between having reliable access to support and merely having a recurring invitation.</p>
<p>Managers should try to protect that time. Exceptions happen, and moving a meeting is not automatically a failure. If you cancel, ideally reschedule or reach a mutual understanding of the circumstances instead of allowing the conversation to disappear without explanation.</p>
<p>Repeated moves can signal that the meeting is not valued, even if that was not your intention. A manager's workload may explain the pressure, but it does not remove the need to support people. The pattern deserves attention rather than another assumption that next week will be easier.</p>
<p>Reliability also includes remembering what was discussed. If each meeting starts from scratch because previous concerns have been forgotten, an unchanged calendar will not solve the problem. Continuity comes from your attention between conversations as well as your presence during them.</p>
<h2 id="remember-commitments-without-documenting-everything">Remember Commitments Without Documenting Everything</h2>
<p>My notes are ad hoc. I use Obsidian or Supernote for my own notes, and agreed shared actions may go in the Teams chat. That describes my practice, not a recommendation to adopt particular products or a standardized system.</p>
<p>The purpose is to remember what matters without losing the conversation while capturing it. A brief reminder about an agreed action can be more useful than extensive notes that are never revisited. If you committed to something, you need a way to remember it that works for you.</p>
<p>Shared actions and private notes are not interchangeable. Not everything belongs in a shared record, especially personal information someone volunteered. Agreeing on a next step does not mean every detail of the discussion needs to be collected alongside it.</p>
<p>I have written about <a href="https://www.devleader.ca/2015/04/21/one-on-one-evolution">how my one-on-one approach evolved</a> in an earlier period. That historical account is not my current notes process. The continuing lesson is to examine whether the approach serves the relationship rather than preserve it simply because you once adopted it.</p>
<p>Follow-through need not mean every problem was solved. It can mean:</p>
<ul>
<li>Reporting what you learned.</li>
<li>Explaining a constraint that affects the next step.</li>
<li>Acknowledging that a commitment is still outstanding.</li>
</ul>
<p>Leaving people to guess what happened is different from being honest about an unresolved issue. That distinction matters when the answer is incomplete, not only when you have good news.</p>
<h2 id="build-connection-without-requiring-personal-disclosure">Build Connection Without Requiring Personal Disclosure</h2>
<p>People are individuals, not interchangeable roles. Personal connection can matter, but it does not have to happen in one specific meeting or through discussion of someone's private life. You can show interest and respect without requiring a personal update.</p>
<p>Sharing is always optional. Trust, respect, and safety may lead someone to volunteer context, but disclosure is not evidence they are participating correctly. If someone chooses not to share, do not interpret that as a relationship problem they must fix.</p>
<p>Treat volunteered personal information as private. Do not turn it into a routine team update or an unnecessary shared note. Respecting that boundary is part of how you behave, not a legal guarantee of confidentiality.</p>
<p>Recording and AI notes are off by default in my one-on-ones. Extremely rare exceptions require both sides opting in because note capture would help them focus on the discussion. Convenience for the manager alone is not a reason to make recording routine. Nor should participation in the meeting be treated as consent to capture it.</p>
<h2 id="frequently-asked-questions">Frequently Asked Questions</h2>
<p>These common questions reflect the same principle: adapt the conversation while keeping support and boundaries intact.</p>
<h3 id="how-often-should-engineering-managers-hold-one-on-one-meetings">How often should engineering managers hold one-on-one meetings?</h3>
<p>Weekly is my usual cadence, but manager and employee should find a workable balance. Protect the agreed time while allowing exceptions. The aim is dependable support, not satisfying a universal frequency rule.</p>
<h3 id="does-every-one-on-one-need-a-prepared-agenda">Does every one-on-one need a prepared agenda?</h3>
<p>No. Preparation can help, and some employees prefer bringing topics or notes. A manager should not rely on everyone doing that. Bring relevant observations yourself and leave room for what matters to the person.</p>
<h3 id="are-status-updates-a-bad-use-of-one-on-one-meetings">Are status updates a bad use of one-on-one meetings?</h3>
<p>Not necessarily. Work can be genuinely top of mind, and discussing it can be useful. Watch for a repeated status-only pattern that crowds out support, feedback, and direction, especially when routine updates already have another suitable channel.</p>
<h3 id="what-if-an-employee-has-very-little-to-say">What if an employee has very little to say?</h3>
<p>Do not equate quietness with disengagement. Make space, offer relevant feedback or context, and learn what works for that individual. The manager's responsibility does not disappear because the employee did not arrive with a topic.</p>
<h3 id="should-employees-discuss-personal-issues-with-their-manager">Should employees discuss personal issues with their manager?</h3>
<p>Only if they choose to. Personal sharing is never mandatory, and a working relationship does not require disclosure. If someone volunteers personal context, treat it as private and avoid collecting or distributing unnecessary details.</p>
<h3 id="should-one-on-ones-be-recorded-or-summarized-with-ai">Should one-on-ones be recorded or summarized with AI?</h3>
<p>My default is no recording or AI notes. Extremely rare mutual opt-in exceptions may help both people focus, but they are not a routine practice, a product recommendation, or a confidentiality guarantee.</p>
<h2 id="a-useful-conversation-can-look-different-each-week">A Useful Conversation Can Look Different Each Week</h2>
<p>One-on-one meetings work best as an adaptable part of the working relationship, not a formula everyone must perform. Give the individual room, stay accountable as the manager, and pay attention to whether your conversations are still meeting real needs.</p>
<p>You do not need a perfect agenda or an answer to every problem. You need enough attention to understand what matters, enough clarity to establish what happens next, and enough continuity that the next conversation builds on the last.</p>
]]></content:encoded>
      <media:content url="https://devleader-d2f9ggbjfpdqcka7.z01.azurefd.net/media/00-one-on-one-meetings-engineering-managers-hero-20261003.webp" />
    </item>
  </channel>
</rss>