Class: Pikuri::Workspace::Extension
- Inherits:
-
Object
- Object
- Pikuri::Workspace::Extension
- Includes:
- Agent::Extension
- Defined in:
- lib/pikuri/workspace/extension.rb
Overview
An Agent::Extension that wires the file tools (Read,
Search::Grep, Search::Glob, FileList, Edit, Write) onto an agent, sharing a single
Workspace composite — which is what makes the read-before-edit gate
work: Read marks a path read in the same record Edit and
overwrite-Write consult. One registration point removes the per-host
add_tool boilerplate. Same opt-in shape as Tasks::Extension:
the host builds the Filesystem and Confirmer and hands them over; the
composite and its read record are internal.
Bash is not here — it isn't a read/edit/write tool and takes the raw Filesystem (it governs the subprocess's fs view, not the read record). It lives in Code::Extension.
Contributes PROMPT_SNIPPET — the "a decline is final" confirmation guidance — so it rides the wiring of the mutating tools instead of being restated in each host prompt, plus the boot snapshot below.
Boot snapshot
A depth-1 Listing of the workspace root, contributed once so
orientation costs zero tool calls — Search::Glob is rg --files, which
emits files only, so glob '*' hides every directory:
<workspace_snapshot>
The workspace root is /home/alice/proj. Its top level, as of the start of this conversation:
lib/ [1 dir, 1 file]
spec/ [1 dir, 1 file]
README.md 2.3K
One level only, and not refreshed — call `file_list` for anything deeper or newer.
</workspace_snapshot>
One level is the design, not a default to grow: Listing's
[N dirs, M files] column already does depth 2's job on the wide-flat
directories where depth 2 costs most — a blog's +_posts/ [0 dirs, 256
files]+ is 30 bytes against 24 KB expanded. Measurements and the rejected
ladder live in DECISIONS.md D_directory_listing.
Re-taken whenever the prompt is re-assembled, so clear_conversation
refreshes it and nothing else does.
Read-only gate
A ReadOnly flag (when the host passes one) is threaded into Edit/Write so they refuse while active — the read-only tools take no gate. The host owns the flag and hands the same instance to whatever drives it (pikuri-code's plan mode). No flag → gate off. Covers Write/Edit only, never Bash — see ReadOnly.
Usage
Pikuri::Agent.new(...) do |c|
c.add_extension Pikuri::Workspace::Extension.new(
filesystem: filesystem, confirmer: confirmer)
end
Constant Summary collapse
- TOOL_CLASSES =
The tools the extension owns, and the duplicate-registration guard list: it must be the single owner of the shared Workspace, so a pre-registered file tool (on a different composite) would defeat the read-ledger sharing.
[Read, Search::Grep, Search::Glob, FileList, Edit, Write].freeze
- PROMPT_SNIPPET =
Returns system-prompt guidance wired alongside the mutating tools: the confirmer may pause a write/edit (and a co-wired Bash) for the user's OK, and a decline is final. Contributed as a snippet so it tracks the wiring rather than each host prompt (the Os/Code precedent), and emitted unconditionally — an auto-approve ("yolo") confirmer is the rare exception, and the guidance is harmless there.
<<~SNIPPET.chomp A tool may ask you to confirm before it makes a change. If the user declines, stop rather than trying another route to the same end — a decline means they want to re-steer you, so hand back and wait for their guidance. SNIPPET
- SNAPSHOT_DEPTH =
Returns
depththe boot snapshot renders at. Deliberately not Listing::DEFAULT_DEPTH: both are 1 today, so folding them together compiles, passes, and silently re-points the snapshot the day FileList's per-call default moves. 1- SNAPSHOT_MAX_BYTES =
Returns byte ceiling on the rendered snapshot; over it the snippet degrades to one line rather than head-truncating, because a truncated listing is an alphabetical prefix that teaches the model
spec/does not exist. Twice the largest root measured (a real$HOME), so it is a fail-safe, not a routine path. 2 * 1024
Instance Attribute Summary collapse
-
#workspace ⇒ Workspace
readonly
The composite shared across the tools; exposed for tests / introspection.
Instance Method Summary collapse
- #configure(c) ⇒ void
-
#initialize(filesystem:, confirmer:, read_only: nil, confirm_all_writes: false) ⇒ Extension
constructor
A new instance of Extension.
-
#on_conversation_reset(_ctx) ⇒ void
Clear the shared read-record when the host clears the conversation, so the read-before-edit gate forgets what the discarded conversation read — else an Edit right after "/clear" could touch a file the model can no longer see.
-
#system_prompt_snippets ⇒ Array<String>
The confirmation guidance (PROMPT_SNIPPET), always — the confirmer is wired on every non-yolo host — followed by the boot snapshot.
Constructor Details
#initialize(filesystem:, confirmer:, read_only: nil, confirm_all_writes: false) ⇒ Extension
Returns a new instance of Extension.
108 109 110 111 112 113 |
# File 'lib/pikuri/workspace/extension.rb', line 108 def initialize(filesystem:, confirmer:, read_only: nil, confirm_all_writes: false) @workspace = Workspace.new(filesystem: filesystem) @confirmer = confirmer @read_only = read_only @confirm_all_writes = confirm_all_writes end |
Instance Attribute Details
#workspace ⇒ Workspace (readonly)
Returns the composite shared across the tools; exposed for tests / introspection.
95 96 97 |
# File 'lib/pikuri/workspace/extension.rb', line 95 def workspace @workspace end |
Instance Method Details
#configure(c) ⇒ void
This method returns an undefined value.
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/pikuri/workspace/extension.rb', line 117 def configure(c) TOOL_CLASSES.each do |cls| if c.tools.any?(cls) raise "#{cls} cannot be pre-registered (in tools: or via c.add_tool) " \ 'when adding Pikuri::Workspace::Extension — the extension auto-registers every ' \ 'file tool so they share one Workspace (and one read record).' end end # The read-only flag (when supplied) is threaded into the two mutators; # the read-only tools take no gate. +nil+ disables it. c.add_tool Read.new(workspace: @workspace) c.add_tool Search::Grep.new(workspace: @workspace) c.add_tool Search::Glob.new(workspace: @workspace) c.add_tool FileList.new(workspace: @workspace) # Edit takes no posture flag: a non-nil confirmer *is* its # confirm-all-writes posture (see {Edit}), so +confirm_all_writes+ # reaches Edit as confirmer-or-nil. c.add_tool Edit.new(workspace: @workspace, read_only: @read_only, confirmer: @confirm_all_writes ? @confirmer : nil) c.add_tool Write.new(workspace: @workspace, confirmer: @confirmer, read_only: @read_only, confirm_all_writes: @confirm_all_writes) nil end |
#on_conversation_reset(_ctx) ⇒ void
This method returns an undefined value.
Clear the shared read-record when the host clears the conversation, so the read-before-edit gate forgets what the discarded conversation read — else an Pikuri::Workspace::Edit right after "/clear" could touch a file the model can no longer see. The Filesystem's policy is untouched; only the read-ledger resets.
150 151 152 153 |
# File 'lib/pikuri/workspace/extension.rb', line 150 def on_conversation_reset(_ctx) @workspace.clear_reads nil end |
#system_prompt_snippets ⇒ Array<String>
Returns the confirmation guidance (PROMPT_SNIPPET), always — the confirmer is wired on every non-yolo host — followed by the boot snapshot.
161 |
# File 'lib/pikuri/workspace/extension.rb', line 161 def system_prompt_snippets = [PROMPT_SNIPPET, workspace_snapshot] |