Repository navigation
Feat/secure room - #295
Open
zonblade wants to merge 95 commits into
Open
Feat/secure room#295zonblade wants to merge 95 commits into
zonblade wants to merge 95 commits into
Conversation
The secure assistant replaces /security with an Orpheus-style room: a Chief that delegates to specialist avatars, who share findings through a shelf. This lands the parts that do not touch the coding chat yet. - registry: prompts, tool definitions, roles, avatars and skills, embedded from src-misc/room with a hot-reloaded per-session overlay that can narrow permissions but never widen them - shelf: SQLite store of claims, evidence, verdicts, findings, reports, assets and trajectories, with revision checks, merging writes and the report-pack rule - coordinator: group_delegate, group_status, group_nudge, group_finish and group_task_complete, with the finish gates and the confirmation queue's completion half - avatars: several avatars may play one specialist role (two Reds), each with its own lanes and models Nothing is wired into the daemon yet; the module is unused at runtime.
The pure-logic half of the lane layer, ahead of the runner that uses it: - inbox: the Chief's priority inbox. A turn is drained highest-first, so an unread user message always leads and a coincident wake rides in the same turn instead of racing ahead. This is the fix for the Orpheus steering problem, where a background wake could answer before the user's message. - control: pause, resume, stop and redirect a work lane, mapped to the action the actor carries out. Pause is cooperative; stop leaves the assignment to be read as uncertain, never silently done. - digest: a compact per-lane picture folded from lane events, which each chat lane reads at turn start so it can report progress without reading the work lanes' chatter. Still unused at runtime; the runner and actor land next.
A lane streams a model reply, runs the tools it asks for, feeds the results back, and loops until the model stops or the step budget runs out. The structure is cloned from app/subagent/engine.rs rather than reused, so the room does not bend into the sub-agent module's shape or break when it changes. The loop reaches the model and koma's built-in tools only through the ModelClient and ToolRouter seams, so it runs under a scripted model and a fake router in tests, with no live provider. A chat lane answers the user in short turns; a work lane runs one assignment and forwards the router's events (a shelf publish, a submit). Steering, pause, resume and stop arrive on the input channel and are applied at a turn boundary. Tested: a chat answer, a work tool round, router events forwarded, a model error, the step budget, steering folded into history, stop, and pause blocking until resume.
The Chief ends its turn after delegating; this brings it back. Each pass reads the lane state behind every pending assignment, moves the assignment to match (a submitted result becomes completed once its lane ends, an unprovable lane becomes uncertain, a cancelled lane cancels the work), then wakes the Chief when the batch settles or needs attention. Waking is idempotent: a content nonce means one distinct batch state wakes the Chief at most once, and a healthily running batch earns only a slow check-in. The automatic review-queue and reporter dispatch is a later step; this syncs and wakes only.
…e reporter The confirmation loop is now automatic, so the Chief no longer delegates every verification by hand. - a probe's published claim is queued for review the moment it and its evidence exist, and the lane is told why when a claim cannot be queued - the next idle reviewer takes up to eight queued claims in one verification, covering at most 32 references - a returned claim goes back to the Red that made it, exactly once: steered into its running lane, or a new probe if it is idle and provably so - a review that fails, is cancelled or is replaced takes its items with it - once a review passes and findings exist, an idle reporter is handed the report pack until it is complete (a detailed technical report per finding plus one manager summary) The reconciler runs this between syncing assignments and deciding to wake the Chief, so a probe that just finished has its review started before the Chief is told the batch settled.
…channel The clerk watches the work lanes and speaks only to the Chief. It cannot pause or stop anything and never edits anyone's work; the Chief decides what a flag is worth. Orpheus's clerk is a room-chatter summariser, so only its rhythm is ported: it runs only while work is active, never twice at once, and a quiet room costs nothing because a model pass starts only when something changed. What a program can establish, a program establishes. A deterministic audit runs on every tick and hands its findings to the clerk's model pass to judge: - a claim or asset targeting a host no scope record covers (a stop finding) - claims published with no scope record at all - a running lane that has gone silent, unless the user paused it - a lane deep into its work that has shared nothing on the shelf - the same assignment given three times Scope matching is strict on purpose, because a false "in scope" hides a real violation: an exact host, an explicit *.domain wildcard (subdomains only), or an IPv4 range, with deny always winning. The clerk lane raises findings with clerk_flag, which can name a specialist lane only. Each flag is also recorded on the shelf as an audit note. Digest events now carry timestamps so stalls can be told from slow work, and the shelf has a cheap fingerprint to tell "nothing new" from "something changed".
The room's map, drawn from the shelf. A lane may publish its own graph of what it is exploring; this merges it with what the shelf proves, so a claim reads as verified only when a finding exists for it. A reviewer's "confirmed" verdict is not enough, and a lane stamping its own node "confirmed" is ignored until the shelf backs it. The lanes' exploration nodes (intent, fact, goal, ...) are kept on top of the shelf's spine, never dropped because a claim exists. Ported from Orpheus shelf-status, trajectory-graph and trajectory-layout, with their unit tests as the spec. The layout keeps the same rank and lane rules, in terminal rows instead of pixels. Views: an outline tree (the default, readable at any width), a layered graph for wide terminals that falls back to the outline when it does not fit, and dot, mermaid and json for reports and tools. Glyphs carry meaning by shape, so the output reads without colour. `koma room trajectory [--goal K] [--format F] [--view V] [--session ID]` is the first change outside the room module: one verb in the parser and one short-circuit in main, the same pattern as `lsp`. It opens the session's database read-only, so it is safe against a live session: no tables, data or schema change. Unknown verbs are still rejected as before. The swim-lane view needs lane activity history rather than the shelf, so it comes with the UI.
Two views the UI needs, both pure logic ahead of the actor and UI. - timeline: who did what, in what order. The digest shows each lane's current state and the shelf shows what was proved; neither keeps a history. This records one milestone per lane as events arrive (opened, published, submitted, failed, flagged) and renders a swim lane per avatar. It lives in the actor's memory, so a restart starts the history fresh. - classify_shelf_items: the per-row gate classification for a status menu (open / verified / returned / report), ported from Orpheus. A verified claim shows through its finding, not twice; a returned verdict surfaces only when its claim is not already shown.
The actor is one state machine that owns the room and connects its parts. Everything arrives on one channel — the user and the control channel, a periodic tick, events from the lanes, and the results of the tools those lanes call — so the actor is a single-threaded loop with no locks of its own. It keeps the Chief's chat lane and the specialist and clerk work lanes; starts a lane for each coordinator StartLane effect and steers or shows the others; folds each lane's events into the digest and the swim-lane timeline; on every tick runs the reconciler and the clerk; and drains the Chief's inbox into one chat turn when the Chief is idle, so an unread user message always leads. A user message while the Chief is busy is injected into the running turn, not dropped. The tool router is the seam between a lane and the room: the room's own tools go to the coordinator and shelf, message_user and clerk_flag become actor messages, and everything else is a koma built-in run through a BuiltinHost. A tool the lane was not offered is refused before it runs. Tested end to end with a scripted model and the real coordinator, shelf and clerk, so the whole room runs with no live provider: the Chief delegates, a work lane publishes to the shelf and submits, the reconciler syncs the probe to completed, message_user reaches the user, and a forged tool name is refused. The two real seams — a model client over stream_complete and a built-in host over execute_tool — and the daemon wiring land next.
Two thin adapters that connect the room to koma's live provider and tools, so the actor runs against them in the daemon exactly as it runs against the scripted model and stub in tests. - ProviderModelClient streams a reply from OpenRouterClient::stream_complete and folds the events into a ModelReply, the way the sub-agent engine does. Every room tool is presented to the model as a function definition. A lane's per-avatar model, provider and effort override a base route; the credentials stay the session's (cross-provider per avatar is a later refinement). - CtxBuiltins runs a lane's built-in tools through execute_tool against a per-avatar ToolCtx, so a red lane's cyber lane and scope differ from a reviewer's (red -> Red, reviewer -> Blue, others -> Research). The daemon builds the contexts and the advertised specs and hands them in. BuiltinHost.run now takes the calling lane's avatar and kind so one host can pick the right context. These compile against the real types; the live path is exercised once the daemon hosts the actor.
RoomSession::open reads a session's room.sqlite (the shelf and work tables share it), loads the registry with the session overlay, spawns the actor, and drives it with a one-second ticker. It takes the model and tool seams as arguments, so it stays free of the daemon's AppState: the daemon builds the real seams, tests pass fakes. Tested from disk: a room opens from a temp session dir and answers a user message, creating its database in the session dir.
koma --daemon --kind secure takes the session and runs the room until a signal. Specialist work lanes use the host tools (bash, files, and for Red the web and browser). Chat lanes, the Chief, and the clerk stay on room tools. A sec_* name is refused.
A restart loads fired nonces from room.sqlite. The same batch state does not wake the Chief again, and the same dispatch line is not posted again.
The secure daemon binds the session socket. A hub status probe still gets a session listing. `koma room attach` prints the room and sends each typed line as the user.
In the main terminal, /security quits the coding daemon and attaches the secure room. /security again returns to the coding chat. koma legacy stays in the coding chat.
A long room added a check-in nonce every few minutes and never dropped one. The actor now prunes at start and on tick once the set passes nonce_cap, keeping the newest nonce_keep.
The room keeps speaking while no terminal is attached, for example during a handoff. The serve loop now keeps the latest status and the last 500 spoken lines and replays them on attach. Hello reports how many frames are history, and an attach asks the actor for a fresh status at once.
SessionScreen returned the room screen above its last useState, usePaste and useKeyboard, so turning the room on rendered fewer hooks and React threw. The return now sits below every hook, and the key and paste handlers do nothing while the room is open. A test mounts the screen and enters the room.
Orpheus keeps a nonce as a unique column on the record it guards and drops it with that record. The room now does the same. The unique key in room.sqlite is the only memory of what fired, so the two in-memory sets and the newest-N eviction are gone. Each nonce carries its work id, and a work that completes or is cancelled takes its nonces with it in the same transaction. An older database gets the column added in place.
User messages are panel cards with an accent rail, other authors sit behind the dotted rail with an inked name, and the composer is the rounded [$] box. Lanes, Swim, Shelf and Trajectory are tabs beside the chat at 100+ columns and one pane at a time below that.
An overlay above the composer lists the matches as you type. Up and Down move, Tab or Enter fills the name, and Enter on a finished word sends.
The daemon lists the models registered in the coding agent and writes a choice into the session overlay, which the registry reloads for the lane's next turn. The terminal shows minion, then lane, then a searchable model list full screen. The chat is now one full-width column with no side pane.
…olours; usage model rows no longer repeat their name when selected
… the state words stay removed
…t list a project's secure sessions, browse the shelf, read an item, goals and reports
…e; /leave stop ends it; the chat shows a secure room marker; Ctrl+C no longer asks; /shelf and /trajectory scroll with page keys and the wheel
…under the other with each child a step to the right
… boxes with a kind and severity line, and a wide layer wraps to the screen width instead of running sideways
…en the wrong one; later ones get (2), (3)
…ee-line boxes, scrolls both ways (shift+wheel goes across) and no longer wraps a layer
… one depth stacked in its column; the wheel scrolls anywhere on the browse and model screens, and the model list pages
…pup: switch keeps the room running, stop ends it
…the test targets pass clippy
…ure-room daemon self-test to the test matrix
…s are dotted, and the CI-only failures are fixed (container overlay owner, graph_query without the linker, Windows pipe and path tests, slow-runner waits)
…s the file, and the test says why a model was refused
…d save names the file and step
…ctory, since a Windows named pipe has no parent folder
…robe, a reverse, a hardening pass and a build each have a procedure instead of only a shelf habit red enumerates the surface, names the attack classes per component, tests the refused path as well as the working one, and reproduces before it claims. re starts from strings/imports/xrefs and confirms a decompiler reading against behavior. blue works from verified findings and writes the check that fails first. researcher prefers primary sources and states what it could not establish. builder reads, changes narrowly, and runs it. The chief is told to name the goal and the surface and leave the method to the lane. Prompts stay affirmative and calm: the added strength is method and duty, not absolutes, so it does not read as a conflict signal to the model's safety training.
… tags, with a per-role scope and pinned skills
…kill_modify replace the skill tool, and the prompt carries a stub plus pinned skills instead of every skill
…nd falls back to the word match when it cannot
… scoped by its role's skills_allow, and a loaded skill stays in that lane's prompt until unloaded
…ill_find is boxed, and the GUI gives them a skill icon
…e names skill_create and skill_modify
… the wire tests no longer share a name when they start in the same clock tick
skills: find them by asking, not by listing (chat and secure room)
…r team, and the daemon labels each line for the user or the team
…ping a fixed 30 ms, which a slow runner misses
…eeping a fixed 80 ms
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.