Skip to content

feat(lazurio): a thread shows when Buddy sent a message or decided a request, and hides Delegace's wake - #106

Merged
immakermatty merged 6 commits into
mainfrom
agent/DEV-6676-buddy-trace
Oct 23, 2026
Merged

immakermatty merged 6 commits into
mainfrom
agent/DEV-6676-buddy-trace

Conversation

@immakermatty

@immakermatty immakermatty commented Oct 22, 2026 •

Copy link
Copy Markdown

Status: ready for review. Pablo approved the exact head b06ec0015 (2026-10-22T21:44Z, round 4, after the single rebase onto 626cf804 and the rename to Delegace). CI on b06ec0015 is green (attempt 2; attempt 1 hit an upstream network-dependent browser test). The headless browser check passed on b06ec0015 (cs-CZ and en-US). Lazurio/t3code main is frozen until the stable 0.1.1-lazurio.1 is out: this PR merges only after that, and only on Matěj's (immakermatty) decision. A release is separate.

Problem

Root decision 0210, evening addendum, point 5 (HumanAndMachines/Lazurio#598): what Buddy writes or approves in threads and other tools carries the visible mark „Buddy za <jméno uživatele>“. Plan DEV-6676, task-2026-10-20-330 (P4.7) asks for:

  1. A message, an answer and a decision on a permission request sent through the t3 agent CLI by Buddy's session show „Buddy za “ in the thread, while the user's own steps show no mark. With a contract and tests on the real consumer.
  2. The mark cannot be forged: neither a message's content nor a session that does not act for the user as Buddy creates it. Rejection is tested before success.
  3. Ring 0 release and the browser check come later and are not in this PR.

The Buddy lead widened it:

  • This claim is the only way the author of a message sent from the CLI is shown.
  • It also carries the system sender Delegace (called budíček until 2026-10-22), whose wake in Buddy's own thread was first to carry its name instead of "Sent by another agent" (superseded, see below).
  • There is no --sender-label flag.

Today a message from t3 thread send shows "Sent by another agent" whoever sent it, and a decision from t3 request approve looks like the user's own click.

Decision of 2026-10-22 (Matěj, through the question tool, after Pablo approved 54d73abd4; the Publication was stopped for it): Delegace's wake is hidden in the thread, as it is on Telegram. The person sees only Buddy's reply, and the word "Delegace" disappears from the UI in both Czech and English. A visible message from Delegace would show the person the internal plumbing. The server, the claim and the interface stay: the delegace kind, T3CODE_ACTING_SENDER=delegace and the record are internal and now drive the hiding.

Decision of 2026-10-22, evening (Matěj): the mechanism is called Delegace. The claim kind is delegace and the variable T3CODE_ACTING_SENDER=delegace. There is no alias: budicek is now an invalid value. The web hides delegace messages exactly as it hid the earlier kind.

Design

One claim with a closed set of kinds

ActingSender (apps/server/src/lazurio/actingSender.ts) has two kinds:

  • { kind: "buddy", for, forAccusative? }: the user's name, and optionally its Czech form after „za“;
  • { kind: "delegace" }: no names.

A name is 1 to 64 characters, already trimmed. It may not contain control, format (bidi overrides, zero-width), private-use, surrogate or line and paragraph separator characters. There is no free label, which would let any session call itself the user.

Where the claim lives, and how the CLI gets it

It is a signed claim of the session: act, beside the runtime-mode ceiling rtc in the session token (SessionStore.ts).

  • EnvironmentAuth.issueSession takes it, and both authenticateMcpClient and the HTTP and WebSocket authentication return it.
  • It cannot be added to a token issued without it, because the signature then fails.
  • A claim the schema refuses is never issued (the claim fails to encode) and never verified (the token is refused).

The agent commands (t3 thread|request|queue|events) issue their two short sessions themselves from the T3 home (row 21). They now read the claim from their environment and issue both sessions with it:

T3CODE_ACTING_SENDER=buddy T3CODE_ACTING_FOR=<name> [T3CODE_ACTING_FOR_ACCUSATIVE=<name after „za“>] t3 …
T3CODE_ACTING_SENDER=delegace t3 …

Why the environment, and not an issued token or a flag:

  • Buddy and Delegace run t3 as the T3 home's user, through the commands that already issue sessions from the home. A variable set once by the Buddy layer or by Delegace's unit reaches every t3 they run, with no token to store, pass or rotate.
  • A per-command flag would be a second mechanism, and any caller could pass it.
  • No consumer needs a long-lived MCP token with the claim (t3 auth session issue --acting-…), so it is left out.

An empty value counts as unset, as for T3's other variables. Any other invalid value fails the command with invalid_request (exit 1) before anything is read, so a misconfigured Buddy fails loudly instead of acting unmarked. t3 lazurio capabilities reports "actingSender": true.

Where the author is recorded

Whose step is read from the session alone, never from the command or the message's words:

  • an MCP client's invocation carries the claim (McpOAuth.ts copies it to McpInvocationContext.client.actingSender);
  • the client surface runs a dispatch as its session (one actingAs(currentSession) call in ws.ts);
  • an agent inside a T3 thread calls the tools as its thread, never as a client, so it cannot have the claim.

Which steps: a decorator around upstream's orchestrator (recordSteps, one wrap in runtimeLayer.ts, outside the managed Folder's guard) records a step after its own dispatch committed it:

  • a message.dispatch (t3 thread launch, t3 thread send), when that dispatch committed the message;
  • a runtime-request.respond (t3 request respond, approve, deny), when that dispatch committed the request's resolution. A late decision that T3 refuses, because the user decided first, records nothing.
  • T3's own messages (automations, notifications, continuations, another thread's agent) never count, even inside Buddy's dispatch.

Only the dispatch that commits a step marks it. T3 answers a command id it already accepted with the events of the dispatch that committed it (Orchestrator.ts, and EventSink.ts for a duplicate that commits meanwhile). That dispatch may be another session's: the person's own t3 with the same --idempotency-key (T4 keeps the key's command id across runs), or any client that knows the id. The decorator therefore:

  • counts only the step's events in its own thread that are newer than the thread's last event when the dispatch began (getThreadEventSequence, read before dispatching; if it cannot be read, nothing counts);
  • records nothing when another dispatch of the same command id ran at the same time, since that one may have committed the step (one entry per command id while any dispatch of it runs);
  • keeps a step's first record (see Where).

A step can thus lose its mark, when two dispatches of its command id run at once, but it never gets someone else's. All of this is overlay code; no upstream seam was added for it.

The numbers compared are one sequence. A committed event gets its sequence from INSERT INTO orchestration_events … RETURNING sequence (OrchestrationEventStore.appendAgentEvents). A replay reads the same column back (readAgentEvents, rowToV2StoredEvent). getThreadEventSequence is MAX(sequence) of that column over the thread's rows (latestAgentSequence), and it is 0 for a thread without events. t3 thread launch creates its thread in its own dispatch (thread.create), then sends the first message (message.dispatch, command id <launch>:initial-message), both through ThreadManagementService, which takes the decorated orchestrator (runtimeLayer.ts). The message is therefore compared with the new thread's creation events.

Where: one JSON line per step in <state dir>/lazurio/acting-senders/<sha256 of the thread id>.jsonl (file 0600, folder 0700).

  • It survives restarts and updates.
  • T3's database and its migrations stay upstream's, as with the thread tabs' holder files.
  • A step's first line stands: the store appends no second line for it, and reading skips any later one.
  • A line cut short by a crash is skipped. A write that fails loses only the mark, and is logged.

For the web: GET /api/lazurio/acting-senders?threadId=<id> answers {at, messages: [{messageId, sender}], requests: [{requestId, sender}]} with cache-control: no-store, under the scope of reading a thread (orchestration:read).

  • A step is recorded right after its command commits, so the web could see the message before its record. The route therefore waits up to 2 s for the thread's acting dispatches that still run, and at says up to when the answer is complete: never after the start of a dispatch that still runs.
  • The decorator and the route share one store, because both take the same layer.

Rendering in the web

  • Delegace's wake is hidden. A message whose acting sender is delegace is not rendered at all: no bubble and no label. The web never shows the mechanism's name.
  • No flash. A message an agent sent from outside any thread (upstream's "Sent by another agent" without a sending thread) is held back until the acting-senders answer covers it (at). Then Buddy's shows with „Buddy za <forAccusative, else for>“ (English "Buddy for "), Delegace's stays hidden, and one without a record shows upstream's "Sent by another agent".
  • Bounded. The wait is at most 5 s (ACTING_SENDER_WAIT_MS). A message that no answer covers by then shows as upstream shows it. The loader asks once per thread, and once more for rows its answer does not cover, which a dispatch that still ran held back or which appeared while it was out.
  • An older server or an error. A server without the route (an older T3), or one that fails, shows the thread at once as upstream does, and is not asked again for that thread.
  • Never delayed. The person's own messages, T3's, automations', other threads' agents' and every other row never wait.
  • One seam for the rows. useLazurioActingSenderRows filters the thread's rows right after upstream's useStableRows in MessagesTimeline.tsx, so everything that draws them (the list, the minimap, find navigation by row) sees the same rows. Buddy's reply after a hidden wake keeps the turn grouping T3 computed, "Worked for…" included. When nothing is hidden the same array comes back, so upstream's row stability holds.
  • A decided request shows „Buddy za …“ beside its time when Buddy decided it, and nothing otherwise.
  • The language is the browser's (prefersCzech), as for the Environment browser's texts. It covers only threads of the Environment that serves the page; other threads show as upstream. The mobile app (upstream's) keeps upstream's rendering, wakes included. The desktop app wraps the web and should hide and mark the same way; that was not checked.

Where the wake still shows, because the overlay cannot hide it there cleanly:

  • the queue banner above the composer: while a wake waits behind Buddy's running turn, "Queued" counts it and shows its text when expanded. Hiding it would break the queue's reordering, which works by server index;
  • find in the thread (Ctrl+F), which searches the thread's entries rather than the drawn rows, so it can match the wake's text (from the code; not checked in the browser);
  • search across threads, whose snippets come from the server's search;
  • the thread's latest-user-message time, which the wake moves and which orders the sidebar;
  • t3 thread read, where agents see the wake.

Notifications show only the status and the thread's title, never a message's text.

Threat model

  • Content cannot create the mark: it comes from the session. The tests send "Buddy za Tomáše" and "Delegace" as message text, and a command with a forged actingSender field.
  • A session without the claim cannot create it. Browser sessions, MCP clients approved through OAuth, tokens from t3 auth session issue and agents inside threads have none, and a token cannot be given one afterwards.
  • Invalid values are refused at the CLI (before anything is read), when a session is issued, and when a token is verified.
  • Under root decision 0172, issuing a session takes the T3 home, which means the whole Environment's trust. The guarantee is therefore: no mark without a session that acts as Buddy or Delegace. That only Buddy and Delegace run with one is process: who sets the variables.

Variants compared

Variant Verdict
Claim signed into the session token; the CLI reads it from its environment Chosen. One mechanism, verified with the scopes, nothing new to store or rotate.
Per-command flag (--sender-label, --acting-as) Any caller could pass it, and it would be a second mechanism beside the session.
Claim in an overlay store keyed by session id Same trust, but a second source of truth beside the token, and a store the CLI process writes.
Author in the orchestration events and projections (contracts, decider, migration) Atomic with the commit, but it changes upstream's contracts, decider and database, a heavy rebuild cost.
Record in its own table in T3's SQLite Survives restarts, but the fork keeps T3's database and migrations upstream's.
Record in files in the T3 home Chosen. Survives restarts and belongs to the overlay. It is not atomic with the commit, hence the route's at and its short wait.

How the decorator knows that its own dispatch committed the step:

Variant Verdict
A "committed" flag in upstream's dispatch result (EventSink knows it) Exact, but a new upstream seam in the orchestrator's result type and in every path that builds it.
Look up the command receipt before dispatching Misses a duplicate that commits between the lookup and the dispatch.
The thread's last event sequence before dispatching, plus an overlap guard per command id Chosen. Overlay only, through upstream's public getThreadEventSequence. A concurrent duplicate costs the step its mark, never gives it a wrong one.

How the web hides Delegace's wake:

Variant Verdict
A label with the sender's name (the version Pablo approved on 54d73abd4, „budíček“) Superseded by the decision: it shows the person the internal plumbing.
Render nothing in the message's row Leaves the row's frame and padding in the list, and the minimap still has the row.
Filter the rows after upstream builds them (one hook) Chosen. One seam, no gap, nothing in the minimap; upstream's turn grouping stays as computed.
Hide it on the server (projection, events) Changes upstream's data, and agents and the CLI need the wake.

Failure modes:

  • The record write fails (a full disk): that step has no mark, and the failure is logged.
  • The server crashes between the commit and the record: that step has no mark.
  • A command id is sent again: a retry with the same --idempotency-key, or another session's command id. T3 answers with the first dispatch's events, and nothing is recorded: a mark neither appears, nor changes, nor repeats.
  • Two dispatches of one command id run at the same time: neither records, so the step that one of them took has no mark.
  • The thread's last event sequence cannot be read before a dispatch: that step has no mark.
  • A thread's record is not deleted with the thread. It is small and never shown.
  • Buddy's own thread grows one line per wake, and every fetch, like every record, reads the whole file. That is fine for thousands of lines.
  • The answer is slow: an agent's message appears when an answer covers it, at most 5 s after it arrived. A wake that no answer covers in time then shows as upstream shows it.
  • The route is missing (an older T3) or fails: the thread shows as upstream does, wakes included.

Interface for the Buddy layer and Delegace

T3CODE_ACTING_SENDER=buddy|delegace. Any other value, budicek included, fails the command (invalid_request, exit 1) before anything is read.

  • Buddy, in the user's threads (the person's Chat T3, home ~/.t3): run t3 with T3CODE_ACTING_SENDER=buddy, T3CODE_ACTING_FOR=<the user's name> and, optionally, T3CODE_ACTING_FOR_ACCUSATIVE=<the name after „za“>. This covers launch, send, respond, approve and deny.
  • Delegace, in Buddy's own thread (Buddy's T3, home ~/.t3-buddy): T3CODE_ACTING_SENDER=delegace t3 thread send <buddy-thread> --mode queue --message-file - --base-dir ~/.t3-buddy --json, plus T4's --idempotency-key. A retry with the same key keeps the first run's record and adds none. The wake is hidden in the person's view of the thread; they see only Buddy's reply. There is no --sender-label.
  • Where to set the variables: never in a T3 service's own environment, whose agents would inherit them. For Buddy, set them in the buddy provider instance's environment in Buddy's T3 settings and in the Hermes gateway's unit. For Delegace, set them in its own unit.
  • Detecting support: t3 lazurio capabilities returns "actingSender": true. An older T3 ignores the variables, and the steps then show no mark.
  • Record: {threadId, kind: "message"|"request", id, sender, recordedAt}. Route: GET /api/lazurio/acting-senders?threadId= as above.

What changes

  • Server, overlay: lazurio/actingSender.ts (claim and environment), actingSenders.ts (store, actingAs, currentActingSender, and recordSteps, which records only the dispatch that commits a step), actingSendersRoute.ts, and overlay.ts (the route). agentCliConnection.ts issues both sessions with the claim, and cli.ts adds the capability.
  • Server, upstream seams:
    • SessionStore.ts: the claim act;
    • EnvironmentAuth.ts: actingSender on issue and on both authenticated sessions;
    • McpOAuth.ts and McpInvocationContext.ts: the claim in the MCP client's invocation;
    • ws.ts: one actingAs call;
    • runtimeLayer.ts: one recordSteps wrap.
  • Web: lazurio/actingSender.ts (Buddy's label, parse, row keys, loader, which rows a thread hides), LazurioActingSender.tsx (the rows hook, the label and the request mark), and three seams in MessagesTimeline.tsx (the rows, the label above a message, the mark beside a decided request).
  • Docs: overlay row 27 in the runbook, with a section "Kdo jednal: Buddy a Delegace"; the overlay spec's section of the same name; and the user guide docs/user/agent-cli.md ("Acting for you as your assistant").

What deliberately does not change

  • No field in packages/contracts and no database change.
  • No t3 auth session issue flag and no free label.
  • Queue edits, interrupts and other steps carry no mark: only messages and decisions do, as the task asks.
  • t3 thread read does not show the mark to agents yet.
  • The mobile app keeps upstream's rendering, wakes included; the desktop app was not checked.
  • For the hidden wake: the server, the claim and the CLI interface do not change (delegace stays and drives the hiding). The queue banner, find in the thread and search across threads are not changed (see "Where the wake still shows").
  • The claim grants nothing.

Overlay row and allowlist

Row 27 is feat(lazurio): a thread shows when Buddy or Delegace sent a message or decided a request (09371d7e6), with five follow-up commits: 32f5a47ac (only a decided request's row subscribes to the record, a guard in the running set, and the desktop wording), e79a58618 (only the dispatch that commits a step marks it), b5c158c90 (a test of Buddy's launch of a new thread), 879289d98 (Delegace's wake is hidden, and an agent's message waits for its sender) and b06ec0015 (no alias for budicek). On main it follows the lead's rows 23 to 26 (#104, #105, #107 and #108). The next rebuild folds all six into row 27. Upstream has no equivalent: it labels an agent's message only "Sent by another agent" and does not know who decided a request. Buddy acting for a person is a Lazurio concept, so it stays in the overlay; only a generic session claim could be offered upstream. The runbook's table, its target stack ("commit of #106") and the new section cover it.

Allowlist (.github/workflows/lazurio-fork-ci.yml):

  • a new group "Who acted": apps/server/src/auth/McpOAuth.ts, apps/server/src/mcp/McpInvocationContext.ts;
  • moved to the shared seams, since two features now change each of them: auth/EnvironmentAuth.ts (from row 20's group), auth/SessionStore.ts (from the hosted configuration), and ws.ts and components/chat/MessagesTimeline.tsx (from the managed Folder). runtimeLayer.ts was there already.

Contract test: a new test, "Buddy's and Delegace's steps carry who took them through their seams". Two existing assertions are updated for the new route in overlay.ts and the new wrap in runtimeLayer.ts. Since 4ee0fda4d it also checks the rows seam (useLazurioActingSenderRows right around useStableRows, exactly once).

Rebase onto main 626cf804 and the rename

Rebased once onto main after the lead's #104, #105, #107 and #108, keeping both sides' behaviour:

  • Runbook: the lead's rows 23 to 26 stay. This PR's row is 27 in the table, in the target stack (after row 26), in its section "Kdo jednal: Buddy a Delegace" and in the paragraph that lists what holds on every Environment.
  • Allowlist: the shared-seams comment names both sides' hooks in runtimeLayer.ts, server.ts, ws.ts, ChatView.tsx and MessagesTimeline.tsx. The lead's groups (Buddy's prompt, Buddy's one conversation, the page chip) and this PR's "Who acted" group stay separate, and no file is listed twice.
  • runtimeLayer.ts: recordSteps(guardBuddyCommands(guardCommands(Orchestrator.layer))). The record of who acted wraps Buddy's one-conversation guard, which wraps the managed Folder's guard, so it keeps only what the guards let through. feat(lazurio): Buddy's T3 Code is Buddy's one conversation, framed by the bubble #107's contract pin and this PR's both follow that line.
  • MessagesTimeline.tsx: this PR's rows hook runs first, and Buddy's day lines and day jump read the rows it returns, so a hidden wake never gets a day line of its own.
  • overlay.ts, cli.ts (capabilities threadSendIdempotency, threadSendOutcome, buddyThread and actingSender), ws.ts (imports) and agentCliConnection.ts (AGENT_CLI_SESSION_LABEL and the claim): both sides.
  • agentCli.test.ts: both sides' tests. The test server takes the orchestrator behind it as a parameter, so feat(lazurio): agents send once per idempotency key, and budíček finds Buddy's thread #105's real-sending test keeps its own runtime's orchestrator while the fakes go through the recorder of acting senders.
  • Contract test: feat(lazurio): Buddy's Hermes gets only the person's words and the page as a Czech block #104's to feat(lazurio): the page Buddy is asked from travels as a chip the person can remove #108's tests and this PR's. The overlay's layer pin now lists seven routes.

Renamed budicek → delegace and budíček → Delegace, in every commit (each commit's tree and message), without an alias:

  • the claim schema ({ kind: "delegace" }), the environment's parsing (T3CODE_ACTING_SENDER=delegace; "must be buddy or delegace", "Delegace acts for no one by name: …"), the records and the route, which carry the kind, the web (kind === "delegace" is hidden as before) and all tests;
  • feat(lazurio): agents send once per idempotency key, and budíček finds Buddy's thread #105's mentions: its overlay row (title and description, which now says the wake is not shown in the thread), the paragraph "Delegace budí takto: T3CODE_ACTING_SENDER=delegace t3 thread send …" with a sentence that the wake is not shown, its tests (T3CODE_ACTING_SENDER: "delegace", wake texts), and comments in agentCliRequests.ts, buddySurfaceCli.ts, buddySurfaceThread.ts, buddySurfaceThreadRoute.ts and cli.ts; feat(lazurio): Buddy's T3 Code is Buddy's one conversation, framed by the bubble #107's section ("… a Delegace přes t3 lazurio buddy thread --json");
  • b06ec0015 tests that budicek is invalid: the CLI refuses it, a claim with it does not decode, and the web skips a record that names it. These three test cases are the only budicek left in the tree; budíček appears nowhere.

Verification

Run on Matěj's HaM work Environment (2 CPU, 7 GB RAM), Node 24.21, pnpm 11.10.0, with a filtered install (--filter "t3..." --filter "@t3tools/web..." --filter "@t3tools/monorepo" --filter "./oxlint-plugin-t3code"). Vitest ran with one worker.

Tests first. The rejection tests come first in every file. The new replay tests ran against d6e918c32 before the fix: all 9 failed for the reported reasons (the step sent again got Buddy's mark, Delegace's replay replaced Buddy's mark, a retry wrote a second line, the store appended a second record). Then each mutation of the fixed implementation ran alone against the 31 tests of actingSendersReplay.test.ts, actingSenders.test.ts and agentCli.test.ts:

Mutation Result
Every dispatch acts as Delegace, with no session check 14 failed: the rejection tests except the two overlap cases, which the overlap guard alone still holds; the success tests, which then see the wrong sender; and the replay tests that start from Buddy's mark
Nothing is recorded 9 failed: the success tests, and the replay tests that start from Buddy's mark; the rejection tests passed
The CLI issues its sessions without the claim 1 failed: the CLI success test
A replay records (no check that the event is newer than the dispatch's start) 2 failed: another session's message and answer sent again from Buddy's session
No overlap guard per command id 2 failed: Buddy's dispatch that waited while the person's dispatch of its command id took the message, and the answer
The store appends a step's record again 1 failed: the first-record test
Reading keeps a step's last line 1 failed: the first-record test
No check that the step's event is in the step's thread 0 failed. This mutant is equivalent today: T3 replays a command id only in its own thread, and a step's event is in the command's thread. The check keeps the sequence comparison within one thread.
The thread's last event cannot be read (run on 54d73abd4, against actingSendersReplay.test.ts alone) 5 of its 10 failed: Buddy's launch of a new thread, and the four tests that start from Buddy's mark

Targeted tests (on 46cd4d966). apps/server: vp test run src/lazurio/actingSendersReplay.test.ts src/lazurio/actingSenders.test.ts src/lazurio/actingSender.test.ts src/lazurio/agentCli.test.ts src/lazurio/cli.test.ts --maxWorkers=1 --no-file-parallelism: 5 files, 46 tests passed. On 54d73abd4, which changes only actingSendersReplay.test.ts, that file passed with 10 tests.

  • actingSendersReplay.test.ts (10, new): upstream's own orchestrator (ProviderReplayHarness over in-memory SQLite) behind the decorator, as T3 builds it. For a message and for the answer to a question, rejections first:

    • another session's step, sent again from Buddy's session: no mark;
    • Buddy's dispatch, held before the orchestrator while the person's dispatch of the same command id takes the step: no mark;
    • Delegace sending Buddy's command id again: Buddy's mark stays;
    • Buddy's own retry: still one line.

    Each case first checks that T3 answered the second dispatch with the step's events, so it cannot pass without a replay. One more test covers the store's and the reader's first record. The last one dispatches Buddy's t3 thread launch of a new thread as ThreadLaunchService does (the thread, then its first message). It checks that the new thread reads as sequence 0 before its creation, that the message's events are numbered after the thread's last event and at most its last event after the dispatch, and that the message carries Buddy's mark.

  • actingSender.test.ts (5): the environment's refusals (unknown kind, names without a kind, Delegace with a name, control, bidi, separator and too-long names), the claim schema's refusals, a token forged afterwards (refused), invalid claims never issued, and the claim reaching /mcp and the client surface.

  • actingSenders.test.ts (6): rejections first (message words, a command's own field, a session without the claim, an agent inside a thread, no caller at all, a late decision T3 refuses, T3's own messages), then Buddy's message, answer, approve and deny and Delegace's wake; the record read by a fresh store (restart); the route's 401, 403, 400 and 200 with no-store; and the reader waiting for a running acting dispatch, complete only up to its start (TestClock). Its fake decider now numbers its events, since the decorator compares sequences.

  • agentCli.test.ts (16, of which 3 are new in this PR): the real t3 CLI against a live server on 127.0.0.1, with the real /mcp and T3's access checks, a client-surface stand-in that does what ws.ts does, and the real route over HTTP:

    • an invalid environment fails before anything is read;
    • without the environment the person's steps carry no mark;
    • with it, Buddy's launch, send, respond, approve and deny and Delegace's queued wake carry it, and the person's own send next to them does not.
  • cli.test.ts (10).

Upstream tests beside the decorator (on 46cd4d966): src/orchestration-v2/runtimeLayer.test.ts src/ws.test.ts src/orchestration-v2/Orchestrator.control-reads.test.ts: 3 files, 123 tests passed. On 86f1bd33e, the tests beside the other seams passed too: SessionStore, EnvironmentAuth, McpOAuth, McpInvocationContext, runtimeLayer and ws, 6 files, 174 tests. Those seams did not change since.

Web (apps/web, unit project, on 86f1bd33e; the web did not change since): src/lazurio/actingSender.test.ts src/components/chat/MessagesTimeline.logic.test.ts src/components/chat/MessagesTimeline.active-tools.test.ts src/reactCompilerCoverage.test.ts: 4 files, 1239 tests passed. That includes the React Compiler compiling every web file, the new .tsx included.

Other checks (on 46cd4d966, repeated on 54d73abd4 for the typecheck, and for lint and format on the changed test):

  • tsc-rs --noEmit --singleThreaded in apps/server: no errors in the package (apps/web had none on 86f1bd33e, and it did not change since). The only errors are in scripts/lib, whose dependencies the filtered install left out.
  • vp lint --report-unused-disable-directives on the changed files: 0 warnings, 0 errors.
  • vp fmt --check on the changed files: clean.
  • node --test scripts/lazurio-release-contract.test.mjs: 39 of 39 passed.

On b06ec0015 (rebased onto 626cf804, renamed), one worker:

  • Release contract: 43 of 43 (feat(lazurio): Buddy's Hermes gets only the person's words and the page as a Czech block #104's to feat(lazurio): the page Buddy is asked from travels as a chip the person can remove #108's tests and this PR's).
  • Server acting sender: actingSender, actingSenders, actingSendersReplay, agentCli (20, with feat(lazurio): agents send once per idempotency key, and budíček finds Buddy's thread #105's idempotency tests) and cli: 5 files, 51 tests passed.
  • The lead's server tests beside the touched seams: buddySurfaceGuards, buddySurfaceThread, buddySurfaceStartup, buddySurfaceDays, buddyPrompt, buddyPrompt.orchestrator, agentCliViews, runtimeLayer, ws and Orchestrator.control-reads: 10 files, 158 tests passed.
  • Web: actingSender (12), buddySurfaceFrame, buddySurfacePage, buddySurfaceProtocol, MessagesTimeline.logic, MessagesTimeline.active-tools and reactCompilerCoverage: 7 files, 1264 tests passed. buddySurfaceDayJump.browser.test.tsx and MessagesTimeline.browser.test.tsx need Chromium, so CI runs them.
  • tsc-rs --noEmit --singleThreaded: no errors in apps/server (only the known scripts/lib errors of the filtered install) and none in apps/web. Format is clean on the changed files; lint has 0 errors and the 3 known upstream warnings in ws.ts.
  • Browser check again, because MessagesTimeline.tsx conflicted: the same setup as below, seeded with a launch and a send without variables, Buddy's send and a Delegace wake, with a second wake sent while the Czech page was open. T3CODE_ACTING_SENDER=budicek failed with invalid_request (exit 1).
Check cs-CZ en-US
The two messages sent without the variables "Sent by another agent" "Sent by another agent"
Buddy's message „Buddy za Matěje“ above it "Buddy for Matěj" above it
Delegace's two wakes, one sent while the page was open never on the page: no node with the wake's text was ever added the same
Buddy's replies after the wakes shown shown

Screenshots (kept outside the PR): cs-thread-delegace-hidden.png, en-thread-delegace-hidden.png. The servers were stopped by their exact process groups afterwards, and the throwaway home was removed.

Hidden wake (4ee0fda4d, web only):

  • Tests first. The new web tests ran against the web code of 54d73abd4: 8 failed, because the behavior did not exist (the rows filter, the loader's view and its wait); the 4 kept tests passed. Each mutation of the new code then ran alone against src/lazurio/actingSender.test.ts (12 tests):
Mutation Result
Delegace's wake is shown 2 failed: the wake is hidden; a later answer hides an expired wake
An agent's message shows before the answer 1 failed: held back until an answer covers it
An older T3 or an error holds messages back 2 failed: shown at once without an answer; the loader stops asking
The person's own messages wait too 1 failed: their own, T3's, automations' and other threads' never wait
The wait has no bound 2 failed: a message that waited too long shows; the loader's wait expires
No second request for rows the answer leaves out 1 failed: the loader asks once more
  • Targeted tests. apps/web unit project, src/lazurio/actingSender.test.ts src/components/chat/MessagesTimeline.logic.test.ts src/components/chat/MessagesTimeline.active-tools.test.ts src/reactCompilerCoverage.test.ts --maxWorkers=1: 4 files, 1245 tests passed. That includes the React Compiler compiling every web file, the new hook included. apps/server acting-sender files (their comments changed): 5 files, 47 tests passed.
  • Other checks: tsc-rs --noEmit --singleThreaded in apps/web: no errors. Lint (0 warnings, 0 errors) and format are clean on the changed files. The contract test passed 39 of 39.
  • Browser check (root decision 0178), on 4ee0fda4d, set up as below. Seeded through the real t3: a launch and a send without the acting variables, Buddy's send, and Delegace's wake. A second wake was sent while the Czech page was open. Before the app's first script, a MutationObserver on the page recorded every node with "Delegace" in its text that was ever added.
Check cs-CZ en-US
The two messages sent without the variables "Sent by another agent" "Sent by another agent"
Buddy's message „Buddy za Matěje“ above it "Buddy for Matěj" above it
Delegace's two wakes, one sent while the page was open not on the page; no node with the wake's text was ever added the same
Buddy's replies after the wakes shown, one after the other shown
"Delegace" anywhere in the page's text no no

Screenshots (kept outside the PR): cs-thread-wake-hidden.png, en-thread-wake-hidden.png. The servers were stopped by their exact process groups afterwards, and the throwaway home was removed.

Browser check (root decision 0178), on d6e918c32, before the decision; its „budíček“ row (the name then) is what 4ee0fda4d replaced. The fix after it changes only when the server records a step, not what the web shows.

  • Setup: the PR's T3 ran on the HaM work Environment on a free loopback port, with a throwaway T3 home and a local ACP command as a fake agent. Headless Chrome (playwright-core) loaded it over an SSH tunnel, once in cs-CZ and once in en-US.
  • Seeded through the real t3 CLI: two messages without the acting variables, Buddy's message that asked for a permission, Buddy's approval of that permission request (npm test), and Delegace's queued wake.
Row in the thread cs-CZ en-US
The two messages sent without the variables "Sent by another agent" "Sent by another agent"
Buddy's message „Buddy za Matěje“ above it "Buddy for Matěj" above it
The npm test permission request Buddy approved „Buddy za Matěje“ in its row "Buddy for Matěj" in its row
The wake (then budíček's) „budíček“ above it „budíček“ above it

The work group was expanded for the screenshots, so the collapsed default view was not captured. The mobile and desktop apps were not checked.

Not run locally:

  • MessagesTimeline.browser.test.tsx: there is no Chromium on the Environment, so CI runs it.
  • The Docker image build and the CLI archives: CI runs them.

Risks

  • New upstream seams in McpOAuth.ts and McpInvocationContext.ts, plus small ones in five allowlisted files. A rebuild must keep the claim next to rtc and keep MCP tools and the client surface's dispatch running in the request's fiber. The contract test and the runbook's rebuild note say so.
  • A dispatch through startup.enqueueCommand that runs before the server is ready keeps the claim, because the claim is provided on the queued effect itself.
  • Every dispatch now reads the current acting sender (one context lookup) and keeps an entry for its command id while it runs. A dispatch of Buddy or Delegace that takes a step also reads the thread's last event sequence first (one indexed MAX(sequence) query), and a record reads the thread's record file to keep the first record.
  • The replay check relies on two upstream behaviors: a command id sent again is answered with its original stored events and their sequences, and getThreadEventSequence is the thread's highest event sequence. actingSendersReplay.test.ts runs on upstream's own orchestrator, so a rebuild that changes either fails there.
  • The web asks once per thread that has agent-sent messages or decided requests, and once more per batch of such new rows. The user's own sends never trigger a request.

Review history

  • 86f1bd33e: the first version. CI green.
  • d6e918c32: a follow-up before review. A work-log row subscribes to the record only when it is a decided question or permission request, and removing a finished acting dispatch's start no longer drops another start when the value is missing. The overlay spec no longer claims that the desktop app cannot reach the route: the app wraps the web, and whether it shows the mark was not checked. CI green, and the browser check above passed on it.
  • Pablo (agentrozjedemeai), changes requested on d6e918c32, one P1: a command id sent again could give another session's step the acting sender's mark. T3 answers a replay with the original events, which the decorator took for its own commit, and reading kept a step's last record, so a later replay could also change a mark. The P1 is accepted as real. T4's stable --idempotency-key makes every retry such a replay.
  • 46cd4d966: the fix, described above under "Only the dispatch that commits a step marks it", with the regression tests the review asked for on upstream's own orchestrator. Pablo suggested a fresh-commit indicator at the dispatch seam "or equivalent". The overlay-only equivalent was chosen so that no upstream seam is added (see the variants above). CI green.
  • 54d73abd4: a check before the re-review asked whether a claimed launch into a thread that does not exist yet keeps its mark, and whether the sequences compared are one number space. Both are answered from the code above. A test of Buddy's launch of a new thread on upstream's own orchestrator confirms it, and it fails when the thread's last event cannot be read.
  • 54d73abd4: Pablo's round 2 (requested by Matěj's agent): APPROVED on the exact head. The P1 is resolved: only the dispatch that commits a step records it, nothing is recorded when a dispatch of the same command id overlaps, and the first record per step wins.
  • Then Matěj's decision (2026-10-22) changed the rendering: Delegace's wake is hidden as on Telegram, and the word "Delegace" leaves the UI. The Publication was stopped for it.
  • 4ee0fda4d: the rendering change (web only: the rows hook, the loader's wait and bound, no „Delegace“ label), the docs (the overlay spec, docs/user/agent-cli.md, runbook row 23) and a contract assertion for the rows seam. Browser check passed. Pablo's re-review is pending.
  • 4ee0fda4d: Pablo's round 3: APPROVED on the exact head. He traced the hold-and-reveal logic, the bounded loader and the fallbacks, and ran 1,245 web tests, the typecheck and the release contract.
  • Rebased once onto main after feat(lazurio): agents send once per idempotency key, and budíček finds Buddy's thread #105/feat(lazurio): Buddy's T3 Code is Buddy's one conversation, framed by the bubble #107/feat(lazurio): the page Buddy is asked from travels as a chip the person can remove #108; claim kind renamed budicek → delegace (Matěj's decision); feat(lazurio): agents send once per idempotency key, and budíček finds Buddy's thread #105's docs/tests converted. New head b06ec0015; the conflicts and the rename are listed above under "Rebase onto main 626cf804 and the rename". 879289d98 also writes the test's right-to-left override as \u202E again, which the hidden-wake commit had turned into the character itself. Pablo's re-check of the exact head is pending.
  • b06ec0015: Pablo's round 4: APPROVED on the exact head. He checked the decorator order, the timeline hook against Buddy's day lines, the rename with no alias, and that rounds 1–3 still hold, and re-ran 43 + 51 + 158 + 1264 tests in an isolated clone.

Follow-ups

  • The lead's Platform agent (P1, not this repo):
    • The Buddy installer sets T3CODE_ACTING_SENDER=buddy, T3CODE_ACTING_FOR and T3CODE_ACTING_FOR_ACCUSATIVE from the persona: in the buddy provider instance's environment in ~/.t3-buddy/userdata/settings.json, and in the Hermes gateway's unit.
    • Delegace's unit sets T3CODE_ACTING_SENDER=delegace.
    • Neither is set in lazurio-t3-buddy.service or in the person's Chat T3 service.
    • Buddy's rules say to pass the variables when running t3 in another Environment over SSH, since they do not cross SSH.
  • DESIGN.md §9: the Delegace wake line becomes T3CODE_ACTING_SENDER=delegace t3 thread send <threadId> --mode queue --idempotency-key <wake_key> --message-file -, without --sender-label.
  • T4 (feat(lazurio): agents send once per idempotency key, and budíček finds Buddy's thread #105, --idempotency-key) keeps the key's command id across runs, so a retry is a replay. A replay records nothing here, so the first run's record stands. T4 must keep the wake on /mcp (t3_thread_send, created by an agent). The client surface forces "created by user", so a wake sent there would look like the user's own message, and the web would not ask for its mark.
  • Later, separately: t3 thread read could show the mark to agents, and a queued message Buddy edits could carry it.
  • The wake elsewhere, if wanted: the queue banner, find in the thread and search across threads would each need an upstream seam of their own (see "Where the wake still shows").
  • Acceptance 3: the Ring 0 release, and a browser check on the released build.

🤖 Generated with Claude Code

Note

Add acting-sender attribution for Buddy and budíček messages and request decisions

  • Adds an acting-sender identity (Buddy with a person name, or budíček) configured via T3CODE_ACTING_SENDER, T3CODE_ACTING_FOR, and T3CODE_ACTING_FOR_ACCUSATIVE environment variables, validated in actingSender.ts
  • The sender is stored as a signed session claim, propagated through MCP, WebSocket, and client-surface dispatch paths, and restored on token verification via SessionStore and EnvironmentAuth
  • An orchestrator decorator in actingSenders.ts records qualifying committed messages and resolved requests to per-thread JSONL files in the state directory, with deduplication by step key and coordination with in-flight dispatches
  • Adds the authenticated GET /api/lazurio/acting-senders route (mounted in the overlay) and web timeline labels in MessagesTimeline.tsx showing who sent a message or decided a request
  • The t3 lazurio capabilities command now reports actingSender
  • Behavioral Change: orchestrator dispatches pass through the LazurioActingSenders.recordSteps decorator in runtimeLayer.ts; invalid sender environment configuration fails agent CLI commands with invalid_request; session tokens can only carry an acting-sender claim at issuance, not after

Macroscope summarized 54d73ab.

@agentrozjedemeai agentrozjedemeai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 — A replay can retroactively assign the acting-sender mark to a different session’s step. In apps/server/src/lazurio/actingSenders.ts (recordSteps, lines 267–287), tookStep treats any result.storedEvents matching the message/request id as a newly committed step and appends the current session’s sender. But upstream Orchestrator.ts (lines 11448–11484) returns the original stored events for an already-accepted command id in the same thread, without re-executing it; EventSink.ts (lines 629–632) does the same for a concurrent duplicate. A claimed client-surface session can replay an unclaimed agent message’s command id (and message id/thread id), or a request decision’s id, and the overlay will add “Buddy for …”/“budíček” to that earlier step despite not committing it. The web loader then renders the false author; a later replay can also overwrite an existing sender because read keeps the last record per key (lines 207–213). This violates the exact claim/replay acceptance criterion. Please record only when the command actually commits for this invocation (expose a fresh-commit indicator at the dispatch seam, or equivalent), and add a real-orchestrator regression test: unclaimed dispatch, replay of its command id from a claimed session in the same thread, no mark or change of existing mark. The current fake decider always emits new events and cannot catch this.

immakermatty added a commit to Lazurio/LazurioPlatform that referenced this pull request Oct 22, 2026
…h --message-file -; a key T3 Code refused is replaced (DEV-6676 P2)

T3 Code's acting-sender claim is out (Lazurio/t3code#106): an agent command
reads who it acts as from its environment and signs it into its own short
sessions. budíček's own `t3` now runs with T3CODE_ACTING_SENDER=budicek, so
the wake shows „budíček“; the marked seam `t3_session()` is gone. The
variable goes into that subprocess's environment alone, never into the
service's. `zadej` runs inside Buddy's environment, whose claim names him,
and T3 Code refuses `budicek` beside a name, so budíček's `t3` drops Buddy's
three variables.

The send follows T4 (#105) and the shared constants: `t3 thread send
<thread> --mode queue --idempotency-key <key> --message-file - --base-dir
~/.t3-buddy --json`. A positional `-` would have been the literal message.

T3 Code keeps a refusal for its key (a rejected receipt is final, #105), so
after a refusal T3 Code names, the next round sends with a new key
(`<key>-o1`, …), which cannot repeat a message that never went out; a lost
answer, a server that does not run or an unnamed failure keep the key.

Tests (41): the fake `t3` applies T3 Code's acting-sender rules and keeps
refusals per key; budíček's `t3` acts as budíček even inside Buddy's
environment, and nothing else it runs does; a refused key is never sent
again and the next one lands once; a lost answer keeps the key and the wake
lands once. Checked against T3 Code's own actingSenderFromEnvironment (#106
at d6e918c3): `budicek` alone is accepted, beside a name refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
immakermatty added a commit to Lazurio/LazurioPlatform that referenced this pull request Oct 22, 2026
…ser's name from his persona (DEV-6676 P1)

`lazurio buddy chat --apply` adds T3 Code's acting-sender claim
(Lazurio/t3code#106) to the `buddy` provider instance's environment:
T3CODE_ACTING_SENDER=buddy, T3CODE_ACTING_FOR and
T3CODE_ACTING_FOR_ACCUSATIVE, the user's name as it stands alone and after
„za“ from ~/.config/lazurio/buddy/persona.json (`user.nominative`,
`user.accusative`). Every `t3` Buddy runs from his chat inherits it, so what
he writes or decides in the user's threads shows „Buddy za <jméno>“.

The claim lives only in Buddy's own provider: never in the environment of a
T3 Code service, lazurio-t3-buddy.service included, or in the user's Chat,
whose agents would all inherit it. A name T3 Code would refuse (not 1 to 64
characters on one line, something to trim, a control, formatting or
separator character) would fail every `t3` Buddy runs, so without a usable
name the command writes nothing (`buddy-persona-unreadable`).

Tests: the members with the claim; an older name replaced; the claim only in
Buddy's provider, the user's Chat settings and the unit untouched, Hermes's
check without it; each unusable persona refused with nothing written. The
written file decodes with T3 Code's ServerSettings, and its three variables
with T3 Code's own actingSenderFromEnvironment (#106 at d6e918c3) as
{kind: "buddy", for, forAccusative}.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
immakermatty added a commit to Lazurio/LazurioPlatform that referenced this pull request Oct 22, 2026
…, which carries no environment; F61 and F14 follow the trace interface (DEV-6676)

SSH carries no environment variables into another Environment, so the claim
that marks Buddy's steps in threads (T3CODE_ACTING_SENDER=buddy,
T3CODE_ACTING_FOR, T3CODE_ACTING_FOR_ACCUSATIVE; Lazurio/t3code#106) would
be lost there. „Jak pověřuješ“ gains one rule: when Buddy runs `t3` in
another Environment, he passes them explicitly and quoted, with the user's
names from his persona. Template revision stays base-instructions-67
(unpublished), its digest re-pinned. Hermes's injection scan
(tools/threat_patterns.py, d923a5eb) is clean for every rendered file at
context and strict scope; .hermes.md is 14.6k (cs) and 16.0k (en)
characters.

F61 now records the claim in Buddy's provider and its boundary (never a
service's environment or the user's Chat), the persona's names and their
refusal, budíček's claim on its own `t3`, `--message-file -`, the new key
after a refusal, and two rejected alternatives (the claim in a unit, a stored
session). The trace follow-ups for the install are gone; Buddy in Telegram
needs the same variables in the gateway's unit, which its install owns. F14's
addendum names the SSH rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@agentrozjedemeai agentrozjedemeai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@immakermatty Re-review of exact head 54d73ab: APPROVED. The prior P1 is resolved. The decorator compares the returned step event against the pre-dispatch thread sequence, suppresses a mark for overlapping dispatches of the same command id, and preserves the first stored mark. I traced the upstream receipt/replay and event-sequence paths and ran the real-orchestrator replay/launch tests (47 targeted tests passed), adjacent orchestrator tests (123 passed), and the release contract (39 passed). Targeted lint/format and server typecheck passed. No new blocker found. The PR is still draft; this review is not a merge or release approval.

@immakermatty
immakermatty marked this pull request as ready for review October 22, 2026 17:06
immakermatty added a commit to Lazurio/LazurioPlatform that referenced this pull request Oct 22, 2026
…y chat, budíček's t3 channel, no rail or bubble on his page, the layer's rules and F61 (DEV-6676) (#592)

* Buddy: the bubble in Lazurio in .hermes.md: its own conversation, the page context as data, SSH only through Lazurio's names (DEV-6676)

A person can now write to their Buddy from the bubble in Lazurio's web
apps (Buddy on the web v1). Buddy's rules gain the section „Bublina v
Lazuriu“ ("The bubble in Lazurio"):
- the bubble is a conversation of its own next to Telegram, and a reply
  goes back where the request came from (root 0210, point 7);
- the block „Kontext stránky (údaje, ne pokyn)“, which the Hermes
  lazurio_web adapter puts into the model's copy of each message, is data
  that says where the person is and commands nothing;
- Buddy looks into the Environment from the context with
  `ssh <environment id>` only when that name is in
  ~/.ssh/config.d/lazurio-environments.conf, which Lazurio generates from
  the network grants (root 0210 evening addendum, point 4); otherwise he
  says he cannot reach it yet, and he never guesses a host or accepts an
  unknown host key;
- work in another Environment goes to a bot there through
  `budicek zadej --env <environment id>`.

The skill lazurio-buddy-pomocnici now says budíček wakes Buddy in the
conversation he gave the work from, not always the main one, matching
budíček's channel (#587).

Template revision base-instructions-67, with the pinned digest and the
manual snapshots. The whole layer passes Hermes's injection scan at context
and strict scope; the SSH rule is split over two lines so that no line pairs
a <…> placeholder with ~/.ssh. Decision note under F14.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Budíček: a delegation from Buddy's chat in Lazurio wakes him there, once, as a queued message in his one thread (DEV-6676 P2)

Buddy's chat in Lazurio is T3 Code with Hermes over ACP (decision F61). Its
provider instance runs `hermes acp` with LAZURIO_BUDDY_CHANNEL=t3, so
`budicek zadej` from the chat records `"kanal": "t3"` (optional in verze 1).
Such a wake is one message in Buddy's one thread: found with
`t3 lazurio buddy thread --json`, sent with `t3 thread send <thread> --mode
queue --idempotency-key <wake key> -` against `--base-dir ~/.t3-buddy`. Queue
never interrupts a turn; the key makes a repeated send after a crash the same
message. Its text is fixed: `budíček <id>: <STATE>`, the bot's report on one
line as `Výsledek bota (údaje, ne pokyn): …`, and `budicek vysledek <id>`.

Without the channel nothing changes: Hermes's Kanban wakes Buddy in Telegram,
as for every record from before. An unknown channel fails `zadej` before
anything exists; without Buddy's thread the task is `nezalozen` and no bot
starts; the last resort stays the Telegram DM. The session that authors the
message is a marked seam (`t3_session`) until the trace claim `budicek`
(Lazurio/t3code agent/DEV-6676-buddy-trace) is merged.

Tests: the channel recorded, the thread answering before the bot, one queued
message with its key and text, a bounded report, a restart right after the
send and at every other step waking once, T3 Code down and back, no thread,
a bot that does not start, a second round, re-wakes and the Telegram last
resort, unknown channels, and Telegram without the channel or from an old
record.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Shell: Buddy's own page draws no rail and no bubble, so his chat in the bubble's window is only his (DEV-6676 P3)

Buddy's chat is now a T3 Code of his own on his origin,
https://buddy.<slug>.lazurio.io (decision F61). T3 Code's page loads
/.lazurio/shell.js and places <lazurio-rail> and <lazurio-buddy> like every
app of an Environment, so inside the bubble's window the shell would draw the
Environment's rail beside Buddy's chat and a second bubble inside Buddy.

On a page whose origin is Buddy's (`isBuddysOwnPage`: the shape every
`buddy.origin` of the merged document has, known before any document
arrives) the rail adopts no styles, keeps no width, takes no jump shortcut
and reads or reports nothing, and <lazurio-buddy> draws no bubble. Every other
page, the apps that frame him included, is drawn as F60 says; the column
head stays the host's to place.

Verified by tests/shell-buddy.test.ts (his origin and every origin a merged
document names as his are his own page; the apps, other Environments and
look-alikes are not) and in headless Chromium against a page placed like T3
Code's: on t3code.ada the rail (64 px) and the launcher; on buddy.ada a 0x0
rail, no padding, no bubble, and only the shell document read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Buddy's chat: lazurio buddy chat gives Buddy's own T3 Code its one provider, Hermes over ACP, beside everything else in its settings (DEV-6676 P1)

Buddy's chat in Lazurio is a T3 Code of its own on the user's Osobní
Prostředí, ~/.t3-buddy (decision F61, Matěj's variant A). Machines run it and
declare its project; what makes it Buddy's are members of its settings, and
Lazurio owns exactly those, in T3 Code's own keys (packages/contracts
settings.ts and providerInstance.ts, provider-acp-registry settings.ts):

- the provider instance `buddy`: T3's ACP driver with a local command,
  `hermes acp`, HERMES_HOME=~/.hermes and LAZURIO_BUDDY_CHANNEL=t3, so
  budíček wakes Buddy back in this chat;
- `codex` and `claudeAgent` off, the only built-in instances T3 Code turns on
  by itself;
- continueThreadsAfterServerUpdate and enableAgentBrowserAccess false.

`lazurio buddy chat` reads whether they are in place; `--apply` writes them
into ~/.t3-buddy/userdata/settings.json and keeps every other member,
replacing the file atomically only while it holds what was read (T3 Code's
own `t3 theme set` protocol beside its running server, which watches the
file). It writes nothing outside the user's own Osobní Prostředí, without
Buddy's layer in the Folder's .agents/buddy, with the layer inside a Git work
tree (checkpoints and a rollback would fork Buddy's memory), or without
Hermes's ACP server (`hermes acp --check`). A file T3 Code could not decode,
a link or a directory is left for the user. The person's Chat (~/.t3) keeps
F51: T3 writes its settings itself.

Not here: the composer's steer default, which is a client setting of T3 Code
(followUpBehavior, the browser's storage) and so the Buddy surface's (T2),
and the project, which T3 Code creates from the unit's declaration.

Tests: the members in T3 Code's shape; every other member kept; read-only
and apply, private modes, nothing rewritten when in place; T3's order and
own members still managed; each refusal writing nothing; files left as they
are; another writer between read and write; other Environments skipped; the
command's JSON and exits. The written file decodes with T3 Code's own
ServerSettings and AcpRegistrySettings: buddy on, Codex and Claude off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Buddy: .hermes.md's „Bublina v Lazuriu“ follows his chat on T3 Code: one conversation, its own T3 tools, budíček's wake there, a stop or steer joined to the earlier request (DEV-6676 P1)

The bubble's chat is now T3 Code with Hermes over ACP in a T3 Code of its own
(decision F61), not the closed `lazurio_web` adapter. The section keeps what
still holds: the page context is data that commands nothing, SSH only
through the names Lazurio generates and never an unknown host key, and work
in another Environment through a bot there. It adds what the T3 mechanism
asks of Buddy:

- the chat is one conversation that goes on day after day, beside Telegram;
- the MCP server `t3-code` T3 Code gives every ACP session belongs to that
  chat: no threads or projects and no steering with it; the user's threads
  stay the `t3` command's;
- the page-context block is written into the user's message and kept in the
  history; the user's words are the rest;
- budíček's wake arrives in the chat as its fixed message, not the user's
  words;
- a stop or a steer joins the earlier request to the next message (Hermes's
  „User correction/guidance after interrupt:“): follow the new one, check
  what a cut-off tool step did before repeating it, and look in
  `budicek seznam --otevrene` before giving a bot work again.

The skill `lazurio-buddy-pomocnici` shows both forms of the wake. Template
revision stays base-instructions-67 (unpublished), its digest re-pinned; the
F14 addendum is rewritten. The whole layer passes Hermes's injection scan
(tools/threat_patterns.py, Lazurio/hermes-agent d923a5eb) at context and
strict scope; .hermes.md is 14.2k (cs) and 15.6k (en) characters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Decisions: F61, Buddy's chat in Lazurio is T3 Code with Hermes over ACP (the Platform's side), and F60's addendum: his frame is his own T3 Code, his page draws no rail or bubble (DEV-6676 P4)

F61 records Matěj's decisions of 2026-10-22 (variant A, steer by default,
the page context in the user's turn, Hermes changes only in the fork, one
conversation, Mattyčus) and the Platform's part: `lazurio buddy chat`
(Buddy's T3 settings, with the boundary to F51 and what is not the
Platform's: the composer's steer default, the project, the unit, Hermes's own
configuration), budíček's channel `t3`, the layer and the shell. It retires
the frame parts of the F61 proposed in closed #587 (the Launchpad listener,
the runtime contract, the adapter proxy and secret, the frame page); its
wake back to the channel stands with T3's channel. Alternatives and the
follow-ups that wait for T3 Code's trace claims are named.

F60's addendum of 2026-10-22: Buddy's origin serves his T3 Code, the frame's
side is T3 Code's (T2), and the shell draws no rail and no bubble on a page
of Buddy's origin, known before any document arrives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Buddy's chat: his instance's one switch is its enabled, and a file with comments is the person's (DEV-6676 P1)

T3 Code reads a false `enabled` inside an instance's configuration as off,
whatever the instance's own `enabled` says. Buddy's instance now keeps no
`enabled` in its configuration, so `--apply` always leaves him on; the test
of the members Lazurio owns covers it.

The refused files are named as they are: a file that is no plain JSON object
is left as it is, both one T3 Code could not decode and one with comments,
which T3 Code reads but a rewrite would drop (the code, F61 and the test).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Buddy's chat: a change of Buddy's T3 settings holds T3 Code's ownership of its home, so no server write is lost (Pablo's review of #592)

Pablo found the gap: comparing the file and then renaming over it are two
steps, so a running T3 Code server could write between the last comparison
and the replace, and that write would be lost while the command answered
`managed`. `t3 theme set` has the same gap; copying it proved nothing.

A change now takes T3 Code's own ownership of the home first: the exclusive
transaction on `server-owner.sqlite` (apps/server/src/serverOwnership.ts)
that its server holds for as long as it runs and that its offline commands
take before they change a home. Holding it, no server writes the file and
none starts until the replace is done; while Buddy's T3 Code runs, a change
is refused as `t3-running` and Machines stop its unit, apply and start it.
Settings already in place need nothing, so a converged apply passes while it
runs. The comparison before the replace stays, against a T3 command that
writes without the ownership, which Lazurio never runs on its homes.

Tests: a running server's ownership refuses a change and nothing is written;
settings in place need none while it runs; from the read to the replace, the
interval between the last comparison and the replace included, a server
starting in another process is busy; the home is owned again once the change
is done; an unlocked writer before the comparison is read again and kept.
Checked across runtimes too: while Node holds the lock exactly as T3 Code
does (node:sqlite, BEGIN EXCLUSIVE), Bun's attempt is busy. F61 and the help
say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Budíček: its wake in Buddy's chat is authored as budíček and sent with --message-file -; a key T3 Code refused is replaced (DEV-6676 P2)

T3 Code's acting-sender claim is out (Lazurio/t3code#106): an agent command
reads who it acts as from its environment and signs it into its own short
sessions. budíček's own `t3` now runs with T3CODE_ACTING_SENDER=budicek, so
the wake shows „budíček“; the marked seam `t3_session()` is gone. The
variable goes into that subprocess's environment alone, never into the
service's. `zadej` runs inside Buddy's environment, whose claim names him,
and T3 Code refuses `budicek` beside a name, so budíček's `t3` drops Buddy's
three variables.

The send follows T4 (#105) and the shared constants: `t3 thread send
<thread> --mode queue --idempotency-key <key> --message-file - --base-dir
~/.t3-buddy --json`. A positional `-` would have been the literal message.

T3 Code keeps a refusal for its key (a rejected receipt is final, #105), so
after a refusal T3 Code names, the next round sends with a new key
(`<key>-o1`, …), which cannot repeat a message that never went out; a lost
answer, a server that does not run or an unnamed failure keep the key.

Tests (41): the fake `t3` applies T3 Code's acting-sender rules and keeps
refusals per key; budíček's `t3` acts as budíček even inside Buddy's
environment, and nothing else it runs does; a refused key is never sent
again and the next one lands once; a lost answer keeps the key and the wake
lands once. Checked against T3 Code's own actingSenderFromEnvironment (#106
at d6e918c3): `budicek` alone is accepted, beside a name refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Buddy's chat: Buddy's provider acts as „Buddy za <jméno>“, with the user's name from his persona (DEV-6676 P1)

`lazurio buddy chat --apply` adds T3 Code's acting-sender claim
(Lazurio/t3code#106) to the `buddy` provider instance's environment:
T3CODE_ACTING_SENDER=buddy, T3CODE_ACTING_FOR and
T3CODE_ACTING_FOR_ACCUSATIVE, the user's name as it stands alone and after
„za“ from ~/.config/lazurio/buddy/persona.json (`user.nominative`,
`user.accusative`). Every `t3` Buddy runs from his chat inherits it, so what
he writes or decides in the user's threads shows „Buddy za <jméno>“.

The claim lives only in Buddy's own provider: never in the environment of a
T3 Code service, lazurio-t3-buddy.service included, or in the user's Chat,
whose agents would all inherit it. A name T3 Code would refuse (not 1 to 64
characters on one line, something to trim, a control, formatting or
separator character) would fail every `t3` Buddy runs, so without a usable
name the command writes nothing (`buddy-persona-unreadable`).

Tests: the members with the claim; an older name replaced; the claim only in
Buddy's provider, the user's Chat settings and the unit untouched, Hermes's
check without it; each unusable persona refused with nothing written. The
written file decodes with T3 Code's ServerSettings, and its three variables
with T3 Code's own actingSenderFromEnvironment (#106 at d6e918c3) as
{kind: "buddy", for, forAccusative}.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Buddy: .hermes.md says to pass the acting-sender claim to t3 over SSH, which carries no environment; F61 and F14 follow the trace interface (DEV-6676)

SSH carries no environment variables into another Environment, so the claim
that marks Buddy's steps in threads (T3CODE_ACTING_SENDER=buddy,
T3CODE_ACTING_FOR, T3CODE_ACTING_FOR_ACCUSATIVE; Lazurio/t3code#106) would
be lost there. „Jak pověřuješ“ gains one rule: when Buddy runs `t3` in
another Environment, he passes them explicitly and quoted, with the user's
names from his persona. Template revision stays base-instructions-67
(unpublished), its digest re-pinned. Hermes's injection scan
(tools/threat_patterns.py, d923a5eb) is clean for every rendered file at
context and strict scope; .hermes.md is 14.6k (cs) and 16.0k (en)
characters.

F61 now records the claim in Buddy's provider and its boundary (never a
service's environment or the user's Chat), the persona's names and their
refusal, budíček's claim on its own `t3`, `--message-file -`, the new key
after a refusal, and two rejected alternatives (the claim in a unit, a stored
session). The trace follow-ups for the install are gone; Buddy in Telegram
needs the same variables in the gateway's unit, which its install owns. F14's
addendum names the SSH rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Budíček: the wake's key never changes for a failure, so a message whose answer was lost or unreadable is never sent twice (Pablo's review of #592)

Pablo found that rotating the key after a named refusal is unsafe. `t3`
reports a refusal T3 Code keeps for good and one that passes alike
(`orchestration_error`), and it synthesizes `tool_failed` when a tool's
error cannot be read, which may follow a message that went out. A new key
after such an answer could wake Buddy twice. T3 Code names no final
refusal, so budíček now keeps the wake's key for every failure: a resend is
deduplicated, and a wake T3 Code refused for good waits, retried and logged
every round, as an undeliverable Kanban wake does (README's limits).

Tests: a refused send keeps its key three rounds running and sends nothing
twice; a message that went out with a `request_failed`, `tool_failed` or
`orchestration_error` answer is sent again with its key and wakes once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Buddy: over SSH the acting-sender names travel as data on standard input, never as the command's text (Pablo's review of #592)

Pablo found that the SSH rule quoted the persona's names into the remote
command: a name T3 Code takes may hold an apostrophe or shell syntax, so
`O'Neil` broke the command and `Ada'; …; #` ran one. The rule now gives Buddy
a command that never puts a name into a command's text: it sends both names
from his own environment on standard input (`printf '%s\n%s\n'
"$T3CODE_ACTING_FOR" "$T3CODE_ACTING_FOR_ACCUSATIVE" | ssh <id> '…'`), and
the fixed remote command reads them with `IFS= read -r` and sets them in
double quotes before `exec t3 …`; a message for `--message-file -` follows
them. Without the variables in his environment, `t3` there refuses.

tests/folder-buddy.test.ts runs that exact command as SSH would run it (the
remote shell with `-c` and the command as one string) with names holding
apostrophes, double quotes, `$(…)`, backticks, `;`, `&`, `|`, `>`, a
backslash and globs: each arrives exactly, no file appears, and a message
after them reaches `t3`. Template revision stays base-instructions-67, its
digest re-pinned; Hermes's injection scan is clean at context and strict
scope; .hermes.md is 14.9k (cs) and 16.4k (en) characters. F14 and F61
follow, with the key policy and two rejected alternatives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

@agentrozjedemeai agentrozjedemeai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@immakermatty Re-review of exact head 4ee0fda: APPROVED. I traced the new row filter, served-thread boundary, acting-sender coverage and bounded loader through the live-update and error paths. Eligible agent-sent user messages wait for an answer, a recorded budicek message is removed before the list/minimap, Buddy messages retain their attribution, and own/system/automation/other-thread messages bypass the hold. The loader has one follow-up request for uncovered rows and a 5 s fallback; an unavailable route falls back to upstream rendering. I ran 1,245 focused web/React Compiler tests, web typecheck (no errors), targeted lint/format, and the 39-test release contract; all passed. The four applicable CI jobs are green and there are no unresolved review threads. The documented queue/search/mobile and timeout fallbacks remain scope limitations, not a claim of universal invisibility. No new blocking issue found. This is a QA verdict on this head, not a release or merge action.

immakermatty added a commit to HumanAndMachines/Lazurio that referenced this pull request Oct 22, 2026
…person, only Buddy's own words

Point 1 now carries Matěj's principle in full, not only the mechanism's name:
the person sees and hears only what Buddy writes in human words. Buddy never
names his mechanism, commands, tools, task states such as HOTOVO or NESTIHL
TERMÍN, MausBot as a system or the "údaje, ne pokyn" label, and his chat
shows no raw tool steps, at most a quiet sign that he is working (as raised by
the PROTOTYPE BUDDY thread, which hides the wake in Lazurio/t3code#106).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
immakermatty and others added 6 commits October 22, 2026 21:06
…r decided a request

Root decision 0210, evening addendum, point 5 (plan DEV-6676, task-2026-10-20-330): what Buddy
writes or decides in a thread carries the visible mark „Buddy za <user>“ ("Buddy for <user>"),
and Delegace's wake in Buddy's own thread carries „Delegace“.

Who a session acts as is a claim signed into its token beside the runtime-mode ceiling (`act`),
from a closed set of kinds: buddy with the user's name, or delegace. The agent commands issue
their sessions with it when T3CODE_ACTING_SENDER (with T3CODE_ACTING_FOR and
T3CODE_ACTING_FOR_ACCUSATIVE) names one, and refuse any other value before reading anything.
The server reads it from the session alone: an MCP client's invocation carries it, the client
surface runs a dispatch as its session, and a decorator of the orchestrator records the message
or the request's resolution the dispatch committed, in a file per thread in the T3 home. The
web reads /api/lazurio/acting-senders and shows the mark instead of "Sent by another agent" and
beside a decided request. The person's own steps and T3's own messages get none.

Overlay row 23. Tests: rejections before successes, the real CLI against /mcp and the client
surface through to the route the web reads, the claim in the token, and the web's label, parse
and loader.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The work log rendered the request mark's hooks for every row; now a row subscribes to the
record only when it is a decided question or permission request. Removing a finished acting
dispatch's start from the running set no longer drops another start when the value is missing.
The overlay spec no longer claims the desktop app cannot reach the route: it wraps the web, and
whether it shows the mark was not checked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
T3 answers a command id it already accepted with the events of the dispatch that committed it.
A session acting as Buddy that sent another session's command id again therefore got that
message or decision back and recorded it as Buddy's, and a later replay replaced an existing
mark, because reading kept a step's last line. The agent CLI's --idempotency-key keeps a command
id across runs, so a retry is such a replay too.

The decorator now records a step only when its own dispatch committed it: the step's event in its
thread must be newer than the thread's last event when the dispatch began, and no other dispatch
of the same command id may have run meanwhile, since that one may have committed it. A step whose
dispatch overlapped another of its command id loses its mark rather than take someone else's.
A step's first record stands: the store appends no other, and reading skips any later line.

actingSendersReplay.test.ts drives this on upstream's own orchestrator, for a message and for an
answer to a question: another session's step sent again from Buddy's session, a dispatch that
waited while another dispatch of its command id took the step, Delegace sending Buddy's command
id again, and Buddy's own retry; plus the store's and the reader's first record. The fakes in
actingSenders.test.ts and agentCli.test.ts number their events now, because the decorator
compares sequences.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`t3 thread launch` creates its thread and then dispatches the first message, so the decorator
reads the new thread's last event right before that message. On upstream's own orchestrator, a
thread without events reads as sequence 0 rather than failing, the message's events are numbered
in the same sequence as the thread's last event, and Buddy's launch is marked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s message waits for its sender

Decided on 2026-10-22: Delegace's wake in Buddy's own thread is not shown at all, as on Telegram,
so the person sees only Buddy's reply. A visible message from Delegace showed the person the
internal plumbing. The word "Delegace" no longer appears in the web app in either language.

The thread's rows pass one overlay hook right after upstream keeps them stable
(useLazurioActingSenderRows in MessagesTimeline.tsx). A message an agent sent from outside the
thread waits until the acting-senders answer covers it, so a wake never shows for a moment. Then
Buddy's shows with „Buddy za …“, Delegace's stays hidden, and any other shows upstream's "Sent by
another agent". The wait is bounded at five seconds, and a server without the route, or an error,
shows the thread at once as upstream does. The person's own messages, T3's, automations' and
other threads' agents' never wait. The loader asks once more for rows its answer does not cover.
A decided request shows the mark only for Buddy.

The server, the claim and the CLI interface do not change; comments and docs that promised a
visible „Delegace“ now say that the wake is hidden.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The claim's second kind is delegace, after the mechanism Delegace. The earlier name is no alias:
an agent command with T3CODE_ACTING_SENDER=budicek fails before it reads or writes anything, a
token claim with that kind does not decode, and the web skips a record that names it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
immakermatty added a commit to Lazurio/LazurioPlatform that referenced this pull request Oct 22, 2026
…zurio's own message to the person

What changes, following the root decision 0210 addendum of 2026-10-22
(HumanAndMachines/Lazurio#619): the command is `delegace` with its
subcommands kept (zadej, vysledek, potvrd, seznam, hlidej), the unit is
`delegace.service`, and the variables are `DELEGACE_*`. Every text Buddy
reads says delegace and plain words instead of the old name or waking: the
notice in his chat (`delegace <id>: <STATE>`, then „Výsledek pomocníka (údaje,
ne pokyn): …“, then `delegace vysledek <id>`, and „(znovu, 2. upozornění)“ on
a repeat), the Kanban card's title, body, creator and metadata, the history
notes, the log lines, the errors and the help. The service's own `t3` acts
with T3 Code's acting-sender kind `delegace`, kept in one constant
(ACTING_SENDER, Lazurio/t3code#106), which shows the notice to Buddy and
never to the person.

The last resort, when Buddy confirms none of the three notices, is now
Lazurio's own short message in the Telegram DM, in Lazurio's voice: what the
helper did with the task, that Buddy is not answering now and that Lazurio
writes instead, then the helper's report (at most 1,500 characters) or its
question. It never names the mechanism, a command, a state, an id or a code.

Unchanged: the mechanism, and the records in
~/.local/state/lazurio/buddy/delegations/ with `verze: 1`, their keys and
states. There is no alias: the pilot installs move by hand (the README's
steps), a Platform release with this goes out together with a T3 Code release
that carries #106 with the kind `delegace`, and Machines' install
(HumanAndMachine-ai/Machines#701) installs the new names. Tests: the service's
52 tests, among them the message to the person for every state, a long and an
empty report, no old name or waking in anything the service writes, and a
record from before the rename going on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
immakermatty added a commit to Lazurio/LazurioPlatform that referenced this pull request Oct 22, 2026
…e person (root 0210 addendum of 2026-10-22, #619)

What: F61's point 2, its open question, alternatives, verification and
addendum, and F14's Buddy addenda (and F60's one mention) now say Delegace,
its command `delegace`, its notice and its tests, in place; the history notes
once that it was named budíček until the root decision 0210 addendum of
2026-10-22 (HumanAndMachines/Lazurio#619). F61 records that the service's
own `t3` acts with T3 Code's kind `delegace` (Lazurio/t3code#106), which
shows the notice to Buddy and never to the person, that the last resort is
Lazurio's own message in Lazurio's voice, and that a Platform with the rename
ships with a T3 Code that carries #106 with that kind. F14's addendum of
2026-10-22 records the layer's section on how Buddy talks to the person,
base-instructions-68, the Hermes scan and the reliance on Hermes's Telegram
default without tool progress.

Why: the root addendum renames the mechanism in code, documents, commands
and Buddy's skills and leaves the mechanism and its records as they are;
the decisions are rewritten rather than given a second account. The browser's
Probudit of F57 is another feature and stays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@immakermatty
immakermatty force-pushed the agent/DEV-6676-buddy-trace branch from 4ee0fda to b06ec00 Compare October 22, 2026 21:20
@immakermatty immakermatty changed the title feat(lazurio): a thread shows when Buddy or budíček sent a message or decided a request feat(lazurio): a thread shows when Buddy or Delegace sent a message or decided a request Oct 22, 2026
@immakermatty immakermatty changed the title feat(lazurio): a thread shows when Buddy or Delegace sent a message or decided a request feat(lazurio): a thread shows when Buddy sent a message or decided a request, and hides Delegace's wake Oct 22, 2026

@agentrozjedemeai agentrozjedemeai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@immakermatty Exact-head re-check of b06ec00: APPROVED. The rebased overlay sits on 626cf80; recordSteps wraps guardBuddyCommands and guardCommands, so refused commands cannot create a step record. The acting-sender rows filter runs immediately after useStableRows; both Buddy day lines and day jump consume those filtered rows, with no day line for a hidden wake. The merged route, CLI capabilities, client/MCP claim propagation, allowlist and release-contract pins retain the preceding features. The kind is now delegace across the claim, records, route, rendering and docs; budicek has no alias and remains only in three rejection tests, and the web has no old user-facing label. Replay/overlap and first-record protections from prior rounds remain. In an isolated exact-head clone I ran the 43 release-contract tests, 51 focused server tests, 158 adjacent server tests and 1,264 focused web/React Compiler tests: all passed; git diff --check is clean. CI run 39856465367 attempt 2 is green, with no unresolved review threads. Known queue/search/mobile and bounded timeout fallbacks remain documented scope limits. QA verdict only: no merge or release during the main freeze.

immakermatty added a commit that referenced this pull request Oct 23, 2026
Root decision 0210, addendum of 2026-10-22, point 1 (plan DEV-6676, row 28):
the person sees and hears only what Buddy writes in human words. In Buddy's
surface (lazurioBuddy) only:

- The timeline keeps the messages and what the person acts on (a secret
  request, answers to Buddy's questions, what Buddy shows them). T3's record
  of the work stays out: tool calls, commands, file edits, reasoning, the
  "Worked for" fold, superseded attempts, subagents, interrupts and context
  compaction. While a turn runs and Buddy is not waiting for the person, one
  quiet line at the end says "Buddy na tom pracuje…" / "Buddy is on it…",
  static text with no expand, timer or animation. After the turn, Buddy's
  final message stays. A failed turn shows one line per turn in human words
  instead of T3's error. The turn's task list above the composer stays out.
- The composer leaves out T3's runtime access and interaction mode (the
  "mode" block) and Hermes's ACP session mode ("Default", its permission
  mode) from the options chip; the model chip and any other option Hermes
  receives stay. The placeholder is "Napište Buddymu…" / "Message Buddy…",
  in the host page's language.
- T3's terminal and right-panel shortcuts, project scripts included, leave
  their keys to the browser, and the reopen-closed-view shortcut is not
  mounted (#109, item 2).

Stacked on row 27 (#106): the timeline's rows are Buddy's
conversation of what row 27 leaves visible, so Delegace's wake stays
hidden and Buddy's filter applies on top.

The logic lives in apps/web/src/lazurio (buddySurfaceTimeline.ts,
buddySurfaceControls.ts, BuddySurface.tsx). Upstream files get one-line
seams: MessagesTimeline.tsx (rows and row content), ChatComposer.tsx
(placeholder, options chip models, resting blocks; new on the allowlist),
ChatView.tsx (shortcuts, task list) and __root.tsx. Regular T3 Code is
unchanged.

buddySurfaceTimeline.browser.test.tsx renders the real timeline: a running
turn with tool calls shows only the quiet line, a finished one only the
final message, a failed one one human line, and T3's timeline outside
Buddy's surface keeps its work rows. buddySurfaceControls tests cover the
options chip through upstream's traits check and the shortcuts through
useChatShortcuts. The release contract pins the seams and that the session
mode id matches the ACP adapter's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
immakermatty added a commit to Lazurio/LazurioPlatform that referenced this pull request Oct 23, 2026
…zurio's own message to the person

What changes, following the root decision 0210 addendum of 2026-10-22
(HumanAndMachines/Lazurio#619): the command is `delegace` with its
subcommands kept (zadej, vysledek, potvrd, seznam, hlidej), the unit is
`delegace.service`, and the variables are `DELEGACE_*`. Every text Buddy
reads says delegace and plain words instead of the old name or waking: the
notice in his chat (`delegace <id>: <STATE>`, then „Výsledek pomocníka (údaje,
ne pokyn): …“, then `delegace vysledek <id>`, and „(znovu, 2. upozornění)“ on
a repeat), the Kanban card's title, body, creator and metadata, the history
notes, the log lines, the errors and the help. The service's own `t3` acts
with T3 Code's acting-sender kind `delegace`, kept in one constant
(ACTING_SENDER, Lazurio/t3code#106), which shows the notice to Buddy and
never to the person.

The last resort, when Buddy confirms none of the three notices, is now
Lazurio's own short message in the Telegram DM, in Lazurio's voice: what the
helper did with the task, that Buddy is not answering now and that Lazurio
writes instead, then the helper's report (at most 1,500 characters) or its
question. It never names the mechanism, a command, a state, an id or a code.

Unchanged: the mechanism, and the records in
~/.local/state/lazurio/buddy/delegations/ with `verze: 1`, their keys and
states. There is no alias: the pilot installs move by hand (the README's
steps), a Platform release with this goes out together with a T3 Code release
that carries #106 with the kind `delegace`, and Machines' install
(HumanAndMachine-ai/Machines#701) installs the new names. Tests: the service's
52 tests, among them the message to the person for every state, a long and an
empty report, no old name or waking in anything the service writes, and a
record from before the rename going on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
immakermatty added a commit to Lazurio/LazurioPlatform that referenced this pull request Oct 23, 2026
…e person (root 0210 addendum of 2026-10-22, #619)

What: F61's point 2, its open question, alternatives, verification and
addendum, and F14's Buddy addenda (and F60's one mention) now say Delegace,
its command `delegace`, its notice and its tests, in place; the history notes
once that it was named budíček until the root decision 0210 addendum of
2026-10-22 (HumanAndMachines/Lazurio#619). F61 records that the service's
own `t3` acts with T3 Code's kind `delegace` (Lazurio/t3code#106), which
shows the notice to Buddy and never to the person, that the last resort is
Lazurio's own message in Lazurio's voice, and that a Platform with the rename
ships with a T3 Code that carries #106 with that kind. F14's addendum of
2026-10-22 records the layer's section on how Buddy talks to the person,
base-instructions-68, the Hermes scan and the reliance on Hermes's Telegram
default without tool progress.

Why: the root addendum renames the mechanism in code, documents, commands
and Buddy's skills and leaves the mechanism and its records as they are;
the decisions are rewritten rather than given a second account. The browser's
Probudit of F57 is another feature and stays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@immakermatty
immakermatty merged commit c81c1e0 into main Oct 23, 2026
10 of 11 checks passed
@immakermatty
immakermatty deleted the agent/DEV-6676-buddy-trace branch October 23, 2026 14:01
@immakermatty
immakermatty restored the agent/DEV-6676-buddy-trace branch October 23, 2026 14:01
@immakermatty
immakermatty deleted the agent/DEV-6676-buddy-trace branch October 23, 2026 14:02
immakermatty added a commit that referenced this pull request Oct 23, 2026
Root decision 0210, addendum of 2026-10-22, point 1 (plan DEV-6676, row 28):
the person sees and hears only what Buddy writes in human words. In Buddy's
surface (lazurioBuddy) only:

- The timeline keeps the messages and what the person acts on (a secret
  request, answers to Buddy's questions, what Buddy shows them). T3's record
  of the work stays out: tool calls, commands, file edits, reasoning, the
  "Worked for" fold, superseded attempts, subagents, interrupts and context
  compaction. While a turn runs and Buddy is not waiting for the person, one
  quiet line at the end says "Buddy na tom pracuje…" / "Buddy is on it…",
  static text with no expand, timer or animation. After the turn, Buddy's
  final message stays. A failed turn shows one line per turn in human words
  instead of T3's error. The turn's task list above the composer stays out.
- The composer leaves out T3's runtime access and interaction mode (the
  "mode" block) and Hermes's ACP session mode ("Default", its permission
  mode) from the options chip; the model chip and any other option Hermes
  receives stay. The placeholder is "Napište Buddymu…" / "Message Buddy…",
  in the host page's language.
- T3's terminal and right-panel shortcuts, project scripts included, leave
  their keys to the browser, and the reopen-closed-view shortcut is not
  mounted (#109, item 2).

Stacked on row 27 (#106): the timeline's rows are Buddy's
conversation of what row 27 leaves visible, so Delegace's wake stays
hidden and Buddy's filter applies on top.

The logic lives in apps/web/src/lazurio (buddySurfaceTimeline.ts,
buddySurfaceControls.ts, BuddySurface.tsx). Upstream files get one-line
seams: MessagesTimeline.tsx (rows and row content), ChatComposer.tsx
(placeholder, options chip models, resting blocks; new on the allowlist),
ChatView.tsx (shortcuts, task list) and __root.tsx. Regular T3 Code is
unchanged.

buddySurfaceTimeline.browser.test.tsx renders the real timeline: a running
turn with tool calls shows only the quiet line, a finished one only the
final message, a failed one one human line, and T3's timeline outside
Buddy's surface keeps its work rows. buddySurfaceControls tests cover the
options chip through upstream's traits check and the shortcuts through
useChatShortcuts. The release contract pins the seams and that the session
mode id matches the ACP adapter's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
immakermatty added a commit that referenced this pull request Oct 23, 2026
Root decision 0210, addendum of 2026-10-22, point 1 (plan DEV-6676, row 28):
the person sees and hears only what Buddy writes in human words. In Buddy's
surface (lazurioBuddy) only:

- The timeline keeps the messages and what the person acts on (a secret
  request, answers to Buddy's questions, what Buddy shows them). T3's record
  of the work stays out: tool calls, commands, file edits, reasoning, the
  "Worked for" fold, superseded attempts, subagents, interrupts and context
  compaction. While a turn runs and Buddy is not waiting for the person, one
  quiet line at the end says "Buddy na tom pracuje…" / "Buddy is on it…",
  static text with no expand, timer or animation. After the turn, Buddy's
  final message stays. A failed turn shows one line per turn in human words
  instead of T3's error. The turn's task list above the composer stays out.
- The composer leaves out T3's runtime access and interaction mode (the
  "mode" block) and Hermes's ACP session mode ("Default", its permission
  mode) from the options chip; the model chip and any other option Hermes
  receives stay. The placeholder is "Napište Buddymu…" / "Message Buddy…",
  in the host page's language.
- T3's terminal and right-panel shortcuts, project scripts included, leave
  their keys to the browser, and the reopen-closed-view shortcut is not
  mounted (#109, item 2).

Stacked on row 27 (#106): the timeline's rows are Buddy's
conversation of what row 27 leaves visible, so Delegace's wake stays
hidden and Buddy's filter applies on top.

The logic lives in apps/web/src/lazurio (buddySurfaceTimeline.ts,
buddySurfaceControls.ts, BuddySurface.tsx). Upstream files get one-line
seams: MessagesTimeline.tsx (rows and row content), ChatComposer.tsx
(placeholder, options chip models, resting blocks; new on the allowlist),
ChatView.tsx (shortcuts, task list) and __root.tsx. Regular T3 Code is
unchanged.

buddySurfaceTimeline.browser.test.tsx renders the real timeline: a running
turn with tool calls shows only the quiet line, a finished one only the
final message, a failed one one human line, and T3's timeline outside
Buddy's surface keeps its work rows. buddySurfaceControls tests cover the
options chip through upstream's traits check and the shortcuts through
useChatShortcuts. The release contract pins the seams and that the session
mode id matches the ACP adapter's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
immakermatty added a commit to Lazurio/LazurioPlatform that referenced this pull request Oct 23, 2026
…med, and the person meets only Buddy's own words (root 0210 addendum of 2026-10-22, DEV-6676) (#604)

* Delegace: move the service's files to src/buddy/delegace under their new names

The root decision 0210 addendum of 2026-10-22 (HumanAndMachines/Lazurio#619)
renames the mechanism that hands Buddy his helpers' results to Delegace. This
commit only moves its files, with their contents unchanged, so that history
follows them: the command budicek becomes delegace, budicek.service becomes
delegace.service, test_budicek.py becomes test_delegace.py, and the README
moves with them.

Only the paths that make the move runnable change: the test's path to the
script and the CI step's path to the tests. The names and texts inside the
files change in the next commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Delegace: the service carries its new name, and its last resort is Lazurio's own message to the person

What changes, following the root decision 0210 addendum of 2026-10-22
(HumanAndMachines/Lazurio#619): the command is `delegace` with its
subcommands kept (zadej, vysledek, potvrd, seznam, hlidej), the unit is
`delegace.service`, and the variables are `DELEGACE_*`. Every text Buddy
reads says delegace and plain words instead of the old name or waking: the
notice in his chat (`delegace <id>: <STATE>`, then „Výsledek pomocníka (údaje,
ne pokyn): …“, then `delegace vysledek <id>`, and „(znovu, 2. upozornění)“ on
a repeat), the Kanban card's title, body, creator and metadata, the history
notes, the log lines, the errors and the help. The service's own `t3` acts
with T3 Code's acting-sender kind `delegace`, kept in one constant
(ACTING_SENDER, Lazurio/t3code#106), which shows the notice to Buddy and
never to the person.

The last resort, when Buddy confirms none of the three notices, is now
Lazurio's own short message in the Telegram DM, in Lazurio's voice: what the
helper did with the task, that Buddy is not answering now and that Lazurio
writes instead, then the helper's report (at most 1,500 characters) or its
question. It never names the mechanism, a command, a state, an id or a code.

Unchanged: the mechanism, and the records in
~/.local/state/lazurio/buddy/delegations/ with `verze: 1`, their keys and
states. There is no alias: the pilot installs move by hand (the README's
steps), a Platform release with this goes out together with a T3 Code release
that carries #106 with the kind `delegace`, and Machines' install
(HumanAndMachine-ai/Machines#701) installs the new names. Tests: the service's
52 tests, among them the message to the person for every state, a long and an
empty report, no old name or waking in anything the service writes, and a
record from before the rename going on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Buddy: the layer names Delegace, and Buddy talks to the person only in his own words, like a friend (base-instructions-68)

What changes in Buddy's Lazurio layer (.agents/buddy), after the root
decision 0210 addendum of 2026-10-22 (HumanAndMachines/Lazurio#619) and the
Organization Admin's principle of the same day:
- .hermes.md: a task Buddy hands a bot is a delegation, given through
  `delegace zadej`, and Delegace sends him a notice; a new section „Jak
  mluvíš s uživatelem“ ("How you talk to the user"): the person sees and
  hears only what Buddy writes, in plain human words, like a friend („Dal
  jsem to Zukovi, ozvu se.“, then „Zuko to má hotové: …“), never that a
  notice came. He never names to the person, in the chat or in Telegram,
  Delegace or its notices, his commands or any tool, the states as words,
  MausBot as a system, the list of delegations or queues, nor the labelled
  data; they stay his tools. In the bubble's section the chat's notice is
  for him alone, and the user sees only his reply.
- The skill lazurio-buddy-pomocnici uses `delegace` throughout and ends a
  notice with Buddy's own follow-up; lazurio-mausbot and
  lazurio-t3-orchestration point back to the section where they hand him a
  tool.
- AGENTS.md's Buddy section: agents never look for a way to notify Buddy.

Why: a conversation with Buddy is like a chat with a friend, and a friend
has no alarm clock; the mechanism stays as it is. The layer sets no Hermes
display setting and relies on Hermes's default, in which Telegram shows no
tool progress.

Impact: every generated Folder file and the layer change, so the template
revision moves to base-instructions-68 with its new pin and snapshots. Tests:
the new section and the skills' pointers in both locales, no old name or
waking in any file of the layer or of any Folder, and no line of the layer in
the shape of Hermes's deception_hide pattern; the rendered layer also passes
Hermes's tools/threat_patterns.py at context and strict scope
(Lazurio/hermes-agent v0.22.2-lazurio.1 and main ff0b336e).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Decisions: F61 and F14 name Delegace and record how Buddy talks to the person (root 0210 addendum of 2026-10-22, #619)

What: F61's point 2, its open question, alternatives, verification and
addendum, and F14's Buddy addenda (and F60's one mention) now say Delegace,
its command `delegace`, its notice and its tests, in place; the history notes
once that it was named budíček until the root decision 0210 addendum of
2026-10-22 (HumanAndMachines/Lazurio#619). F61 records that the service's
own `t3` acts with T3 Code's kind `delegace` (Lazurio/t3code#106), which
shows the notice to Buddy and never to the person, that the last resort is
Lazurio's own message in Lazurio's voice, and that a Platform with the rename
ships with a T3 Code that carries #106 with that kind. F14's addendum of
2026-10-22 records the layer's section on how Buddy talks to the person,
base-instructions-68, the Hermes scan and the reliance on Hermes's Telegram
default without tool progress.

Why: the root addendum renames the mechanism in code, documents, commands
and Buddy's skills and leaves the mechanism and its records as they are;
the decisions are rewritten rather than given a second account. The browser's
Probudit of F57 is another feature and stays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Tests: the golden Folders' AGENTS.md digests follow the Buddy line that agents never notify Buddy

CI's tests (2/2) on 135a992 failed on the eight goldens of
tests/try-overlay-preset.test.ts. They mask the template revision, so they
moved only because AGENTS.md's Buddy section now says agents never look for
a way to notify Buddy („upozornit“), a deliberate change of the layer commit.
Only the AGENTS.md digests change, in Czech and English for each golden; every
other generated file, the derived presets and the shell documents are as
before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Delegace: the person's message carries no delegation id from the helper's own words (Pablo's review of #604)

Pablo found that the last resort could still show the person the
delegation's id. The brief asks every helper to write its files into the
task's own folder, ~/Documents/buddy/<id>/, with absolute paths, so an
ordinary report („Výstup: /home/ada/Documents/buddy/<id>/vysledek.md“) went
to the person with the id in it, against the message's stated contract.

Now the helper's report or question reaches the person through
without_id(): a path in the task's folder, here or in another Environment's
home, keeps only the path inside it („rozbor.md“), and the folder itself or
any other mention of the id becomes the task's title. The words are
otherwise the helper's own; the record keeps them whole for Buddy. README
and F61 now say exactly this: Lazurio's own words never name the mechanism,
a command, a state or an error code, and the id appears nowhere.

Tests: a report with absolute paths into the folder, a quoted path with a
space, the folder itself and a bare id, here and on another Environment, and
a question with such a path; with the id handling taken out the test fails.
The service's 53 tests pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Delegace: a path's whole home goes from the person's message, a home with spaces included (Pablo's re-review of #604)

Pablo found that the shortening of a path into the task's folder matched only
from /Documents onward when the home held a space: „Výstup: /home/Jane
Doe/Documents/buddy/<id>/vysledek.md“ became „Výstup: /home/Jane
Doevysledek.md“, leaking part of the home and leaving a broken file name.

Now the home is anchored on its known start: this service's own home, which
the brief names exactly, `~`, or a home under /home/, /Users/ or /root,
whose own name may hold spaces or punctuation but never a slash or a line
end. The whole home goes, the person reads only the path inside the task's
folder („vysledek.md“), and the words before a path stay as the helper wrote
them, a slash among them („A/B test:“). Any other mention of the id still
becomes the task's title, and Buddy's stored report stays whole.

Tests: a report and a question with a spaced home, here (the service's own
home, as the brief names it) and on another Environment, with plain,
backticked and „quoted“ paths; and words with a slash before a path. They
fail on the service of 2c4c5d0, and the words-before test fails when the
home matches any run of segments. The earlier test took its home before the
temporary directory was replaced; it now takes it after. The person-text
guard no longer forbids the bare word vysledek, which a helper's own file
may carry; the command goes with the word delegace. The service's 55 tests
pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
immakermatty added a commit to Lazurio/LazurioPlatform that referenced this pull request Oct 24, 2026
…-instructions-71, #612)

On Ring 0, Bob's Buddy greeted the person by naming `/help` and signed his Telegram messages
"*Buddy za Boba*". Root decision 0210 (addendum of 2026-10-22) wants only his own words in his
chat.

- `.hermes.md`, "How you talk to the user": he introduces himself and talks like a person.
  He names commands, tools, settings, models or Hermes only when the user asks, and he
  never signs his messages to the user, whether in their chat or on Telegram.
- The mark rule covers only what he writes or approves for the user for others: T3 Code
  threads, comments and documents. T3 Code from 0.1.1-lazurio.2 shows the mark itself on
  what he sends, answers or approves in a thread (Lazurio/t3code#106). Only an older
  version (`t3 --version`) needs it as the first line, and `lazurio-t3-orchestration`
  says the same for a brief, an answer and "Publikuj".

The silence on a notice that brings nothing new (#612 point 3) waits for
Lazurio/hermes-agent#10: over ACP the marker would show.

Template revision base-instructions-71 with its digest and snapshots (only the revision line
changes), the F14 addendum, and tests in both locales.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
immakermatty added a commit to Lazurio/LazurioPlatform that referenced this pull request Oct 24, 2026
…-instructions-71, #612) (#614)

On Ring 0, Bob's Buddy greeted the person by naming `/help` and signed his Telegram messages
"*Buddy za Boba*". Root decision 0210 (addendum of 2026-10-22) wants only his own words in his
chat.

- `.hermes.md`, "How you talk to the user": he introduces himself and talks like a person.
  He names commands, tools, settings, models or Hermes only when the user asks, and he
  never signs his messages to the user, whether in their chat or on Telegram.
- The mark rule covers only what he writes or approves for the user for others: T3 Code
  threads, comments and documents. T3 Code from 0.1.1-lazurio.2 shows the mark itself on
  what he sends, answers or approves in a thread (Lazurio/t3code#106). Only an older
  version (`t3 --version`) needs it as the first line, and `lazurio-t3-orchestration`
  says the same for a brief, an answer and "Publikuj".

The silence on a notice that brings nothing new (#612 point 3) waits for
Lazurio/hermes-agent#10: over ACP the marker would show.

Template revision base-instructions-71 with its digest and snapshots (only the revision line
changes), the F14 addendum, and tests in both locales.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants