Skip to content

Build Plan: forge try — instant-gratification onboarding command #350

Description

@initializ-mk

Meta: Detailed build plan for a new forge try onboarding command, filed for execution. Phase 1's "hero skill must be keyless" requirement is coupled to #349 (weather skill → keyless API) — land or account for that first so the demo never dead-ends on a missing key.

Build Plan: forge try — Instant-Gratification Onboarding

Target: Claude Code, direct execution.
Repo: github.com/initializ/forge (multi-module Go workspace, go.work, Go 1.25).
Command owner: forge-cli. Keep forge-core free of any CLI/render dependency.


1. Objective

Ship forge try: a single command that takes a first-time user from install to talking to a working agent whose loop they can watch in under 60 seconds, with zero required decisions — no channels, no secrets file, no forge build, no container, no cluster.

The command must reuse the existing Runner, executor, OAuth flow, audit logger, and progress hooks. It does not introduce a second agent loop, a second scaffold path, or a second provider-resolution path.

The differentiator is that the loop is visible: as the agent picks a skill, calls a tool, and hits an egress check, those events render inline in the terminal. This is the same audit signal the enterprise story rests on, surfaced as the first-run delight.


2. Target experience (build to this exact spec)

$ forge try

  forge try — talking to a live agent in your terminal.
  No build, no cluster. Ctrl-D or /exit to quit.

  Using OpenAI (signed in).           # or "Using ANTHROPIC_API_KEY from env" / "Using local Ollama (llama3.2)"
  Agent: quickstart · skills: weather · tools: http_request, datetime_now, math_calculate

  Try:  what's the weather in Tokyo?
        fetch https://example.com and summarize it
        what's 17% of 4,200?

you › what's the weather in Tokyo, should I pack an umbrella?

  ▸ skill  weather
  ▸ tool   weather_current(location="Tokyo")
  ▸ egress api.open-meteo.com   ✓ allowed
  ◂ 18°C, light rain this evening

agent › 18°C in Tokyo with light rain expected tonight. Yes, take the umbrella.

  audit  {"event":"tool_exec","tool":"weather_current","egress":"api.open-meteo.com","decision":"allow"}

you › ^D

  You just ran an agent whose every tool call and egress you can see and audit.
  Want to keep it and make it yours?  →  forge try --keep   (writes ./forge-quickstart)
  Then edit skills/, run it as a service with `forge serve`, or deploy with `forge package`.

Non-interactive single-shot form (used by CI, tests, and copy-paste docs):

$ forge try --once "what's 2 + 2?"

3. Architecture decisions (locked — do not relitigate)

  1. In-process, not HTTP. forge try builds the Runner/executor in-process and drives it from a stdin REPL. No A2A server, no daemon, no forge serve, no port binding. This is the fastest path and avoids the daemon/port machinery.
  2. Ephemeral workspace by default. Scaffold the demo agent into a temp dir (os.MkdirTemp, cleaned on exit) unless --keep is passed, in which case scaffold into ./forge-quickstart. Never write encrypted secrets to disk for the ephemeral run.
  3. Reuse scaffold() with a preset. Do not fork init logic. Add a preset descriptor that scaffold() consumes so forge try and forge init share one code path.
  4. Reuse the existing provider resolution + OAuth. forge try adds an ordering policy on top of existing resolution (env key → saved OAuth → Ollama → interactive picker). It does not add a new LLM client or new credential store.
  5. Consume existing audit + progress events; do not invent new ones. The visible-loop renderer subscribes to the audit sink and progress hooks already emitted by the runner. No new audit event constants.
  6. Renderer lives in forge-cli. forge-core stays CLI/TTY-agnostic. The pretty renderer is a forge-cli concern.
  7. Hero interaction must be genuinely keyless (beyond the model credential). Confirm the demo skill needs no third-party API key; if weather (embedded) requires one, use a keyless endpoint (e.g. Open-Meteo via http_request or a keyless weather variant) so the demo never dead-ends on a missing key. See Phase 1 verification. (Tracked by Weather embedded skill points at key-gated APIs (never works) — switch to a keyless service #349.)

4. Scope exclusions (out of scope for this doc)

  • Hosted/metered demo key proxied through Initializ. (Highest-value future add; note it, do not build it here.)
  • Any forge ui / web changes. Terminal only. (forge try --ui is a later, separate doc.)
  • Remote skill registry (Tier 3).
  • Authoring any new embedded skill. Reuse the existing registry.
  • Packaging, K8s manifests, egress allowlist files, OTel changes.
  • Channels (Slack/Telegram) — must not appear anywhere in the try path.

5. Reuse inventory (find these before writing new code)

Symbol / path Module Use in forge try
scaffold(...) + init options struct forge-cli/cmd/init.go Scaffold demo agent from a preset
runOAuthFlow() forge-cli/cmd/* (wrapped in cmd/ui.go as OAuthFlowFunc) Browser OpenAI sign-in
oauth.LoadCredentials() forge-core (llm/oauth) Detect existing OAuth session
createProviderClient(...), ResolveModelConfig() forge-cli/runtime/runner.go Build the LLM client
OAuthClient, ResponsesClient, OpenAIClient forge-core/llm/ Existing routing (OAuth→Responses, key→ChatCompletions). Do not mix.
Fallback auto-detect from OPENAI_API_KEY/ANTHROPIC_API_KEY/GEMINI_API_KEY forge-cli/runtime Already exists; reuse for env-key detection
Runner: registerAuditHooks, registerProgressHooks, registerSkillTools, executor build forge-cli/runtime/runner.go Build the loop; attach the renderer
AuditLogger, Emit, EmitFromContext, audit constants (tool_exec, egress_allowed, egress_blocked, guardrail_check, llm_call, session_start/end) forge-core/runtime/audit.go Event source for the renderer
Progress hooks (tool_start/tool_end via registerProgressHooks) forge-cli/runtime/runner.go Inline tool spinner/labels
Embedded skills (weather, http_request builtin, datetime_now, math_calculate, uuid_generate) forge-skills/local/embedded/, forge-core/tools/builtins/ Demo agent capability set
lipgloss (already a dep) Styling the renderer; orange accent #F97316

If any symbol name differs, adapt to the real name — do not create a parallel duplicate.


6. Phases

Each phase must build and run before starting the next. Stop on any failed verification and report; do not proceed.


Phase 1 — Command skeleton + demo preset + ephemeral scaffold

Goal: forge try exists, scaffolds the demo agent into a temp dir, loads it, prints the agent summary, and exits cleanly. No LLM call yet.

File Change
forge-cli/cmd/try.go New. tryCmd cobra command. Flags: --keep, --provider, --model, --once <prompt>, --quiet, --audit, --yes. Positional optional first prompt.
forge-cli/cmd/root.go (or wherever rootCmd.AddCommand lives) Register tryCmd. Optional hidden alias demo.
forge-cli/cmd/init.go Add a Preset field to the init/scaffold options and a quickstartPreset() returning the demo agent definition (see below). scaffold() consumes the preset instead of wizard answers. Do not duplicate scaffold.
forge-cli/internal/preset/quickstart.go New (or inline in init.go). The demo agent: agent_id: quickstart, resolved model, skills: [weather-or-keyless-equivalent], tools: [http_request, datetime_now, math_calculate], egress auto-derived, no channels, memory in-memory. Include a short system prompt describing the capabilities and the three suggested prompts.

Demo preset requirements

  • Egress mode: allowlist, domains auto-derived from the chosen skill (no dev-open).
  • No channels, no secrets file, no long-term memory.
  • System prompt: friendly general assistant; may check weather, fetch URLs, do math/time; concise answers.

Verification

go build ./...
go vet ./...
# ephemeral scaffold + load, no model call:
forge try --yes --provider ollama --model llama3.2 --once ""   # empty prompt -> load + summary + exit
# expect: agent summary line printed, temp dir created then removed, exit 0
ls ./forge-quickstart 2>/dev/null && echo "LEAK: ephemeral wrote to cwd (should not)"
forge try --keep --yes --provider ollama --once "" && test -f ./forge-quickstart/forge.yaml && echo "keep OK"
rm -rf ./forge-quickstart

Verify keylessness of the hero skill (blocking): confirm the weather embedded skill needs no third-party API key at runtime. If it does, switch the preset's hero to a keyless path (Open-Meteo https://api.open-meteo.com via http_request, or a keyless weather variant) and update the suggested prompts + egress domain accordingly. The hero interaction must never fail on a missing third-party key. (See #349.)

Anti-patterns

  • Do not copy/paste scaffold into a try-specific function.
  • Do not add a channel or secrets step.
  • Do not leave the temp dir behind on exit (defer cleanup, including on Ctrl-C).

Done when: forge try scaffolds, loads, summarizes, and cleans up, with --keep writing to ./forge-quickstart.


Phase 2 — Model credential auto-resolution

Goal: forge try picks the fastest available model credential with no prompts when one exists, and offers a minimal picker only when nothing is available.

File Change
forge-cli/cmd/try.go Add resolveTryProvider(ctx, flags) implementing the ordering policy below. Returns a resolved provider/model + a human label for the summary line.

Resolution order

  1. --provider/--model flags, if set → use them.
  2. Env key present (ANTHROPIC_API_KEYOPENAI_API_KEYGEMINI_API_KEY, in that preference) → use silently, print Using <PROVIDER> from env.
  3. Saved OpenAI OAuth session (oauth.LoadCredentials() succeeds) → use OAuth/Responses, print Using OpenAI (signed in).
  4. Ollama daemon reachable at 127.0.0.1:11434 → use with a small default model; print a one-line note that first response may be slow if the model must download. Do not silently block on a multi-GB pull; if the model is absent and no TTY, error with guidance.
  5. Nothing available and TTY present → picker (bubbletea, reuse wizard components): [Sign in with OpenAI (recommended)] [Paste an API key] [Use local Ollama]. OpenAI → runOAuthFlow(). Paste → masked input, held in memory (written to disk only under --keep).
  6. Nothing available and no TTY (CI) → clear error listing the three options.

Respect existing routing: OAuth tokens use the Responses endpoint; API keys use Chat Completions. Never mix (existing invariant). Reuse createProviderClient.

Verification

# env-key path (fastest, silent):
ANTHROPIC_API_KEY=sk-... forge try --once "say hi in 3 words"
# expect: "Using ANTHROPIC_API_KEY from env", a reply, exit 0

# no creds, no TTY -> actionable error, non-zero exit:
env -u ANTHROPIC_API_KEY -u OPENAI_API_KEY -u GEMINI_API_KEY forge try --once "hi" < /dev/null

Anti-patterns

  • No new credential store or .env mutation for the ephemeral run.
  • Do not fall through to an unauthenticated client (existing 401-surfacing behavior must hold).
  • No em-dashes in any printed string (house style).

Done when: each of the four credential sources drives a working reply, and the no-creds path gives an actionable message.


Phase 3 — In-process REPL driving the executor

Goal: A real conversation. Read stdin, run one agent turn per line through the existing executor, stream the assistant reply, maintain history in memory, exit on /exit, Ctrl-D, or Ctrl-C.

File Change
forge-cli/cmd/try.go Add the REPL loop. Build the Runner/executor once (Phase 1 preset + Phase 2 credentials), then per line call the executor with accumulated history. Handle --once as a single turn then exit.
forge-cli/runtime/runner.go If needed, expose a minimal in-process "run one turn with history" entry that the REPL calls, reusing the same executor path forge run uses. Do not add a second executor.

Behavior

  • Maintain conversation history in a slice in memory; append user + assistant + tool messages exactly as the executor loop already does.
  • Stream assistant tokens to stdout under an agent › prefix.
  • Honor Ctrl-C mid-turn via the existing cancellation path (context cancel at iteration/tool boundary).
  • --once <prompt>: run exactly one turn, print, exit with the turn's status code.

Verification

go build ./... && go vet ./...
printf 'what is 2+2?\n/exit\n' | forge try --provider ollama --model llama3.2
forge try --once "fetch https://example.com and give me the page title"
# expect: a tool call happens (http_request), a real answer, clean exit

Anti-patterns

  • Do not stand up the A2A HTTP server or forge serve.
  • Do not reimplement the tool-calling loop; call the existing executor.
  • Do not persist sessions to disk for the ephemeral run.

Done when: multi-turn chat works, tools actually execute, cancel and exit are clean.


Phase 4 — Visible-loop renderer

Goal: The differentiator. Render skill/tool/egress/guardrail events inline as the turn runs, plus one compact audit line per turn. This is what makes the first 60 seconds feel like Forge and not a generic chatbot.

File Change
forge-cli/internal/tryview/renderer.go New. A renderer that subscribes to the audit sink + progress hooks and prints styled lines. Owns all lipgloss styling.
forge-cli/cmd/try.go Attach the renderer to the runner (add it as a secondary audit sink / progress consumer alongside the existing NDJSON sink). Gate with --quiet (hide loop) and --audit (show full NDJSON instead of the compact line).

Event → line mapping

Event Rendered line
skill selected (from tool_exec context / system) ▸ skill <name>
tool_exec phase=start ▸ tool <tool>(<short arg preview>)
egress_allowed ▸ egress <domain> ✓ allowed
egress_blocked ▸ egress <domain> ✗ blocked
guardrail_check decision=blocked ▸ guard <name> ✗ blocked
tool_exec phase=end ◂ <short result preview>
per-turn summary one dim audit {…compact json…} line (suppressed by --quiet, expanded by --audit)
  • Styling: orange accent #F97316 for the /labels; muted grey for the audit line. Auto-detect no-color / non-TTY and degrade to plain text.
  • Truncate arg/result previews (~80 cols); never dump full tool payloads inline.
  • The renderer reads from existing events only. If a field (e.g. domain) is not on the event, omit that line — do not re-derive it.

Verification

# hero path shows the loop:
forge try --once "what's the weather in Tokyo?"   # expect skill+tool+egress lines then the answer
forge try --once "what's the weather in Tokyo?" --quiet   # expect answer only, no loop lines
forge try --once "what's the weather in Tokyo?" --audit    # expect full NDJSON per event

Anti-patterns

  • Do not add new audit event types or emit from forge-core for rendering.
  • Do not put any lipgloss/TTY code in forge-core.
  • Do not block the turn on rendering; rendering is a passive consumer.

Done when: the hero prompt shows skill → tool → egress → answer inline, --quiet and --audit both work, and non-TTY output is clean plain text.


Phase 5 — First-run polish + graduation hook

Goal: Frame the delight and hand the user up the ladder to building their own.

File Change
forge-cli/cmd/try.go Welcome banner (see Section 2). Print the three suggested prompts. On exit (interactive, not --once), print the graduation message.
forge-cli/cmd/try.go If the ephemeral run was not --keep, the exit message offers forge try --keep (writes ./forge-quickstart). If it was --keep, point at editing skills/, forge serve, and forge package.

Copy rules: concise, problem-first, no em-dashes, orange accent only on the command tokens. Do not oversell.

Verification

printf '/exit\n' | forge try --provider ollama --model llama3.2   # expect graduation message
forge try --keep --once "hi" && ls ./forge-quickstart && rm -rf ./forge-quickstart

Done when: banner, suggestions, and the correct graduation message all render.


Phase 6 — Docs: rebuild getting-started around forge try

Goal: Make the first documented step a single command with a payoff, and move the production pipeline out of the "quick start."

File Change
docs/getting-started/quick-start.md Rewrite. First code block is one command: brew install initializ/tap/forge && forge try. Then the hero transcript. Then a 4-rung ladder (below). Remove channels/build/deploy from this page.
docs/getting-started/ship-to-production.md New. Move the existing 7-step pipeline (init → skills → secrets → validate → build → package → deploy) here, unchanged in substance.
docs/getting-started/installation.md Trim so it is not a prerequisite to the quick start; link, don't gate.
README.md (repo root) Replace the getting-started snippet with the forge try one-liner + transcript.
forge.md §15 "How to create an agent" Add a one-line pointer at the top: for a first taste, forge try; the pipeline below is for building your own.

The ladder (put in quick-start.md):

  1. 60s forge try — talk to an agent, watch it use a tool.
  2. 5 min forge try --keep then forge init — make it yours, edit a SKILL.md, add a skill.
  3. 15 min forge serve + tail the audit log + add a guardrail and watch it block.
  4. Ship forge package — egress-enforced container into your own cluster.

Verification

# link check / build of docs site if applicable; otherwise manual review:
grep -n "forge try" docs/getting-started/quick-start.md README.md
grep -rn "slack" docs/getting-started/quick-start.md && echo "FAIL: channels leaked into quick start"

Done when: quick-start's first block is forge try, the pipeline lives on its own page, and no channel/build steps remain in the quick start.


Phase 7 — Tests

File Change
forge-cli/cmd/try_test.go New. Table-driven, stdlib testing, no testify. Cover: preset builds a valid config; resolveTryProvider ordering (env > oauth > ollama > picker) with a mock TTY-absent path; --once single-turn exit code; ephemeral temp dir is removed; --keep writes to cwd.
forge-cli/internal/tryview/renderer_test.go New. Feed synthetic audit/progress events; assert the mapped lines (allowed/blocked/guardrail), truncation, and plain-text (no-color) degradation.

Mock LLM/tool calls with httptest.NewServer where a network hop is implied; do not hit real providers in unit tests.

Verification

go test ./forge-cli/...
go build ./... && go vet ./...

Done when: new tests pass and the whole workspace builds and vets clean.


7. Global anti-pattern checklist (applies to every phase)

  • No second agent loop, scaffold path, or provider-resolution path — reuse.
  • No A2A server, daemon, or port binding in forge try.
  • No forge build, container, K8s, or egress-file generation in the try path.
  • No channels anywhere near try.
  • No new audit event constants; the renderer consumes existing events.
  • No lipgloss/TTY code in forge-core.
  • No secrets written to disk for the ephemeral run (only under --keep).
  • No silent fall-through to an unauthenticated LLM client.
  • No blocking on multi-GB Ollama pulls without a warning and a non-TTY error path.
  • No em-dashes in user-facing strings.

8. Definition of done (whole feature)

forge try on a clean machine with an OpenAI login (or any model key in env) drops the user into a streaming chat with a working agent, the hero prompt shows skill → tool → egress → answer inline plus one audit line, --once gives a one-line non-interactive demo, --keep graduates the agent to ./forge-quickstart, docs lead with the one-liner, and go build ./... && go vet ./... && go test ./forge-cli/... are green.

9. Deferred (name, do not build)

  • Hosted metered demo key via Initializ proxy (true zero-credential first run).
  • forge try --ui (browser variant reusing forge ui chat).
  • A curated second demo agent (e.g. "talk to your codebase" via filesystem + grep skills) once those built-in tools land.

Metadata

Metadata

Assignees

Labels

documentationImprovements or additions to documentationenhancementNew feature or requestforge-cliAffects the forge-cli command-line tool (init, run, build, mcp commands)

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions