Skip to content

Design: universal harness API (builder pattern) #56

Description

@ikma-ricon

Context

During sessions#50 step 2 review, Or floated the idea of a builder-style API for harnesses:

harness
|> Cli.Harness.Pi.with_model("claude-opus-4-6")
|> Cli.Harness.Pi.with_cwd("/tmp")
|> Cli.Harness.Pi.with_system_prompt("/tmp/prompt.txt")
|> Cli.Harness.Pi.run(message)

vs the current flat-args protocol:

harness.build_command(message, model, system_prompt_file, session, timeout, harness_opts)

The ambition: develop a universal agent harness API — a superset over what pi, claude, and future harnesses each accept, with adapter-specific knobs exposed cleanly (e.g. claude's --dangerously-skip-permissions stays on claude only).

Why deferred

Step 2 kept the flat-args protocol because:

  1. Bash doesn't do builders well. We'd have builder semantics in Python + Elixir but flat args in bash — cross-language drift that the step 2 self-review specifically flagged as a risk.
  2. Designing in the dark. The builder's value is adapter-specific surface, but we only have one adapter today. What pi and claude actually need to differ on is what step 3 will reveal.
  3. Scope. Step 2 already bundles resolver + wake reshape + three-language dispatchers. An API redesign belongs in a focused follow-up.

When to revisit

After step 3 (claude) lands, if any of these are true:

  • Engine ends up branching on harness type to pass different args
  • Adapter-specific opts leak into generic call sites
  • build_command's 6-arg signature grows or splits per adapter
  • Claude has config that doesn't map cleanly onto pi's shape

That's the evidence-based signal. Until then, flat args are adequate and the builder is speculative.

Open questions (to answer with claude in hand)

  • Does the bash layer need builders at all, or can it stay flat while Python/Elixir get richer APIs? (Bash mostly writes JSONL entries; it may not need composition.)
  • Is the right level of abstraction harness.build_commandharness.run(config) with an explicit Config struct, or a pipe-chain of with_* transformers?
  • Can the universal API be inferred from the union of pi + claude's opts, or does it need to be designed top-down?

Not a step 3 blocker

File this under "things to notice while writing claude, not things to build first."

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions