Class: Insika::Onboarding

Inherits:
Object
  • Object
show all
Defined in:
lib/insika/onboarding.rb

Overview

LLM-first onboarding surface (item 20 / §5.6). The "Flue trick": the insika serves, from itself, a start.md addressed to the DEVELOPER'S OWN coding agent ("Read /start.md then help me build my first agent") plus a machine-readable /models.json and the public docs mirrored as raw markdown. It is rails new reimplemented as a prompt, with the generator being the coding agent the developer already has.

Pure and data-defined: everything it serves comes from injected sources — a start.md TEMPLATE file, a NAME=>path map of public docs, and (optionally) the SettingsStore / LLMProviderStore / the served agents. It only READS (no writes, no RubyLLM, no Executor), so the transport can call it under the constitutional rule. Secrets never leak: models_json reads the MASKED provider view and omits base urls/keys entirely — only slugs and model ids, which is all a coding agent needs to write a correct model/provider line.

Injection (all optional but template_path/docs):

template_path  -> the start.md source (a real file, so it stays reviewable/raw)
docs           -> { "slug" => "/abs/path.md", … } served at GET /docs/:slug.md.
                An explicit ALLOWLIST — the gitignored internal docs are never
                in it, and there is no filesystem traversal (only map keys).
settings_store -> platform default_model / fallbacks / thinking (nil = omit)
provider_store -> configured providers + their model ids, MASKED (nil = omit)
agents         -> callable returning [{ id:, model:, provider:, description: }]
                for the agents already served here (nil = none)

Constant Summary collapse

MODELS_SCHEMA_VERSION =

Bumped when the models.json SHAPE changes (its own contract, independent of the settings schema). A consuming coding agent can branch on it.

1
PUBLIC_DOCS =

The PUBLIC docs allowlist, repo-relative: slug => path. Explicit on purpose — the gitignored internal docs (FOLLOWUP / techspec / TRANSLATION-TRACKER / …) are NEVER here, so /docs can only ever serve OSS material. Kept in sync with the tracked .md prose (README + docs/*.md).

{
  "readme" => "README.md",
  "why" => "docs/WHY.md",
  "agents" => "docs/AGENTS.md",
  "tools" => "docs/TOOLS.md",
  "skills" => "docs/SKILLS.md",
  "context" => "docs/CONTEXT.md",
  "workflows" => "docs/WORKFLOWS.md",
  "channels" => "docs/CHANNELS.md",
  "plugins" => "docs/PLUGINS.md",
  "security" => "docs/SECURITY.md",
  "architecture" => "docs/ARCHITECTURE.md",
  "running-local" => "docs/RUNNING-LOCAL.md",
  "deploy" => "docs/DEPLOY.md",
  "embedding" => "docs/EMBEDDING.md",
  "sandbox" => "docs/SANDBOX.md",
  "benchmark" => "docs/BENCHMARK.md",
  "observability" => "docs/OBSERVABILITY.md",
  "evals" => "docs/EVALS.md",
  "refinement" => "docs/REFINEMENT.md",
  "loadtest" => "docs/LOADTEST.md",
  "releasing" => "docs/RELEASING.md"
}.freeze
TEMPLATE =

Repo-relative path to the start.md template.

"docs/onboarding/start.md"

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(template_path:, docs: {}, settings_store: nil, provider_store: nil, agents: nil) ⇒ Onboarding

Returns a new instance of Onboarding.



78
79
80
81
82
83
84
# File 'lib/insika/onboarding.rb', line 78

def initialize(template_path:, docs: {}, settings_store: nil, provider_store: nil, agents: nil)
  @template_path = template_path
  @docs = docs || {}
  @settings_store = settings_store
  @provider_store = provider_store
  @agents = agents # callable -> [Hash] | nil
end

Class Method Details

.standard(root:, settings_store: nil, provider_store: nil, agents: nil) ⇒ Object

Builds the standard onboarding surface rooted at root (the repo/gem root), wiring the PUBLIC_DOCS allowlist + start.md template. The three composition roots (minimal wiring, DSL serve, deployment) pass their own stores/agents on top. A doc whose file is absent (a slimmed-down gem) is simply dropped — never a boot failure.



69
70
71
72
73
74
75
76
# File 'lib/insika/onboarding.rb', line 69

def self.standard(root:, settings_store: nil, provider_store: nil, agents: nil)
  docs = PUBLIC_DOCS.each_with_object({}) do |(slug, rel), acc|
    path = File.join(root, rel)
    acc[slug] = path if File.file?(path)
  end
  new(template_path: File.join(root, TEMPLATE), docs: docs,
      settings_store: settings_store, provider_store: provider_store, agents: agents)
end

Instance Method Details

#doc(slug) ⇒ Object

Raw markdown for one public doc, by slug. nil = unknown slug (the transport 404s). No path traversal is possible: slug must be a KEY of the injected allowlist.

The Jekyll frontmatter the docs site needs (title/parent/nav_order — the same files ARE the site's pages) is STRIPPED here: a coding agent asked for the prose, not for sidebar metadata, and this keeps the response byte-identical to what it was before the site existed.



131
132
133
# File 'lib/insika/onboarding.rb', line 131

def doc(slug)
  read_doc(slug)&.sub(FRONTMATTER, "")
end

#docs_index(base_url:) ⇒ Object

The public docs index (name + title + fetchable raw-markdown url). Drives discovery: a coding agent lists this, then GETs the ones it needs.



117
118
119
120
121
122
# File 'lib/insika/onboarding.rb', line 117

def docs_index(base_url:)
  base = normalize_base(base_url)
  @docs.keys.sort.map do |slug|
    { name: slug, title: doc_title(slug), url: "#{base}/docs/#{slug}.md" }
  end
end

#models_json(base_url:) ⇒ Object

Machine-readable model catalog. Everything a coding agent needs to write a valid model/provider line, and nothing secret. Sources that are absent (nil store) simply drop their key — a fresh DSL serve still returns a coherent document (served agents + thinking levels + whatever default is set).



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/insika/onboarding.rb', line 99

def models_json(base_url:)
  base = normalize_base(base_url)
  settings = @settings_store&.get || {}
  {
    schema_version: MODELS_SCHEMA_VERSION,
    base_url: base,
    responses_url: "#{base}/v1/responses",
    default: default_model(settings),
    fallbacks: fallback_models(settings),
    utility_model: presence(settings["utility_model"]),
    thinking_levels: ModelSelection::THINKING_LEVELS,
    providers: providers,
    agents: served_agents
  }.compact
end

#start_md(base_url:) ⇒ Object

The onboarding skill (raw markdown), with the live base url interpolated so the coding agent knows where to fetch the models list and docs. Read on each request (the file is small and this surface is low-traffic) — editing start.md needs no restart.



90
91
92
93
# File 'lib/insika/onboarding.rb', line 90

def start_md(base_url:)
  base = normalize_base(base_url)
  substitute(File.read(@template_path), base)
end