Module: Pikuri

Defined in:
lib/pikuri-core.rb,
lib/pikuri/tool.rb,
lib/pikuri/agent.rb,
lib/pikuri/paths.rb,
lib/pikuri/testing.rb,
lib/pikuri/version.rb,
lib/pikuri/trifecta.rb,
lib/pikuri/extractor.rb,
lib/pikuri/file_type.rb,
lib/pikuri/sanitizer.rb,
lib/pikuri/url_cache.rb,
lib/pikuri/finalizers.rb,
lib/pikuri/subprocess.rb,
lib/pikuri/tool/fetch.rb,
lib/pikuri/agent/event.rb,
lib/pikuri/bundler_env.rb,
lib/pikuri/tool/scraper.rb,
lib/pikuri/agent/control.rb,
lib/pikuri/agent/history.rb,
lib/pikuri/trifecta/node.rb,
lib/pikuri/agent/listener.rb,
lib/pikuri/extractor/html.rb,
lib/pikuri/agent/extension.rb,
lib/pikuri/tool/calculator.rb,
lib/pikuri/tool/parameters.rb,
lib/pikuri/tool/search/exa.rb,
lib/pikuri/tool/web_scrape.rb,
lib/pikuri/tool/web_search.rb,
lib/pikuri/trifecta/report.rb,
lib/pikuri/ruby_llm_patches.rb,
lib/pikuri/agent/synthesizer.rb,
lib/pikuri/tool/search/brave.rb,
lib/pikuri/agent/configurator.rb,
lib/pikuri/tool/search/result.rb,
lib/pikuri/tool/trifecta_legs.rb,
lib/pikuri/agent/listener_list.rb,
lib/pikuri/tool/search/engines.rb,
lib/pikuri/agent/chat_transport.rb,
lib/pikuri/tool/execute_context.rb,
lib/pikuri/extractor/passthrough.rb,
lib/pikuri/trifecta/contribution.rb,
lib/pikuri/tool/search/duckduckgo.rb,
lib/pikuri/agent/extension_context.rb,
lib/pikuri/agent/listener/terminal.rb,
lib/pikuri/agent/control/interloper.rb,
lib/pikuri/agent/control/step_limit.rb,
lib/pikuri/agent/listener/token_log.rb,
lib/pikuri/tool/search/rate_limiter.rb,
lib/pikuri/agent/control/cancellable.rb,
lib/pikuri/agent/listener/rate_limited.rb,
lib/pikuri/agent/context_window_detector.rb,
lib/pikuri/agent/listener/in_memory_event_list.rb

Overview

Boot file: configures the Zeitwerk autoloader for pikuri-core/lib/pikuri/ and eager-loads it, so after require 'pikuri-core' every constant pikuri-core ships is defined. Sibling gems set up their own loaders into the same Pikuri:: namespace.

The Pikuri module also owns the logging surface (Pikuri.logger_for, Pikuri.log_io=, the PIKURI_LOG / PIKURI_LOG_<NAME> env vars): each subsystem holds a memoized Logger writing through a shared IO that Pikuri.log_io= swaps in one shot.

Why eager-load

The stateless bundled tools (+Tool::CALCULATOR+, Tool::WEB_SCRAPE, Tool::FETCH) are ALL_CAPS value constants, and Zeitwerk only auto-loads constants matching its filename↔CamelCase convention. Eager-loading runs the files defining those values so a bin script can c.add_tool them without per-file require. (web_search is host-configured via Tool::WebSearch.build, so it's not a value constant.) Cost: a few ms.

Defined Under Namespace

Modules: BundlerEnv, Extractor, FileType, Finalizers, Paths, RubyLlmPatches, Sanitizer, Testing, Trifecta Classes: Agent, Subprocess, Tool, UrlCache

Constant Summary collapse

PROMPT_DIRS =

Search path for bundled system prompts. Mutable: each pikuri gem appends its own prompts/ at boot, so Pikuri.prompt(name) from a host requiring pikuri-code finds coding-system-prompt.txt there. Public so a downstream user can read pikuri's prompts as a starting point; prefer prompt.

Returns:

  • (Array<String>)
[File.expand_path('../prompts', __dir__)]
LOG_LEVELS =

Mapping from PIKURI_LOG env-var values (lowercased) to Logger level constants. Anything else falls back to INFO.

Returns:

  • (Hash{String=>Integer})
{
  'debug' => Logger::DEBUG,
  'info'  => Logger::INFO,
  'warn'  => Logger::WARN,
  'error' => Logger::ERROR,
  'fatal' => Logger::FATAL
}.freeze
Loader =

Zeitwerk loader for every constant under pikuri-core/lib/pikuri/. A constant (not block-scoped) so a downstream host can add ignore rules without monkey-patching. Sibling gems set up their own loaders.

Returns:

  • (Zeitwerk::Loader)
Zeitwerk::Loader.new
VERSION =

Gem version, advertised in pikuri.gemspec. Bump on every release following semver: patch for bug fixes, minor for backward-compatible additions to the public surface (+Pikuri::Tool+ / Pikuri::Agent / listeners / bundled tools), major for breaking changes to that surface or to the bin/pikuri-* CLIs.

'0.1.0'

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.log_ioIO

Returns shared sink every logger_for writes through.

Returns:

  • (IO)

    shared sink every logger_for writes through



56
57
58
# File 'lib/pikuri-core.rb', line 56

def log_io
  @log_io
end

Class Method Details

.logger_for(name) ⇒ Logger

Memoized Logger tagged with name as its progname. Level resolves PIKURI_LOG_<NAME>PIKURI_LOGINFO. Repeated calls with the same name return the same instance, so log_io= can rewire them all.

Parameters:

  • name (String)

    subsystem tag (the progname)

Returns:

  • (Logger)


84
85
86
87
88
89
90
91
# File 'lib/pikuri-core.rb', line 84

def logger_for(name)
  @log_loggers[name] ||= begin
    lg = Logger.new(@log_io, progname: name)
    override = ENV["PIKURI_LOG_#{name.upcase}"].to_s.downcase
    lg.level = LOG_LEVELS.fetch(override, @log_default)
    lg
  end
end

.prompt(name) ⇒ String

Read a bundled prompt by basename, searching PROMPT_DIRS in order (+.txt+ auto-appended; Symbols accepted). For downstream users bootstrapping their own Agent wiring from pikuri's defaults.

Examples:

agent = Pikuri::Agent.new(system_prompt: Pikuri.prompt(:'pikuri-chat'), ...)

Parameters:

  • name (String, Symbol)

    basename of the prompt file

Returns:

  • (String)

    file contents

Raises:

  • (ArgumentError)

    if no matching file exists in any PROMPT_DIRS



103
104
105
106
107
108
109
110
111
112
# File 'lib/pikuri-core.rb', line 103

def prompt(name)
  basename = name.to_s
  basename += '.txt' unless basename.end_with?('.txt')
  PROMPT_DIRS.each do |dir|
    path = File.join(dir, basename)
    return File.read(path) if File.exist?(path)
  end
  available = PROMPT_DIRS.flat_map { |dir| Dir.exist?(dir) ? Dir.children(dir) : [] }.sort.uniq
  raise ArgumentError, "Unknown pikuri prompt #{name.inspect}; available: #{available.join(', ')}"
end

.prompts(*names) ⇒ String

Read several bundled prompts and join them into one system prompt — the way a binary assembles a base prompt plus shared fragments (e.g. the tool-loop hygiene in agent-loop.txt).

system_prompt = Pikuri.prompts(:'pikuri-chat', :'agent-loop')
system_prompt += "\n\nCurrent date: #{...}"   # dynamic tail, still one blank line

Each fragment is right-stripped before joining with a single blank line, and the result carries no trailing newline — so neither the fragment seams nor a later +"\n\n"+-prefixed append double-blanks, regardless of the .txt files' own trailing newlines.

Parameters:

  • names (Array<String, Symbol>)

    prompt basenames, in order

Returns:

  • (String)

    the fragments joined by a blank line

Raises:

  • (ArgumentError)

    if any name has no matching file



129
130
131
# File 'lib/pikuri-core.rb', line 129

def prompts(*names)
  names.map { |name| prompt(name).rstrip }.join("\n\n")
end