Skip to content

feat(harness): add host capability seams with inert defaults - #83

Draft
senamakel wants to merge 12 commits into
tinyhumansai:mainfrom
senamakel:agent-to-tinyagents
Draft

feat(harness): add host capability seams with inert defaults#83
senamakel wants to merge 12 commits into
tinyhumansai:mainfrom
senamakel:agent-to-tinyagents

Conversation

@senamakel

Copy link
Copy Markdown
Member

Summary

Adds harness::host — ten host-capability seams with inert default impls, plus the spec
that records why they exist and where their boundary sits.

A generic agent runtime needs a way for an embedding application to supply the capabilities
the runtime itself should not own: long-term recall, turn context assembly, security
decisions, budget admission, agent definitions, experience, learning, progress delivery,
tool-outcome classification, and model resolution. The crate already ships 18 harness
extension traits on exactly this pattern (ChatModel, Tool, ChatHistory, Store,
Summarizer, …); this is ten more of the same kind, not a new architecture.

Nothing in the crate calls these yet. They land empty on purpose, so a host can adopt them
incrementally without the runtime depending on the outcome. OpenHuman is the first consumer.

Spec: docs/spec/host-capabilities-spec.md. Module doc: docs/modules/harness/host.md.

Scope boundaries worth reviewing

These are the decisions that shape the surface — each is argued in the spec:

  • BudgetGate is money and admission only. Context-window fitting is Summarizer +
    RunLimits, not a budget concern.
  • ProgressSink is delivery, not transport. No receive side. An interactive loop that
    reads from a terminal or chat platform stays host surface.
  • SecurityGate::filter_tools is advertisement, not enforcement. Per-call authorization
    is authorize_call, which is the only async method on the trait because it is the one
    site that genuinely awaits.
  • AgentDefinition is deliberately minimal (id, description, system_prompt,
    model, tools, extras: Value) with Default derived. Richer host types map into it
    via extras rather than the crate freezing fields it cannot generalize.
  • Configuration is not a seam. Every method takes &State, so a host parks a reloadable
    handle there and keeps read-at-call-time semantics with zero crate surface — better than a
    virtual getter, and it needs no new type.

Open question for reviewers

All ten are generic over State, matching ChatModel / Tool / Middleware. That may be
the wrong call, and I would like a maintainer's read before hosts adopt them.

Only 7 of the existing 18 extension traits carry State, and all 7 are execution traits.
The 11 that are genuinely capability traits — ChatHistory, Store, Summarizer,
EmbeddingModel, VectorStore, ResponseCache, WorkspaceIsolation, … — are bare
pub trait X: Send + Sync capturing their dependencies in Arc fields, and are the closer
analogue to these ten.

The alternative is: make the eight capability seams non-generic Arc<dyn Trait>, keeping
State only on ModelResolver (which must return ModelRegistry<State>) and
ContextComposer (closest to a middleware). Changing this later is a signature break for
every implementor, so it is cheaper to settle now.

API Or Behavior Changes

Additive only. No existing API changed, no behavior changed. Nothing in the crate
calls the new traits.

New public surface under harness::host, re-exported from the crate root:

Trait Default impl
MemoryProvider InMemoryMemoryProvider
ContextComposer PassthroughContextComposer
SecurityGate RootContainedSecurityGate
BudgetGate UnmeteredBudgetGate
DefinitionRegistry InMemoryDefinitionRegistry
ExperienceStore NoopExperienceStore
LearningSink NoopLearningSink
ProgressSink NoopProgressSink
ToolOutcomeClassifier NoopToolOutcomeClassifier
ModelResolver StaticModelResolver

Version 2.1.02.1.1 (patch). Deliberate: the embedding host pins ^2.1, and a minor
bump would force a host manifest edit this change is scoped to avoid.

Most methods are sync, matching the call sites they are modelled on. Only
SecurityGate::authorize_call, ContextComposer::prepare_turn, and the MemoryProvider /
ExperienceStore / LearningSink I/O methods are async.

Tests

  • cargo fmt --check
  • cargo clippy --all-targets -- -D warnings
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo build --all-targets
  • cargo build --all-targets --all-features
  • cargo test
  • cargo test --all-features

cargo test --all-features: 1330 lib tests, all integration targets, 45 doctests, 0 failures.
37 of those cover the new module's default impls.

Also added tests/host_seam_hygiene.rs, which scans src/harness/host/ for embedder-specific
vocabulary and fails with file, line, and reason. It runs under the normal test command rather
than as a CI lane someone has to remember, and its SCANNED_DIRS list is documented as needing
to grow as more code moves into the crate.

Documentation

  • docs/spec/host-capabilities-spec.md — the catalogue, the rejected alternatives, and the
    publishability boundary. Filed in docs/spec/ per docs/spec/README.md, indexed from its
    Module 1 section.
  • docs/modules/harness/host.md — per-trait scope table, including the three coupling
    concerns resolved by scoping rather than by adding an eleventh trait.
  • Full rustdoc on every trait, method, and public field.

senamakel added 12 commits July 28, 2026 09:11
Add `harness::host`: ten extension traits an embedding application
implements, each paired with a no-op or in-memory default so a host can
adopt them one at a time. Nothing in the crate calls them yet — this
lands the surface, not the wiring.

- MemoryProvider / InMemoryMemoryProvider — scored retrieval over a
  namespaced corpus. Explicitly not ChatHistory: no messages(), no
  append, no offsets, no transcript replay or compaction.
- ContextComposer / PassthroughContextComposer — system prompt (sync,
  once per run for prompt-cache stability) plus per-turn fragments
  (async) with typed placement.
- SecurityGate / RootContainedSecurityGate — tool advertisement,
  per-call authorization (three-valued: allow / deny / require
  approval), lexical path containment, input screening, and redaction.
- BudgetGate / UnmeteredBudgetGate — admission leases, pricing, usage
  ledger, and a graceful per-turn stop verdict.
- DefinitionRegistry / InMemoryDefinitionRegistry over a deliberately
  minimal AgentDefinition; host-specific data rides in opaque `extras`.
- ExperienceStore, LearningSink, ProgressSink, ToolOutcomeClassifier,
  ModelResolver, each with its inert default.

A seam is async only where a realistic implementation performs I/O;
lookups, lexical checks, pricing tables, and classification stay sync so
adopting them does not force callers to become async.

tests/host_seam_hygiene.rs scans the module for embedder vocabulary and
fails with file, line, and reason. Its SCANNED_DIRS list must grow as
runtime code is relocated into the crate.

Design notes in docs/modules/harness/host.md. Patch version bump; the
change is purely additive.
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f65381f1-25f8-4524-9cb8-6c42e11ec6f9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

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