Skip to content

ginny — Natural-Language → Typed Programs

Ginny is a CLI agent built on @aeye that turns natural-language requests into executable gin programs. Every type, function, and var the LLM creates is persisted as JSON in your project, building a typed catalog that grows with your codebase.

bash
npm install -g @aeye/ginny
cd my-project
ginny                       # opens an interactive REPL
ginny "add 2 and 3"         # one-shot

View source on GitHub

What it demonstrates

FeatureHow ginny uses it
Multi-providerOpenAI + OpenRouter + AWS Bedrock; credentials probed via standard SDK chain (env, aws sso login, IAM role, ~/.aws/credentials).
Sub-agentsFive specialized agents (programmer / architect / designer / dba / researcher), each with their own toolset. The programmer recursively spins up new programmers when designing fn bodies.
Adaptive tool selectionCatalog searches use embedding-based filtering when corpus size exceeds a threshold; small catalogs return everything.
Structured outputSub-agents produce typed gin ExprDef / TypeDef JSON via Zod schemas — the LLM literally cannot return invalid expressions.
Strict modeWires in strictSupport from @aeye/models so gpt-4o / claude 4.5+ / gemini 2.0+ get grammar-constrained tool inputs.
Prompt filesLoads cletus.md / agents.md / claude.md from the working directory as system context.
Context managementPer-sub-agent context with run-state isolation; usage and cost accumulate across the session.
HooksProvider-level request/response logging with payload-size tracking; AI-level model-selection logging.

Architecture

A small council of sub-agents, each with one job:

                          ┌─────────────┐
       user request  ──▶  │  programmer │
                          └──────┬──────┘
         ┌────────────────┬──────┴──────┬────────────────┐
         ▼                ▼             ▼                ▼
  ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
  │  architect  │ │   designer   │ │     dba      │ │  researcher  │
  │   (types)   │ │    (fns)     │ │    (vars)    │ │  (web search │
  │             │ │              │ │              │ │   + pages)   │
  └─────────────┘ └──────┬───────┘ └──────────────┘ └──────────────┘

                         ▼ (recursive spin-up)
                    programmer
  • programmer — writes a draft gin program, calls test() against sample args, calls finish() when a test passes. Uses find_or_create_* tools to pull in catalog entries; uses edit_type / edit_fn for backwards-compatible edits.
  • architect / designer / dba — search-and-create patterns over ./types/*.json, ./fns/*.json, ./vars/*.json.
  • researcher — wraps web_search (Tavily) + web_get_page, returns { answer, sources }.

Each sub-agent is an @aeye Prompt with its own toolset; when the programmer needs a new function, it asks the designer, which spawns another programmer to author the body. Recursion is a first-class workflow primitive.

Persistence

Catalog entries are one JSON file per name, in three CWD-relative directories:

./types/Task.json           # the Task type
./fns/factorial.json        # the factorial function
./vars/apiBaseUrl.json      # a persistent var (type + value + docs)

Filenames are identity. You can hand-edit any file between sessions; ginny picks up changes on the next run. Drop a new file in by hand — discovered on next search.

Key patterns from the source

AI wiring (packages/ginny/src/ai.ts)

Multi-provider setup with strictSupport wired in so strict-capable model families auto-engage strict mode:

typescript
import { models, strictSupport } from '@aeye/models';

const ai = AI.with<Ctx, Meta>()
  .providers(enabledProviders)
  .create({
    defaultContext: { /* ... */ },
    defaultMetadata: { providers: providersMeta },
    models,
    modelOverrides: [...strictSupport],
  })
  .withHooks({
    onModelSelected: async (ctx, request, selected) => {
      logger.log(`model selected: ${selected.model.id}`);
      return selected;
    },
  });

Per-sub-agent model override (packages/ginny/src/registry.ts)

Each sub-agent reads its model from a dedicated env var (GIN_PROGRAMMER_MODEL, GIN_DESIGNER_MODEL, ...) and passes it through metadata. Sub-agents can run on different tiers:

bash
GIN_PROGRAMMER_MODEL=gpt-5     # heavy lifting
GIN_DBA_MODEL=gpt-4o-mini      # cheap, just file IO
GIN_RESEARCHER_MODEL=...

Strict tool schemas (packages/gin/src/schemas.ts)

The build / test / finish loop schemas use strict: true because malformed gin expressions are unrecoverable — the agent must produce valid ExprDef JSON or fail loudly. With strictSupport wired in, selection only considers strict-capable models for these prompts.

When to look at ginny

  • You're building a sub-agent system. Ginny's recursive spin-up pattern (programmer asks designer asks programmer) is hard to find documented elsewhere.
  • You want a CWD-relative typed catalog. The types / fns / vars-as-files pattern is reusable; ginny's catalog code is small.
  • You're integrating gin into your own tool. Ginny is the reference embedding — everything it does is a thin layer of tool definitions over @aeye/gin.
  • You want to see strict mode + multi-provider in production. The ai.ts file is ~330 lines covering provider probing, retry/hook wiring, payload-size logging, and strict-support integration.

Running from source

bash
git clone https://github.com/ClickerMonkey/aeye.git
cd aeye
npm install
cd packages/ginny
npm run start              # dev (tsx --conditions=source)
npm run build              # bundled dist/index.js with shebang

Released under the GPL-3.0 License.