Skip to content

apple: native @Generable metadata schema for §B enrichment#7

Open
jakejimenez wants to merge 1 commit into
feat/init-llm-enrichmentfrom
feat/apple-metadata-schema
Open

apple: native @Generable metadata schema for §B enrichment#7
jakejimenez wants to merge 1 commit into
feat/init-llm-enrichmentfrom
feat/apple-metadata-schema

Conversation

@jakejimenez
Copy link
Copy Markdown
Owner

Summary

Stacked on #6. PR #6 introduced §B metadata enrichment but had to skip Apple Intelligence — nlci-apple's Swift bridge was hardcoded to a single @Generable CommandResult schema, so it couldn't return the structured YAML §B asks for. On the project's recommended setup (Apple as primary backend), nlci init quietly fell back to hardcoded scaffold values. This PR closes that gap so Apple gets §B natively, without needing an Ollama detour.

Architecture

A new mode field in BridgeInput selects which @Generable schema the model is constrained to. New ToolMetadata / SafetyRules / SynonymEntry structs in Swift map 1:1 onto the existing EnrichmentMetadata type in Go.

On the Go side, a new optional capability interface backend.MetadataGenerator is type-asserted at the call site (idiomatic Go capability detection — cf. io.WriterTo). AppleBackend implements it; other backends don't, so they fall through to PR #6's existing YAML-prompt path. No change to the core Backend interface.

```
runMetadataEnrichment
↓ Resolve active backend
├── implements MetadataGenerator? → GenerateMetadata (native @generable)
└── otherwise → enrichInitMetadata (YAML prompt)
```

Backward compatibility

--ping now returns a capabilities: ["command", "metadata"] array. AppleBackend.Ping caches it; HasCapability("metadata") gates the new method. Old Swift binaries don't emit the array, so the gate is closed and an actionable error directs the user to rebuild:

apple: nlci-apple binary does not advertise metadata mode — run `make build-apple && make install-apple` to upgrade

In the other direction, old Go binaries don't set mode, and the new Swift bridge's decodeIfPresent defaults it to "command" — behavior unchanged.

Verified end-to-end

`nlci init jq` on Apple Intelligence produces:

  • description: `Process JSON data using \`jq\` for transformations and manipulations` (was: `jq CLI`)
  • system_prompt: tool-specific guidance referencing real jq flags (was: generic boilerplate)
  • safety: populated from the model's reading of the help text (was: empty array)
  • synonyms: 8+ entries tied to actual jq flags like `compact: -c`, `raw: -R` (was: heuristic-only)

Total init time: ~11s for jq end-to-end. Apple Intelligence handles the structured generation natively in one round-trip.

Files

  • `apple/Sources/NLCIApple/MetadataResult.swift` (new) — `@Generable` schemas
  • `apple/Sources/NLCIApple/{App,BridgeInput,BridgeOutput}.swift` — mode switch + capabilities
  • `internal/backend/metadata.go` (new) — `MetadataGenerator` interface
  • `internal/backend/apple.go` — `Ping` caches capabilities; `GenerateMetadata` method
  • `cmd/nlci/main.go::runMetadataEnrichment` — type-assert and prefer native; fall through to YAML
  • `cmd/nlci/main_test.go` — `convertNativeMetadata` table tests

Test plan

  • `go test ./...` — all green
  • `go vet ./...` — clean
  • `swift build -c release` in `apple/` — clean
  • `nlci-apple --ping` returns the new `capabilities` array
  • `nlci init jq` on Apple produces a real description, system_prompt, safety, and synonyms

Known follow-ups

The model occasionally over-populates `safety.require_confirmation` for non-destructive flags (e.g. it added jq's output-format flags like `-c`, `-R` to the confirmation list). This is a prompt-tuning issue, not architectural — the YAML can be edited by the user, and the prompt's `@Guide(description:)` strings can be tightened in a follow-up to be more explicit about what counts as destructive.

🤖 Generated with Claude Code

PR #6 added §B metadata enrichment but had to skip Apple Intelligence
because nlci-apple's Swift bridge constrained output to a single
CommandResult struct. On the project's recommended setup (Apple as
primary backend), §B silently degraded to hardcoded scaffold values.
This change extends the bridge so Apple gets the full §B benefit
natively, without needing Ollama or a YAML round-trip.

Swift side (apple/Sources/NLCIApple/):
- MetadataResult.swift adds three @generable structs: ToolMetadata
  (description, system_prompt, safety, synonyms), SafetyRules, and
  SynonymEntry (a keyword + targets pair, since Dictionary isn't
  auto-Generable).
- BridgeInput.swift adds a `mode` field (default "command" preserves
  pre-existing behavior for old Go callers).
- BridgeOutput.swift adds `metadata` (populated in metadata mode) and
  `capabilities` (populated by --ping so Go can detect stale binaries).
- App.swift switches on input.mode to pick CommandResult vs
  ToolMetadata when calling session.respond(generating:).

Go side:
- internal/backend/metadata.go introduces an optional capability
  interface MetadataGenerator + types MetadataRequest, MetadataResult,
  Safety. Backends opt in by implementing it; others fall through to
  PR #6's textual YAML prompt path. Idiomatic Go capability detection
  (cf. io.WriterTo).
- internal/backend/apple.go: Ping now parses --ping output for the
  capabilities array; HasCapability gates the new method;
  GenerateMetadata sends mode="metadata", parses BridgeOutput.metadata,
  and flattens the array-of-pairs synonyms into a map[string][]string.
- cmd/nlci/main.go: runMetadataEnrichment type-asserts the active
  backend for backend.MetadataGenerator and prefers native generation;
  on error or no-implementation it falls through to enrichInitMetadata.
  convertNativeMetadata maps the backend type into the cmd-local
  EnrichmentMetadata, applying the same placeholder-leak guard as the
  YAML parser.

Verified end-to-end with `nlci init jq` on Apple Intelligence:
- description: "Process JSON data using `jq` for transformations…"
  (was: "jq CLI")
- system_prompt: tool-specific guidance referencing real jq flags
  (was: generic "You are an expert jq user")
- safety + synonyms populated from the model's reading of the help
  text (was: empty arrays + heuristic-only synonyms)

Backward compatibility:
- Old Swift binary + new Go: HasCapability("metadata") is false,
  GenerateMetadata returns an actionable rebuild error, init falls
  through to YAML path (which also fails on Apple), scaffold gets
  hardcoded values. User sees a clear "make build-apple && make
  install-apple" hint.
- New Swift binary + old Go: BridgeInput.mode defaults to "command"
  via decodeIfPresent, behavior unchanged.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant