Repository navigation
feat(lazurio): a thread shows when Buddy sent a message or decided a request, and hides Delegace's wake - #106
Conversation
0f0f9a4 to
41ddc01
Compare
agentrozjedemeai
left a comment
There was a problem hiding this comment.
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.
…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>
…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>
…, 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
left a comment
There was a problem hiding this comment.
@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.
…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
left a comment
There was a problem hiding this comment.
@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.
…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>
…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>
…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>
…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>
4ee0fda to
b06ec00
Compare
agentrozjedemeai
left a comment
There was a problem hiding this comment.
@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.
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>
…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>
…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>
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>
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>
…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>
…-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>
…-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>
Status: ready for review. Pablo approved the exact head
b06ec0015(2026-10-22T21:44Z, round 4, after the single rebase onto626cf804and the rename to Delegace). CI onb06ec0015is green (attempt 2; attempt 1 hit an upstream network-dependent browser test). The headless browser check passed onb06ec0015(cs-CZ and en-US). Lazurio/t3codemainis frozen until the stable0.1.1-lazurio.1is 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:
t3agent 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.The Buddy lead widened it:
--sender-labelflag.Today a message from
t3 thread sendshows "Sent by another agent" whoever sent it, and a decision fromt3 request approvelooks 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: thedelegacekind,T3CODE_ACTING_SENDER=delegaceand 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
delegaceand the variableT3CODE_ACTING_SENDER=delegace. There is no alias:budicekis now an invalid value. The web hidesdelegacemessages 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 ceilingrtcin the session token (SessionStore.ts).EnvironmentAuth.issueSessiontakes it, and bothauthenticateMcpClientand the HTTP and WebSocket authentication return it.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:Why the environment, and not an issued token or a flag:
t3as 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 everyt3they run, with no token to store, pass or rotate.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 capabilitiesreports"actingSender": true.Where the author is recorded
Whose step is read from the session alone, never from the command or the message's words:
McpOAuth.tscopies it toMcpInvocationContext.client.actingSender);actingAs(currentSession)call inws.ts);Which steps: a decorator around upstream's orchestrator (
recordSteps, one wrap inruntimeLayer.ts, outside the managed Folder's guard) records a step after its own dispatch committed it:message.dispatch(t3 thread launch,t3 thread send), when that dispatch committed the message;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.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, andEventSink.tsfor a duplicate that commits meanwhile). That dispatch may be another session's: the person's ownt3with the same--idempotency-key(T4 keeps the key's command id across runs), or any client that knows the id. The decorator therefore:getThreadEventSequence, read before dispatching; if it cannot be read, nothing counts);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
sequencefromINSERT INTO orchestration_events … RETURNING sequence(OrchestrationEventStore.appendAgentEvents). A replay reads the same column back (readAgentEvents,rowToV2StoredEvent).getThreadEventSequenceisMAX(sequence)of that column over the thread's rows (latestAgentSequence), and it is0for a thread without events.t3 thread launchcreates its thread in its own dispatch (thread.create), then sends the first message (message.dispatch, command id<launch>:initial-message), both throughThreadManagementService, 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).For the web:
GET /api/lazurio/acting-senders?threadId=<id>answers{at, messages: [{messageId, sender}], requests: [{requestId, sender}]}withcache-control: no-store, under the scope of reading a thread (orchestration:read).atsays up to when the answer is complete: never after the start of a dispatch that still runs.Rendering in the web
delegaceis not rendered at all: no bubble and no label. The web never shows the mechanism's name.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".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.useLazurioActingSenderRowsfilters the thread's rows right after upstream'suseStableRowsinMessagesTimeline.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.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:
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
actingSenderfield.t3 auth session issueand agents inside threads have none, and a token cannot be given one afterwards.Variants compared
--sender-label,--acting-as)atand its short wait.How the decorator knows that its own dispatch committed the step:
getThreadEventSequence. A concurrent duplicate costs the step its mark, never gives it a wrong one.How the web hides Delegace's wake:
54d73abd4, „budíček“)Failure modes:
--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.Interface for the Buddy layer and Delegace
T3CODE_ACTING_SENDER=buddy|delegace. Any other value,budicekincluded, fails the command (invalid_request, exit 1) before anything is read.~/.t3): runt3withT3CODE_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.~/.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.buddyprovider instance's environment in Buddy's T3 settings and in the Hermes gateway's unit. For Delegace, set them in its own unit.t3 lazurio capabilitiesreturns"actingSender": true. An older T3 ignores the variables, and the steps then show no mark.{threadId, kind: "message"|"request", id, sender, recordedAt}. Route:GET /api/lazurio/acting-senders?threadId=as above.What changes
lazurio/actingSender.ts(claim and environment),actingSenders.ts(store,actingAs,currentActingSender, andrecordSteps, which records only the dispatch that commits a step),actingSendersRoute.ts, andoverlay.ts(the route).agentCliConnection.tsissues both sessions with the claim, andcli.tsadds the capability.SessionStore.ts: the claimact;EnvironmentAuth.ts:actingSenderon issue and on both authenticated sessions;McpOAuth.tsandMcpInvocationContext.ts: the claim in the MCP client's invocation;ws.ts: oneactingAscall;runtimeLayer.ts: onerecordStepswrap.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 inMessagesTimeline.tsx(the rows, the label above a message, the mark beside a decided request).docs/user/agent-cli.md("Acting for you as your assistant").What deliberately does not change
packages/contractsand no database change.t3 auth session issueflag and no free label.t3 thread readdoes not show the mark to agents yet.delegacestays and drives the hiding). The queue banner, find in the thread and search across threads are not changed (see "Where the wake still shows").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) andb06ec0015(no alias forbudicek). Onmainit 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):apps/server/src/auth/McpOAuth.ts,apps/server/src/mcp/McpInvocationContext.ts;auth/EnvironmentAuth.ts(from row 20's group),auth/SessionStore.ts(from the hosted configuration), andws.tsandcomponents/chat/MessagesTimeline.tsx(from the managed Folder).runtimeLayer.tswas 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.tsand the new wrap inruntimeLayer.ts. Since4ee0fda4dit also checks the rows seam (useLazurioActingSenderRowsright arounduseStableRows, exactly once).Rebase onto
main626cf804and the renameRebased once onto
mainafter the lead's #104, #105, #107 and #108, keeping both sides' behaviour:runtimeLayer.ts,server.ts,ws.ts,ChatView.tsxandMessagesTimeline.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(capabilitiesthreadSendIdempotency,threadSendOutcome,buddyThreadandactingSender),ws.ts(imports) andagentCliConnection.ts(AGENT_CLI_SESSION_LABELand 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.Renamed
budicek→delegaceand budíček → Delegace, in every commit (each commit's tree and message), without an alias:{ 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;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 inagentCliRequests.ts,buddySurfaceCli.ts,buddySurfaceThread.ts,buddySurfaceThreadRoute.tsandcli.ts; feat(lazurio): Buddy's T3 Code is Buddy's one conversation, framed by the bubble #107's section ("… a Delegace přest3 lazurio buddy thread --json");b06ec0015tests thatbudicekis 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 onlybudicekleft in the tree;budíčekappears 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
d6e918c32before 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 ofactingSendersReplay.test.ts,actingSenders.test.tsandagentCli.test.ts:54d73abd4, againstactingSendersReplay.test.tsalone)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. On54d73abd4, which changes onlyactingSendersReplay.test.ts, that file passed with 10 tests.actingSendersReplay.test.ts(10, new): upstream's own orchestrator (ProviderReplayHarnessover in-memory SQLite) behind the decorator, as T3 builds it. For a message and for the answer to a question, rejections first: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 launchof a new thread asThreadLaunchServicedoes (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/mcpand 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 withno-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 realt3CLI against a live server on 127.0.0.1, with the real/mcpand T3's access checks, a client-surface stand-in that does whatws.tsdoes, and the real route over HTTP: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. On86f1bd33e, the tests beside the other seams passed too:SessionStore,EnvironmentAuth,McpOAuth,McpInvocationContext,runtimeLayerandws, 6 files, 174 tests. Those seams did not change since.Web (
apps/web, unit project, on86f1bd33e; 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.tsxincluded.Other checks (on
46cd4d966, repeated on54d73abd4for the typecheck, and for lint and format on the changed test):tsc-rs --noEmit --singleThreadedinapps/server: no errors in the package (apps/webhad none on86f1bd33e, and it did not change since). The only errors are inscripts/lib, whose dependencies the filtered install left out.vp lint --report-unused-disable-directiveson the changed files: 0 warnings, 0 errors.vp fmt --checkon the changed files: clean.node --test scripts/lazurio-release-contract.test.mjs: 39 of 39 passed.On
b06ec0015(rebased onto626cf804, renamed), one worker: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) andcli: 5 files, 51 tests passed.buddySurfaceGuards,buddySurfaceThread,buddySurfaceStartup,buddySurfaceDays,buddyPrompt,buddyPrompt.orchestrator,agentCliViews,runtimeLayer,wsandOrchestrator.control-reads: 10 files, 158 tests passed.actingSender(12),buddySurfaceFrame,buddySurfacePage,buddySurfaceProtocol,MessagesTimeline.logic,MessagesTimeline.active-toolsandreactCompilerCoverage: 7 files, 1264 tests passed.buddySurfaceDayJump.browser.test.tsxandMessagesTimeline.browser.test.tsxneed Chromium, so CI runs them.tsc-rs --noEmit --singleThreaded: no errors inapps/server(only the knownscripts/liberrors of the filtered install) and none inapps/web. Format is clean on the changed files; lint has 0 errors and the 3 known upstream warnings inws.ts.MessagesTimeline.tsxconflicted: 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=budicekfailed withinvalid_request(exit 1).cs-CZen-USScreenshots (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):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 againstsrc/lazurio/actingSender.test.ts(12 tests):apps/webunit 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/serveracting-sender files (their comments changed): 5 files, 47 tests passed.tsc-rs --noEmit --singleThreadedinapps/web: no errors. Lint (0 warnings, 0 errors) and format are clean on the changed files. The contract test passed 39 of 39.4ee0fda4d, set up as below. Seeded through the realt3: 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, aMutationObserveron the page recorded every node with "Delegace" in its text that was ever added.cs-CZen-USScreenshots (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 what4ee0fda4dreplaced. The fix after it changes only when the server records a step, not what the web shows.cs-CZand once inen-US.t3CLI: 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.cs-CZen-USnpm testpermission request Buddy approvedThe 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.Risks
McpOAuth.tsandMcpInvocationContext.ts, plus small ones in five allowlisted files. A rebuild must keep the claim next tortcand 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.startup.enqueueCommandthat runs before the server is ready keeps the claim, because the claim is provided on the queued effect itself.MAX(sequence)query), and a record reads the thread's record file to keep the first record.getThreadEventSequenceis the thread's highest event sequence.actingSendersReplay.test.tsruns on upstream's own orchestrator, so a rebuild that changes either fails there.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.agentrozjedemeai), changes requested ond6e918c32, 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-keymakes 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.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.mainafter 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 renamedbudicek→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 headb06ec0015; the conflicts and the rename are listed above under "Rebase ontomain626cf804and the rename".879289d98also writes the test's right-to-left override as\u202Eagain, 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
T3CODE_ACTING_SENDER=buddy,T3CODE_ACTING_FORandT3CODE_ACTING_FOR_ACCUSATIVEfrom the persona: in thebuddyprovider instance'senvironmentin~/.t3-buddy/userdata/settings.json, and in the Hermes gateway's unit.T3CODE_ACTING_SENDER=delegace.lazurio-t3-buddy.serviceor in the person's Chat T3 service.t3in another Environment over SSH, since they do not cross SSH.T3CODE_ACTING_SENDER=delegace t3 thread send <threadId> --mode queue --idempotency-key <wake_key> --message-file -, without--sender-label.--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.t3 thread readcould show the mark to agents, and a queued message Buddy edits could carry it.🤖 Generated with Claude Code
Note
Add acting-sender attribution for Buddy and budíček messages and request decisions
T3CODE_ACTING_SENDER,T3CODE_ACTING_FOR, andT3CODE_ACTING_FOR_ACCUSATIVEenvironment variables, validated in actingSender.tsSessionStoreandEnvironmentAuthGET /api/lazurio/acting-sendersroute (mounted in the overlay) and web timeline labels in MessagesTimeline.tsx showing who sent a message or decided a requestt3 lazurio capabilitiescommand now reportsactingSenderLazurioActingSenders.recordStepsdecorator in runtimeLayer.ts; invalid sender environment configuration fails agent CLI commands withinvalid_request; session tokens can only carry an acting-sender claim at issuance, not afterMacroscope summarized 54d73ab.