feat(harness): run a company agent on the embedded OpenHuman runtime (#9)#15
Merged
senamakel merged 8 commits intoJul 18, 2026
Merged
Conversation
`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.
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
…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
marked this pull request as ready for review
July 18, 2026 03:15
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
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_rostermaps every manifest[[agent]]to an openhumanAgentviaAgentBuilder, andHarnessPool::run(agent_id, instruction) -> replyis the per-agent entry point. It was never constructed and never executed. Every cycle falls through toEchoBrain, soserveanswers 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 …" endThe turn reaches the pool through a
Brainimpl rather than a branch in the chat handler, so the cycle keeps event persistence, the per-company serial lock, trace persistence andAgentReplyjournaling — and the HTTP surface is unchanged. Thewith_harness/CompanyRuntime::harness()seam stays wired with the sameArc, so WS3 desk routing can still take it later.Commits
fix(build)maintest(harness)feat(harness)HostedProviderspeaks full history + parses usage; inference config from envfeat(harness)last_turn_usage())feat(harness)feat(harness)feat(runtime)HarnessBrain—/company/chatrouted through the agent pool (echo→harness)feat(companies)openhuman_democompany + refreshed harness docs🔴
--features openhumandoes not compile onmainWorth pulling out, since it is independent of the rest of this PR:
vendor/openhumanmoved to4eab04a7, which builds against its own vendored tinyagents19dc2c43(1.9.0). This repo's[patch.crates-io]pointsvendor/tinyagentsatb580f1ab(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 = falsecomment, 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 owntinyagentspath dependency, so its manifest has to resolve for any harness build.The harness had never run
src/harness/mod.rshad no tests. Every tested unit underharness/was a leaf (build,cost,memory,policy,provider); the path that matters —build_roster→AgentBuilder::build→Agent::turn— had never executed once, in any test or any binary.It does now, offline over
MockProvider:No panics, no missing globals, no prompt-file surprises. The embedding works; the rest of this PR is reachability.
zero_usage_turn_writes_nothingpins 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 openhumanbuilds a one-agent company (Tiny Robotics, a CEO), points itsHostedProviderat a hosted inference endpoint, and runs one turn. Against staging/openai/v1:This is the whole path end to end:
build_roster→AgentBuilder→Agent::turn→HostedProvider::chat()(full history, real endpoint) → usage parsed off the wire →record_turn_cost→ aUsageSampleon the meter and aninference.spendledger entry. The agent answers in the first person as its manifest role — the persona system prompt withomit_identityreplaces 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 buildor 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 ofEchoBrain's"You said: …". Thechat_routes_through_the_harness_brainintegration test asserts exactly this over the real Axum router (deterministically, overMockProvider, so it runs in CI). Brain selection precedence is explicit:with_brain(test injection) > harness (attached pool + inference config) > hosted/echo — andservepicks 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 openhumanonly, and the defaultcargo build/CI links none of it.Landed:
harness_inference_from_envreadsOPENCOMPANY_INFERENCE_{URL,KEY,MODEL}, falling back toTINYHUMANS_API_KEY+/openai/v1. No key ⇒None⇒ the runtime keeps its echo brain.HostedProvideroverrideschat()(full history + usage) alongsidechat_with_system.CompanyAgent::runreturns(reply, TurnUsage);TurnUsagedropscall_count. Turn usage is now metered (was inert).HarnessBrainimplements theBrainport;RuntimeBuildergainswith_harness_inference, and brain precedence iswith_brain> harness > hosted/echo.serveboots on the harness brain when an inference credential is present.omit_identity);HarnessDeps.model_overrideapplies a roster-wide model.live_company_turn; new demo companycompanies/openhuman_demo.Tests
cargo fmt --all -- --checkcargo clippy --all-targets -- -D warningscargo build --all-targetscargo 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— cleancargo test --features openhuman— 500 (496 lib + 4 bin)chat_routes_through_the_harness_brain— HTTPPOST /company/chatreturns the agent reply, not echo/openai/v1— real in-character reply + metered usage (transcript above)Documentation
docs/modules/openhuman/README.md— documentsHarnessBrain, the brain precedence, theOPENCOMPANY_INFERENCE_*resolution and the persona seam, and rewrites the cost-metering section now thatlast_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
vendor/tinyagents→19dc2c43here? 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.--features openhumanCI 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.TINYHUMANS_API_KEYagainstapi.tinyhumans.ai/openai/v1with SKUchat-v1the 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, andHostedProvider's OpenAI-compatible shape fits/openai/v1but not/orchestration/v1.