Skip to content

Vault: an agent's secret request and the agents' guidance (root decision 0198 point 9, DEV-6631 tasks 651 and 652) - #401

Merged
immakermatty merged 10 commits into
mainfrom
agent/DEV-6631-secret-requests
Oct 14, 2026
Merged

immakermatty merged 10 commits into
mainfrom
agent/DEV-6631-secret-requests

Conversation

@immakermatty

@immakermatty immakermatty commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

Root decision 0198 point 9: a secret never reaches agents through chat or a file, and a person needs no account in the vault to hand one over. An agent gets it in this order:

  1. (a) The person signs in to the service in the Environment's shared browser window, and the Bitwarden extension saves it.
  2. (b) The agent creates the secret itself in a signed-in session of the service.
  3. (c) Otherwise the agent asks for it in words the person understands: what it needs, why, and where in the service to find it.

Until now an agent had no way to do (c) and no instructions for the order. This PR builds (c) end to end and teaches agents a → b → c (DEV-6631 tasks 651 and 652). It builds on the vault (#391) and on Nastavení → Trezor (#402), both merged. Decision F54 records it.

Target state

  • lazurio vault request creates a request:
    • Arguments: --item <name> --need <what> --why <why> --where <where in the service> --field <Name>[:secret] ... (1–10 fields).
    • It prints the request's id and its page, <the Folder's recorded external origin>/settings/vault/requests/<id>. Where no entry is recorded, it prints the page's path instead. --json gives the request with link.
    • request status <id> answers pending, saved (with the item's name) or declined; request list lists every request.
    • Input is validated: one line each, bounded lengths, unique field names, at most 50 waiting.
    • A request is refused unless the Environment's own vault is connected: not while its creation is unresolved (creating), and never for a 0197 pilot.
  • One core for the CLI and the Launchpad, coordinated by files and the account's kernel lock: one JSON per request in the account's custody (requests/<id>.json, 0600 in 0700) with a random 24-character base32 id, and never a value. The CLI never calls the Launchpad.
  • Admitted, same-origin routes: GET /api/vault/requests (waiting), GET /api/vault/requests/<id>, POST …/<id>/save {values} and POST …/<id>/decline {}. Any admitted person answers; no account in the vault is needed.
    • A save holds the account's lock, so a request is answered once.
    • It writes exactly one Secure note named like the request into the collection Agenti: one custom field per request field (hidden for a secret one, text otherwise), with the need, why and where as its notes.
    • The item goes through bw create item with the encoded item on stdin (a new stdin option of the ToolRunner), never on argv. Then bw sync, and the request is saved.
    • A vault that refuses or does not answer leaves the request waiting.
  • The page /settings/vault/requests/<id> is a sub-route of Nastavení → Trezor, like Integrace's forms:
    • the need as its title, then why and where;
    • one input per field (a password input with "Zobrazit" for a secret one) and the item it becomes;
    • "Uložit do trezoru" and "Odmítnout".
    • On its route, the request page alone shows of the Trezor section.
  • "Žádosti agentů" fills Vault: Nastavení → Trezor and the Správci of the Environment's own vault (DEV-6631 tasks 648, 638b) #402's slot #vault-requests above the vault: the count and one row per waiting request, each with "Zadat".
  • Values never go to browser storage, the address or a log; the inputs are emptied once the request is answered. The texts are formal Czech, neutral and minimal.
  • Runtime secrets (F46) may also come from a Secure note with exactly one hidden field, besides a Login's password. Two hidden fields are ambiguous. The frozen 0197 pilot reads passwords only, as before.
  • Agents' guidance (task 652) is in the bitwarden usage text and the manual's Secrets section, template revision base-instructions-50. It tells agents:
    • never ask for a secret in chat and never take one from a file;
    • re-check lazurio vault status before saying anything about the vault;
    • read secrets only from the vault through the shared session, and never print values;
    • get what is missing by (a), worded as conditional on the extension being present; then (b), with bw encode piped into bw create item, never as an argument; then (c), lazurio vault request in plain words and formal Czech, handing over the link and going on with other work.

What changes

  • src/tools/status.ts: the ToolRunner takes stdin.
  • src/vault/bw.ts: createNote sends the encoded note on stdin and reads only the new id from bw's answer. The item reader keeps a Secure note's hidden-field count and the value of the only one.
  • src/vault/session.ts and flow.ts: readSecrets reads Secure notes for the own vault (notes: true), and the journal gains a request operation.
  • New core files: src/vault/requests.ts (the core) and src/vault/request-id.ts (the import-free id pattern and page path). src/vault/cli.ts gains request, request status and request list.
  • New src/launchpad/vault-request-routes.ts, composed in server.ts on the same vaultHostOf as the Tools row and the Trezor page.
  • routes.ts gets the request sub-route of vault. index.html gets the first paint, the request page #vault-request as the last child of Vault: Nastavení → Trezor and the Správci of the Environment's own vault (DEV-6631 tasks 648, 638b) #402's section, the comment of its slot, and CSS. New vault-requests-panel.ts and vault-requests-view.ts, plus changes to ui.ts and messages.ts (en, and formal neutral cs).
  • src/tools/catalog.ts and src/folder/manual.ts carry paths a, b and c. render.ts moves to base-instructions-50 (main's Copy: address people formally (vykání) in Launchpad, shell, CLI and agents' speech #328 took 48 and Vault: each Environment owns its vault (root decision 0198, DEV-6631 task 638a) #391 took 49), with its pinned digest, the folder-tools test and the snapshots.
  • Docs: new decision F54 (main took F52 for the module generator, and the vault is F53). F46 and F53 point to it. Also updated: environment-tools.md, launchpad-development.md, module-adoption.md and public-development.md.
  • Tests: new tests/vault-requests.test.ts and tests/vault-requests-view.test.ts. Extended: the vault-secrets, vault-pilot, launchpad-routes, launchpad-first-paint and folder-manual tests. The fake bw now creates items from stdin, as the real one does.

What deliberately does not change

Verification

  • bun run check on head 8d7f316fa0dd882708f3ccf27e937d958cea333a, rebased on main 1cc2ef6: Biome, tsc, the public guard, the proof build and smoke, then 3528 pass, 8 skip, 0 fail across 251 files, exit 0 (650 s).

  • GitHub's checks on this head passed: static checks, tests 1/2 and 2/2, check ubuntu-24.04 and scope; macOS and Macroscope skipped.

  • Contract tests on the real consumers, against the fake Vaultwarden and the fake bw (tests/vault-requests.test.ts, 10 tests):

    • create, list and status;
    • invalid words and fields;
    • no vault, a 0197 pilot or a workstation; too many waiting; a vault whose creation is unresolved (creating);
    • the save writes exactly one Secure note with a text and a hidden field in Agenti, notes = need/why/where. bw's argv is create item --nointeraction only, followed by a sync;
    • the values reach no argv ($HOME/bw.calls), request file, file of this Platform, journal or answer;
    • answered once;
    • read right away as a runtime secret;
    • decline;
    • values that are not exactly the fields;
    • no token, a wrong token, no Origin or a foreign Origin refused locally (403); a cross-site or foreign-origin save and a missing cookie refused behind the gateway (401);
    • a refused or unreachable vault keeps the request.
  • F46: tests/vault-secrets.test.ts and tests/vault-pilot.test.ts.

  • The page: tests/launchpad-routes.test.ts, tests/launchpad-first-paint.test.ts and tests/vault-requests-view.test.ts.

  • The guidance: tests/folder-manual.test.ts renders every preset in both languages and checks the order a, b, c and each path's commands. The revision is pinned in tests/folder-template-revision.test.ts.

  • In a browser (root decision 0178), on this head: a throwaway Launchpad with the fake vault and two requests. The harness is outside the repository, at ~/Lazurio/drafts/DEV-6631-phase1/secret-requests/ui-check/serve.ts. Checked:

    • Vault: Nastavení → Trezor and the Správci of the Environment's own vault (DEV-6631 tasks 648, 638b) #402's Trezor page shows the rule, "Žádosti agentů 2" with "Zadat", then the vault row;
    • a request's page shows alone, with Trezor's own content hidden; "Uložit do trezoru" is enabled once filled;
    • saving gives "Uloženo do trezoru.";
    • the vault holds one Secure note in Agenti with a text and a hidden field;
    • the typed values were nowhere: not in the DOM, browser storage, the address, the Launchpad's log, bw.calls or the request files;
    • back on Trezor the count drops to 1.

    Earlier on the pre-rebase branch: show/hide, decline, an unreachable vault and the retry, and a narrow screen. Screenshots (local, synthetic values): ~/Lazurio/drafts/DEV-6631-phase1/secret-requests/01…13-*.png.

  • End to end against the real image ghcr.io/lazurio/vaultwarden:1.37.4-lazurio.1@sha256:68da93bb…f492 (amd64 under Rosetta in OrbStack) with the real pinned bw 2026.7.0 (macOS arm64), run on the pre-rebase head 18aefea (the request path has not changed since). 22 of 22 checks passed:

    • the real bw lists exactly one Secure note per save in Agenti, Client ID as text and Client secret hidden with the given values, and notes without a value;
    • nothing exists for the declined request, and a second save is refused as answered;
    • vaultSecrets reads the note's hidden field;
    • argv was exactly create item --nointeraction, and the items went on stdin;
    • no value appears in 19 argv lists, 11 journal entries or the request files.

    Everything was removed afterwards. The Linux bw binary was not exercised.

Risks, open questions, follow-ups

  • A save interrupted after bw created the item can leave a duplicate. This happens if the Launchpad stops between bw creating the item and the request being marked saved, or if a timeout hits after the request reached the vault. The request then still waits, and a second save creates another item of the same name, which F46 reads as ambiguous until a person removes one. F54's limits record this.
  • Who answered is not recorded. The Launchpad has the admission, not the person (F11). F53's addendum now names the admitted subject, so a later change could record it.
  • Inputs are single-line, so a multi-line secret (a PEM key) cannot be pasted as is. A browser's own password manager may offer to save a password field; the person decides.
  • Answered requests are kept; nothing prunes them yet.
  • Open question: should lazurio vault request refuse when the vault already holds an item of that name? The guidance tells agents to look first.

Plan and work context

  • Mission Control: DEV-6631 tasks 651 and 652, plan; root decision 0198 point 9; wireframe prototypes-lazurio 794f3735.
  • Worktree: HumanAndMachine-ai_GEN3/.worktrees/productionspace/LazurioPlatform/DEV-6631-secret-requests, branch agent/DEV-6631-secret-requests, on main.
  • Agent / thread: Claude Code (Opus 5.5), a subagent of session b643d1c5-bc04-44a8-a2c5-d80707571628 (claude --resume b643d1c5-bc04-44a8-a2c5-d80707571628).
  • Machine: the Admin's development workstation.

Status: ready for review

🤖 Generated with Claude Code

@immakermatty
immakermatty force-pushed the agent/DEV-6631-secret-requests branch from 18aefea to 2a57a9c Compare October 14, 2026 06:52

@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 review of 2a57a9c against stacked base 3425e71: APPROVED. I traced the request store/lock, CLI, admitted Launchpad routes, stdin-only bw create path, Secure-note reader, UI and guidance. Locally in an isolated clone, git diff --check and check:static passed; 74 focused tests passed (0 failed, 28 snapshots) with Bun 1.4.3 under the host's heavy-command lock. GitHub's current-head checks are green; I did not independently rerun the full 2639-test suite or the real Vaultwarden run. The documented interrupted-create duplicate and absent answerer attribution remain known limits, not additional claims of verification. Public-diff scan found no named client Organization or person; the sole named Organization in added lines is the internal HumanAndMachine-ai reference to a wireframe. No merge performed.

immakermatty and others added 10 commits October 14, 2026 16:08
…s a runtime secret

The ToolRunner takes an optional `stdin`, which the process reads before its
standard input closes. The bw driver gains `createNote`: one Secure note in
the Environment's own vault (organization and collection by ID, one custom
field per value, hidden for a secret one), encoded as `bw encode` would and
written to `bw create item` on stdin, never on argv; of bw's answer, which
prints the whole item with its values, only the new item's id is read.

Runtime secrets (decision F46) now also come from a Secure note with exactly
one hidden custom field, besides a Login's password: what an agent's secret
request becomes (DEV-6631 task 651). A note with more than one hidden field is
`ambiguous` and never guessed; one without is `empty`. The item reader keeps
only the count of a note's hidden fields and the value of the only one; its
text fields and notes are dropped as before. The frozen 0197 pilot subset
reads login passwords only, unchanged.

Tests: the item reader on notes with one and two hidden fields and with
malformed fields; `vaultSecrets` against the fake vault reading a note's one
hidden value, two hidden fields ambiguous, none or an empty one empty, no
other note content in the answer, no value on argv or in the journal; the
pilot ignoring a Secure note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… it into its vault

Root decision 0198 point 9 (c), DEV-6631 task 651: what an agent cannot get
any other way, it asks a person for with `lazurio vault request --item
<name> --need <what> --why <why> --where <where in the service> --field
<Name>[:secret] ...`, which prints the request's id and its page in the
Launchpad (`<external origin>/settings/vault/requests/<id>`); `lazurio vault
request status <id>` answers pending, saved (with the item's name) or
declined, and `list` every request. The words are checked (one line each,
bounded), 1 to 10 fields with unique names, at most 50 waiting, and only
where the Environment's own vault is connected (never a 0197 pilot).

One core for the CLI and the Launchpad, coordinated by files and the
account's kernel lock: one JSON per request under the account's custody
(`requests/<id>.json`, 0600 in 0700), a random 24-character base32 id, and
never a value. The Launchpad's admitted, same-origin routes `GET
/api/vault/requests` (waiting ones), `GET /api/vault/requests/<id>`, `POST
.../<id>/save {values}` and `POST .../<id>/decline` let any admitted person
answer, without an account in the vault. A save, under the lock so a
request is answered once, writes exactly one Secure note named like the
request into the collection Agenti (a hidden field for each secret field,
the need, why and where as its notes) through `bw create item` on stdin,
syncs and marks the request saved; a refusal of the vault leaves it waiting.

Tests over the real consumers (`runVaultCommand`, `startLaunchpad` over HTTP,
locally and behind the gateway) against the fake Vaultwarden and a fake bw
that now creates items from stdin: create, list and status; invalid words
and fields; no vault, a pilot, too many; the save creating exactly one note
with the right fields in Agenti, read right away as a runtime secret; the
values in the vault only (not on argv, in a request file or any file of this
Platform, the journal or an answer); decline; values that are not the
fields; an unadmitted or cross-origin request refused; a refused or
unreachable vault keeping the request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…at or a file

DEV-6631 task 652, root decision 0198 point 9. The `bitwarden` tool's usage
and the manual's Secrets section tell agents: never ask for a secret in chat
and never take one from a file or a message; check the vault's state again
with `lazurio vault status` before saying anything about it; read secrets
only from the vault through the shared session and never print a value; and
get what the vault lacks in this order: (a) where the shared browser window
has the Bitwarden extension, the person signs in to the service there and
confirms saving it into the Environment's vault (worded as conditional: the
extension arrives with a later task); (b) a secret the agent can create
itself in a signed-in session (a new API key or client secret) is created
and stored in the vault with bw, piped through `bw encode` into `bw create
item`, never as an argument; (c) otherwise `lazurio vault request`, phrased
so the person understands what is wanted (what, why, where in the service),
formal Czech, with the link handed over and other work continued meanwhile.

This computer (the local preset) has no vault yet: its manual keeps the
store the Operator names and gains only the rule against chat and files.
Agent instructions stay in the Czech singular; the person-facing example
addresses people formally.

Template revision base-instructions-50 with its pinned digest and the
snapshots; a test renders every preset in both languages and checks the
order a, b, c in the manual and the commands of each path in the tool.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s own page

Root decision 0198 point 9 (c), DEV-6631 task 651, the wireframe's
screens/SecretRequest.tsx and the section of EnvVault.tsx. Nastavení →
Trezor (F53's addendum) gets the sub-route `/settings/vault/requests/<id>`,
like Integrace's forms; the first-paint script and `pageRoute` agree on it.
vault-requests-panel.ts draws "Žádosti agentů" (the count and one row per
waiting request with "Zadat") into the Trezor page's slot `#vault-requests`,
and the request's page into `#vault-request`, which this change adds as the
last of the Trezor section.

The page: the agent's need as its title, then why and where, one input per
field (a password input with "Zobrazit" for a secret one), the item it
becomes, "Uložit do trezoru" (enabled once every field has a value) and
"Odmítnout". A value goes from its input into the one save request and
nowhere else: not to browser storage, the address or a log; the inputs are
emptied once the request is answered, and kept after a refusal the person
can retry (the vault busy or unreachable). Formal Czech, minimal texts. The
request id pattern is shared by the core and the routes
(src/vault/request-id.ts), which import nothing.

Tests: the routes of the section and the page, the first paint, the
view's texts in both languages and its reading of the routes' answers, and
the page serving both routes. Checked in a browser with the fake vault
(section, filling in, show and hide, save, decline, an unreachable vault
and the retry, a narrow screen).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t follow it

The new decision F54 records root decision 0198 point 9 as built: the
request and its CLI, the store beside the account's custody, the admitted
routes, the one Secure note a save writes into Agenti through bw's standard
input, the request page and section, the runtime secret read from a Secure
note's one hidden field, the agents' guidance (paths a, b and c, template
revision base-instructions-50) and the journal; with its honest limits (who
answered is not recorded; a save interrupted after bw created the item can
lead to a second item of the name; answered requests are kept), the
alternatives and how it is verified.

F46 and F53 point to it; environment-tools.md (custody, bw, CLI, Launchpad,
runtime secrets, the agents' texts), launchpad-development.md (the page),
module-adoption.md (a module secret from a Secure note, asked for with a
request) and public-development.md (a value entered for a request passes
through memory to bw's stdin and is never written) follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a command, never to print them

`lazurio vault request status` said to read a saved item with `bw get item`,
which prints the item with its values into the agent's output. The line now
says to pass its fields from `bw get item` straight into the command that
needs them, as the tool's usage text does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e requests fill its slot

The Trezor page (F53's addendum) leaves the slot
`<section class="group" id="vault-requests" hidden>` above its vault. The
requests' panel fills that slot with its head and rows instead of a group
of its own. On a request's page the panel marks the Trezor section
(`data-request`) and only `#vault-request` shows of it, whatever else the
Trezor page holds.

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

Since #391's `creating` state (an organization whose creation is not
resolved yet: a receipt with its time and no ids), the request core already
refuses it as `not-connected`, as every vault that is not ready: it needs
the recorded vault and Agenti by ID. A test now writes that record, in its
four-field form with `pendingSince`, and shows a new request and a save
refused while the status reads `creating`, and the waiting request saved
once the vault is ready again.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… F53, as main numbers them

Main took F52 for the module generator and renumbered the vault decision
to F53, so the secret-request decision is F54. The comments of the request
core, its CLI, routes, page and tests, the bw driver, the runner, the
catalog and the manual say so; the docs were renumbered in their own
commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@immakermatty
immakermatty force-pushed the agent/DEV-6631-secret-requests branch from 2a57a9c to 8d7f316 Compare October 14, 2026 14:25

@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-review of 8d7f316 against main 1cc2ef6: APPROVED. I compared the prior approved stack via range-diff, inspected the #402 Trezor slot/request-only route, F54/F53 references and base-instructions-50 pin, and traced the retained request core, stdin-only bw create and Secure-note reader. In a fresh isolated clone, git diff --check and check:static passed; focused tests passed in two serial shards (23 + 74, 0 failed, 28 snapshots) under the host heavy-command lock. Current-head CI is green; no unresolved threads or competing review requests. I did not independently rerun the full suite, real-image e2e or browser smoke; the author reports those separately, with real-image e2e on the pre-rebase head. Known interrupted-create duplication and absent answerer attribution remain documented limits. No merge performed.

@immakermatty
immakermatty merged commit 1d4e24c into main Oct 14, 2026
7 checks passed
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