Skip to content

feat(harness): run a company agent on the embedded OpenHuman runtime (#9)#15

Merged
senamakel merged 8 commits into
tinyhumansai:mainfrom
oxoxDev:feat/9-openhuman-harness-agent
Jul 18, 2026
Merged

feat(harness): run a company agent on the embedded OpenHuman runtime (#9)#15
senamakel merged 8 commits into
tinyhumansai:mainfrom
oxoxDev:feat/9-openhuman-harness-agent

Conversation

@oxoxDev

@oxoxDev oxoxDev commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Draft — feature-complete, held for review. All eight commits have landed: /company/chat answers with a real company agent on the hosted brain, in-character and metered (transcript below), and the first commit fixes a build that is broken on main right now. Kept in draft pending the three questions at the bottom (the endpoint-pricing one in particular gates the hosted default).

Closes #9.

opencompany has an agent, which then sits on the openhuman runtime — this PR wires that, one turn end to end.

src/harness/ is already a complete OpenHuman embedding: build_roster maps every manifest [[agent]] to an openhuman Agent via AgentBuilder, and HarnessPool::run(agent_id, instruction) -> reply is the per-agent entry point. It was never constructed and never executed. Every cycle falls through to EchoBrain, so serve answers chat with "You said: …" even under --features openhuman.

What lands here

sequenceDiagram
    autonumber
    actor Owner
    participant HTTP as POST /company/chat
    participant Cycle as CycleRunner
    participant Brain as Brain seam
    participant Echo as EchoBrain
    participant Pool as HarnessPool
    participant OH as openhuman Agent

    Owner->>HTTP: {"text": "Who are you?"}
    HTTP->>Cycle: run_cycle(OperatorMessage)
    Cycle->>Brain: run_cycle

    rect rgb(255, 235, 235)
        Note over Brain,Echo: today — the only wired path
        Brain->>Echo: run_cycle
        Echo-->>Owner: "You said: Who are you?"
    end

    rect rgb(232, 245, 233)
        Note over Brain,OH: this PR — HarnessBrain
        Brain->>Pool: run(agent_id, text)
        Pool->>OH: Agent::turn(text)
        OH-->>Pool: reply + usage
        Pool-->>Owner: "I'm the Legal Researcher at …"
    end
Loading

The turn reaches the pool through a Brain impl rather than a branch in the chat handler, so the cycle keeps event persistence, the per-company serial lock, trace persistence and AgentReply journaling — and the HTTP surface is unchanged. The with_harness / CompanyRuntime::harness() seam stays wired with the same Arc, so WS3 desk routing can still take it later.

Commits

fix(build) bump vendored tinyagents to openhuman's pin — fixes a broken build on main
test(harness) execute the roster build and an agent turn — the embedding provably works
feat(harness) HostedProvider speaks full history + parses usage; inference config from env
feat(harness) meter real turn usage into the ledger (via public last_turn_usage())
feat(harness) live company-turn example against the hosted brain
feat(harness) persona system prompt + roster-wide model override (agent answers in-character)
feat(runtime) HarnessBrain/company/chat routed through the agent pool (echo→harness)
feat(companies) openhuman_demo company + refreshed harness docs

🔴 --features openhuman does not compile on main

Worth pulling out, since it is independent of the rest of this PR:

error[E0599]: no function `with_responses_api_primary` on `OpenAiModel`
error[E0560]: struct `HarnessToolCall` has no field named `invalid`
...
error: could not compile `openhuman` (lib) due to 36 previous errors

vendor/openhuman moved to 4eab04a7, which builds against its own vendored tinyagents 19dc2c43 (1.9.0). This repo's [patch.crates-io] points vendor/tinyagents at b580f1ab (1.8.0), so openhuman compiles against an API that predates it. The two submodules are a lockstep pair.

CI only builds default features, so nothing caught it — and nothing will catch the next one.

Also corrected the default-features = false comment, which claimed it "drops only the treesitter code compressor". At the new pin openhuman's default set is eight features (tokenjuice-treesitter, voice, web3, media, meet, skills, flows, mcp) and shedding all of them is deliberate — a company agent wants the agent loop, memory and a provider, none of the rest.

And CI's submodule init now covers vendor/openhuman/vendor/tinycortex/vendor/tinyagents; tinycortex declares its own tinyagents path dependency, so its manifest has to resolve for any harness build.

The harness had never run

src/harness/mod.rs had no tests. Every tested unit under harness/ was a leaf (build, cost, memory, policy, provider); the path that matters — build_rosterAgentBuilder::buildAgent::turn — had never executed once, in any test or any binary.

It does now, offline over MockProvider:

test harness::tests::roster_builds_every_manifest_agent ... ok
test harness::tests::run_executes_a_turn_on_the_openhuman_runtime ... ok
test harness::tests::ensure_is_idempotent ... ok
test harness::tests::turns_are_serialised_and_history_survives ... ok
test harness::tests::unknown_agent_is_invalid_request ... ok
test harness::tests::unknown_company_is_not_found ... ok
test harness::tests::zero_usage_turn_writes_nothing ... ok

test result: ok. 7 passed; 0 failed

No panics, no missing globals, no prompt-file surprises. The embedding works; the rest of this PR is reachability.

zero_usage_turn_writes_nothing pins the documented inert-metering contract on purpose, so turning usage on has to flip it deliberately rather than silently.

🟢 A company agent, live on the hosted brain

cargo run --example live_company_turn --features openhuman builds a one-agent company (Tiny Robotics, a CEO), points its HostedProvider at a hosted inference endpoint, and runs one turn. Against staging /openai/v1:

── prompt → ceo ──
Who are you, and what does Tiny Robotics do? Answer in one sentence.

── ceo reply ──
I'm the CEO of Tiny Robotics, where we design and manufacture miniature
robotic systems for industrial inspection, medical procedures, and research
applications.

── metered usage ──
provider=managed  in=945  out=27  cached=0  cost_usd=0.000181818

This is the whole path end to end: build_rosterAgentBuilderAgent::turnHostedProvider::chat() (full history, real endpoint) → usage parsed off the wire → record_turn_cost → a UsageSample on the meter and an inference.spend ledger entry. The agent answers in the first person as its manifest role — the persona system prompt with omit_identity replaces openhuman's own assistant identity (it dropped the input tokens from 2,343 to 945). Metering is live, not inert.

The example reads its credential from the environment and, with none set, prints how to supply one and exits non-zero — so it is safe to run anywhere and is never built by the default cargo build or CI.

🟢 …and over HTTP

The same path is reachable through the product surface: with the harness brain wired, POST /api/v1/company/chat {"text": "…"} runs one agent turn and returns the reply instead of EchoBrain's "You said: …". The chat_routes_through_the_harness_brain integration test asserts exactly this over the real Axum router (deterministically, over MockProvider, so it runs in CI). Brain selection precedence is explicit: with_brain (test injection) > harness (attached pool + inference config) > hosted/echo — and serve picks the harness brain automatically when an inference credential is in the environment.

API Or Behavior Changes

The default build is untouched throughout — everything below is --features openhuman only, and the default cargo build/CI links none of it.

Landed:

  • harness_inference_from_env reads OPENCOMPANY_INFERENCE_{URL,KEY,MODEL}, falling back to TINYHUMANS_API_KEY + /openai/v1. No key ⇒ None ⇒ the runtime keeps its echo brain.
  • HostedProvider overrides chat() (full history + usage) alongside chat_with_system.
  • CompanyAgent::run returns (reply, TurnUsage); TurnUsage drops call_count. Turn usage is now metered (was inert).
  • HarnessBrain implements the Brain port; RuntimeBuilder gains with_harness_inference, and brain precedence is with_brain > harness > hosted/echo. serve boots on the harness brain when an inference credential is present.
  • Each agent gets a persona system prompt (omit_identity); HarnessDeps.model_override applies a roster-wide model.
  • New gated example target live_company_turn; new demo company companies/openhuman_demo.

Tests

  • cargo fmt --all -- --check
  • cargo clippy --all-targets -- -D warnings
  • cargo build --all-targets
  • cargo test — 460 (456 lib + 4 bin)

Feature legs (not run by CI):

  • cargo check --features openhuman — clean (36 errors before the tinyagents bump)
  • cargo check --features tiny — clean
  • cargo test --features openhuman — 500 (496 lib + 4 bin)
  • chat_routes_through_the_harness_brain — HTTP POST /company/chat returns the agent reply, not echo
  • live turn against staging /openai/v1 — real in-character reply + metered usage (transcript above)

⚠️ cargo clippy --all-targets --features openhuman -- -D warnings does not pass, and cannot today. Vendored tinyagents 1.9.0 trips two large_enum_variant lints, and a [patch.crates-io] path dependency does not get cap-lints, so -D warnings denies them. No lint points at opencompany code. It only became reachable now that the feature compiles at all — flagging it because it would block a --features openhuman CI leg.

Documentation

  • docs/modules/openhuman/README.md — documents HarnessBrain, the brain precedence, the OPENCOMPANY_INFERENCE_* resolution and the persona seam, and rewrites the cost-metering section now that last_turn_usage() makes it live.
  • docs/spec/runtime/config.md — the three new inference env vars.
  • companies/openhuman_demo/README.md — how to run the demo on the harness brain (server + curl, and the example).

Questions

  1. OK to bump vendor/tinyagents19dc2c43 here? Without it the feature does not build. Happy to split it into its own PR if you'd rather land it immediately — it is independent of the harness work.
  2. Want a cached --features openhuman CI job? Green default CI does not prove this leg, which is exactly how the lockstep break landed. Would need the vendored-lint question above resolved first.
  3. Is TINYHUMANS_API_KEY against api.tinyhumans.ai/openai/v1 with SKU chat-v1 the right hosted default for company agents — or should they go through /orchestration/v1? Worth being deliberate: the two surfaces price the same tokens ~6× apart, and HostedProvider's OpenAI-compatible shape fits /openai/v1 but not /orchestration/v1.

oxoxDev added 5 commits July 17, 2026 17:29
`cargo check --features openhuman` fails on main with 36 errors inside
vendored openhuman (`OpenAiModel::with_responses_api_primary` missing,
`HarnessToolCall` has no field `invalid`, ...).

`vendor/openhuman` moved to 4eab04a7, which builds against its own vendored
tinyagents 19dc2c43 (1.9.0). This repo's `[patch.crates-io]` points
`vendor/tinyagents` at b580f1ab (1.8.0), so openhuman compiles against an
API that predates it. The two submodules are a lockstep pair.

CI only builds default features, so nothing caught the skew.

Also correct the `default-features = false` comment: openhuman's default set
grew to eight features at the new pin, and shedding all of them is
deliberate, not just the treesitter compressor. And init tinycortex's own
`vendor/tinyagents` in CI — its manifest must resolve for a harness build.

Verified: cargo check --features openhuman (clean), --features tiny (clean),
cargo test (460 passed), cargo test --features openhuman (476 passed).
…ai#9)

`src/harness/mod.rs` had no tests: every tested unit under `harness/` was a
leaf (build, cost, memory, policy, provider), and the path that matters —
`build_roster` -> `AgentBuilder::build` -> `Agent::turn` — had never run.

These execute it offline over `MockProvider` and in-memory ports, so the
embedding is proven before anything is wired to it. Covers: the roster maps
every manifest `[[agent]]`, a turn actually runs on the openhuman runtime and
returns its reply, `ensure` is idempotent, a second turn reuses the pooled
agent, and both error mappings.

`zero_usage_turn_writes_nothing` pins the documented inert-metering contract,
so turning usage on has to flip this test deliberately.

`build_roster` becomes `pub(crate)` so the test can build a roster without
going through the pool.

Verified: cargo test --features openhuman harness::tests (7 passed),
cargo test --features openhuman (487: 483 lib + 4 bin),
cargo test (460: 456 lib + 4 bin),
cargo clippy --all-targets -- -D warnings (clean — what CI runs),
cargo fmt --all -- --check.

Note: `cargo clippy --all-targets --features openhuman -- -D warnings` does
not pass, and cannot today — vendored tinyagents 1.9.0 trips two
`large_enum_variant` lints, and a `[patch.crates-io]` path dependency does not
get `cap-lints`, so `-D warnings` denies them. No lint points at opencompany
code. This only became reachable now that the feature compiles at all; it is
worth knowing before any `--features openhuman` CI leg lands.
…onfig (tinyhumansai#9)

Override Provider::chat() so the path Agent::turn actually calls sends the
full conversation (the trait default collapses it to system + last-user) and
parses the response's token/cost usage — standard OpenAI usage plus the
openhuman.{usage,billing} envelope. Add harness_inference_from_env resolving
OPENCOMPANY_INFERENCE_{URL,KEY,MODEL}, falling back to TINYHUMANS_API_KEY and
the /openai/v1 default; no key yields None so the runtime keeps its echo brain.

Verified under --features openhuman: cargo test harness::provider 9/9; the
parse tests use the exact /openai/v1 staging payload shape.
CompanyAgent::run now reads the completed turn's token/cost totals from
openhuman's public last_turn_usage() accessor (landed with the tinyagents
bump) and HarnessPool::run records them — replacing the inert zero-usage
placeholder. Drop the vestigial TurnUsage.call_count; a turn is zero-usage
when it moved no tokens and cost nothing (the offline MockProvider), so test
turns stay inert while a live turn writes a usage sample. Gate the ledger
entry on cost, not tokens: the /openai/v1 passthrough reports tokens but bills
backend-side, so a token-bearing zero-cost turn meters usage without posting a
meaningless $0.00 spend line.

Verified: cargo test --features openhuman harness 31/31, incl. the preserved
zero_usage_turn_writes_nothing contract.
…nyhumansai#9)

An explicit example (gated on the openhuman feature, never built by default or
CI) that builds one company agent on the embedded OpenHuman runtime and runs a
single turn against a hosted inference endpoint, printing the reply and the
turn's metered usage. Reads its credential from the environment via
harness_inference_from_env, so with no key it prints how to supply one and
exits non-zero — safe to invoke anywhere.

Ran against staging /openai/v1: a real agent turn (2343 in / 29 out,
cost $0.00044) returned a coherent reply, exercising build_roster ->
AgentBuilder -> Agent::turn -> HostedProvider.chat() -> usage metering end to
end.
@coderabbitai

coderabbitai Bot commented Jul 17, 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: 80327ac2-21ee-4f09-9dea-f3bf1290a223

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.

oxoxDev added 3 commits July 17, 2026 22:40
…nyhumansai#9)

Each agent now gets a persona system prompt framing it as its manifest role at
the company, built via SystemPromptBuilder::for_subagent with omit_identity so
it speaks as that role instead of falling back to openhuman's own assistant
identity. Add HarnessDeps.model_override, applied to every agent over the
per-agent tier->model default, set from the resolved hosted-inference model.

Verified live on staging: the CEO of a demo company now answers "I'm the CEO
of Tiny Robotics..." (was "I'm OpenHuman"), and omit_identity cut the turn's
input tokens 2343 -> 945. cargo test --features openhuman harness 36/36.
…in (tinyhumansai#9)

HarnessBrain implements the Brain cognition port over a HarnessPool: each
operator message runs a real openhuman agent turn and returns the agent's
reply (metered via HarnessPool::run), instead of EchoBrain's "You said: …".

Brain selection precedence is now explicit: with_brain (test injection) >
harness (with_harness + with_harness_inference) > hosted/echo. The bin's
attach_harness resolves the inference credential from the environment and, when
present, wires the pool + hosted config so serve boots on the harness brain;
with no credential it keeps the hosted/echo path. All openhuman-gated — the
default build is byte-identical.

Verified: cargo test --features openhuman 496/496, incl. a new HTTP
integration test (chat_routes_through_the_harness_brain) asserting POST
/company/chat returns the agent reply, not the echo brain's.
…umansai#9)

A minimal three-agent company (CEO / Engineer / Writer) that runs on the
embedded OpenHuman harness — the smallest end-to-end demo that an OpenCompany
agent sits on the OpenHuman runtime. Validated with 'opencompany check'.

Refresh the OpenHuman module doc: document HarnessBrain and the with_brain >
harness > hosted/echo precedence, the OPENCOMPANY_INFERENCE_* env resolution,
the persona/omit_identity seam, and rewrite the cost-metering section now that
last_turn_usage() (openhuman#4940) makes it live. Add the three inference env
vars to docs/spec/runtime/config.md.
@senamakel
senamakel marked this pull request as ready for review July 18, 2026 03:15
@senamakel
senamakel merged commit d3020c3 into tinyhumansai:main Jul 18, 2026
2 checks passed
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.

Run a company agent on the embedded OpenHuman runtime (harness brain)

2 participants