opencoti-llamafile / docs /features /package_courier_harness.md
ManniX-ITA's picture
bench: defaults β€” parallel 8, tps-floor 15 scheduler-driven, stall timeout auto-scales with steps (bug-2222)
4e9dd50 verified
|
Raw
History Blame Contribute Delete
6.12 kB
# Package-courier harness β€” concurrency test + agentic performance benchmark
*Spec agreed 2026-07-16 (user design + AskUserQuestion decisions). Tasks
#739 (work server) / #740 (LangGraph app) / #741 (launcher + reporting).
Sibling of the #712 PolyKV control-plane API; consumes `opencoti-langgraph`.*
## Purpose
One harness that is simultaneously:
1. a **concurrency test** for the PolyKV control-plane (pools, fork,
admission, capacity, tps) under real multi-session load;
2. a **live tool-calling exercise** of the engine's native
`/v1/chat/completions` tool-call path (the coverage gap from the C4
audit), multi-turn;
3. a repeatable **agentic performance benchmark** with a single scalar
score (dollars) plus timing/throughput stats for graphs;
4. the **reference LangGraph implementation** for the
`opencoti_langgraph` integration package (updated 2026-07-16,
bug-2220): each worker episode is a compiled `langgraph.StateGraph`
β€” `START β†’ capacity_gate β†’ agent ⇄ tools β†’ END` with a bounded
`hold` backoff loop β€” where `capacity_gate` is the packaged
`make_capacity_gate` node, `agent` is one `OpencotiChatModel` call,
and routing is `Command`-based over `add_messages` state. Offline
routing tests: `bench/package_courier/test_agent_graph.py`. Live
gates 2026-07-16 (Qwen3-4B, 3090): pooled 8/8 delivered 100%
(cache_n>0 on 64/64 turns, scheduler 2β†’5 workers), naive 6/6 100%.
## The workload
A Python **work server** (runs in the `opencoti` conda env, part of the
harness) owns the ground truth:
- At start it **procedurally generates** `P` packages (default **100**),
each with a chain of `n` dependent steps (default **10**). Every chain
is unique in tool sequence and content mix. Steps are built from a
vocabulary of fake attribute tools (`haircolor`, `cartype`, `petname`,
…): step *k*'s instruction names the tool to call and embeds the
answer of step *kβˆ’1* as input. Content is credible-but-nonsense β€”
plausible enough that a model executes it without wondering why.
- Tools exposed to the agents (as LangChain tools hitting the work
server's REST API; the model emits **native OpenAI tool_calls** via
`bind_tools`/ToolNode):
- `get_package()` β†’ `package_id` + step-1 instructions (acquisition
is the queue pull);
- `<attribute>(package_id, n_step)` β†’ the step's answer; the server
**records every call and verifies** it is the right tool with the
right args for that package/step;
- `next_step(package_id, n_step, result)` β†’ verifies the result
(it is the input of the next step) and returns step *k+1*;
- `deliver_package(package_id, final_answer)` β†’ verified against the
chain's final answer; correct delivery pays **$1**;
- a completion signal endpoint the framework calls when all agents
are done.
- **Timeout**: server ends the run if no package is delivered for
15 min (default) and no completion signal arrived.
## The agent side (opencoti-langgraph app)
- Root **pool** = system prompt + tool instructions (chat-templated
block). Workers attach with `pool_id` + per-worker `session_id`.
- **Naive mode** (`--no-polykv`): identical workload on plain sessions;
worker count fixed at `K == --parallel`, FIFO package pull. This is
the only shape naive supports β€” it is the A/B baseline.
- **PolyKV mode** (default): additionally supports a **dynamic worker
target driven by `--tps-floor`** β€” the framework must oblige the
floor: spawn additional workers only while
`GET /capacity` admits (`can_admit`, `expected_tokens` arm) and
`mean_active_tps` stays β‰₯ floor; back off when the floor is
threatened. Enforced admission (`429 + Retry-After` β†’
`PoolSaturatedError`) is honored as the hard backstop.
- The framework handles package acquisition, chain execution
(each step = a new turn with a tool call), retries/prioritization
(oldest-acquired first), monitors deliveries, and signals completion.
## Launcher & flags
The launcher boots all three processes: the opencoti-llamafile server
(model, `-c`, `--parallel`, `--polykv-max-pools`, KV types, MTP flags β€”
all pass-through), the work server, and the agent app.
Defaults (updated 2026-07-16, user decision + bug-2222): 100 packages Γ—
10 steps; engine `--parallel 8`; pooled worker fleet driven by
`--tps-floor 15` (fixed worker counts are only for the naive A/B leg,
where the floor defaults to 0); work-server stall timeout auto-scales
`max(900 s, steps Γ— 60 s)` β€” the stall timer resets only on delivery,
so long chains (e.g. 55 steps) need the scaled window; model =
**Qwen3.6-27B-Omnimerge-v4 (bs2)** β€” hybrid GDN + NextN MTP, i.e. the
serving target and the hardest pool path; everything overridable.
## Scoring & report
- Score: delivered-correct Γ— $1 (100 packages β†’ $100 = 100%).
- Report: packages delivered / not delivered / **lost in transit** /
failed-verification (and **at which step**), total wall time, delivery
time mean/percentiles, per-step verification log.
- Engine stats: the harness polls `/polykv/tps` (per-session tps + ctx
budget), `/polykv/pools/*/capacity` (headroom, `compaction_pressure`),
`/slots`, `/metrics` on an interval β†’ timeseries for graphs; the
framework also reports its own API stats at the end.
- **Speed curves (the HF question β€” how does agent speed behave as turns
and context grow):** every model turn is sampled with its chain step
`n`, prompt size, `cache_n`, generation tps and prompt tps; the report
aggregates **tps-vs-step** (mean tps at chain step 1..n across all
packages) and **tps-vs-context** (512-token buckets). Pooled vs naive
runs give the same curves side by side β€” i.e. whether/where per-turn
speed decays with accumulated context, and what the pool buys back.
- Every run doubles as a **pooled-vs-naive A/B benchmark** (same seed,
same packages) β€” memory, aggregate tps, deliveries/minute.
## Non-goals (v1)
- No compaction exercise (chains are short; CompactionNode has its own
gate) β€” candidate for v2 with long chains.
- No multi-model routing; one engine per run.