Status: complete. There is now one model stack — ModelSpec/ModelConfig
→ build_model/connect → Model → completions/. The legacy Stack-A
plumbing (models/client.py, dispatch.py, envelope.py, the whole
adapters/ package, and the Guarded class) has been deleted. The sections
below are kept as the record of how the collapse was carried out.
Delivered in: the CLI/ADK/Web native-backend commits, the AdapterModel
deletion, then connect() → Model (phase 1) and the Stack-A teardown
(phase 2). The two pre-step checks (tracking callbacks are logging-only;
ModelResponse satisfies CompletionModel) both held.
The seam that lets them coexist is the structural CompletionModel protocol
(core/contracts/protocols.py): both a ModelClient and a Model satisfy it,
so ModelJudge/ModelGuardrail accept either.
End state = Stack B only.
hackagent/client.py(liveTarget): L422–425 imports; L459check_supported; L492connect→build_model; L494Guarded→GuardedModel; L505models.for_role.models/factory.py:ModelFactory.for_roleL176connect→build_model; keepwith_credentials/spec_from_config.evaluation/panel.py:ModelJudge.llm: CompletionModel— unchanged (protocol).orchestrator/planning/planner.py: L21/88get_litellm→ new home.models/build.py: addagent_type → native backendregistry in_adapter_model's place; absorb_normalise_ollama_endpoint/resolve_litellm_model.- Campaign bridge (
legacy.py): already reuses prebuilt models; onceTarget.targetis aModel, the reuse becomes type-honest (verify seam).
models/
model.py Model ABC
response.py ModelResponse (drop the _from_mapping envelope branch)
config.py ModelConfig / Connection / Generation
provider_config.py LiteLLM provider table
build.py build_model (registry) + build_embedder + as_completion
connect.py connect(spec)->Model, for_role, supported-type check <- replaces client.py
guardrail.py GuardedModel + ModelGuardrail <- Guarded removed
retry.py embeddings.py target_params.py
completions/
litellm.py LiteLLMModel (+ ollama normalise, resolve_litellm_model)
cli.py SubprocessCLIModel base + Claude Code / Codex / Hermes
adk.py web.py browser.py
Gone: dispatch.py, client.py, envelope.py, completions/adapter.py, the
whole adapters/ package.
Check 1 — tracking callbacks: safe to drop. litellm_callbacks.py's
HackAgentTrackingLogger only emits Python log records (prompt/response
previews, cost, duration); it writes nothing to the store. Persisted run
tracking is the campaign's RunTracker/tracking/ package, independent of it.
It is Stack-A-only (the native LiteLLMModel neither registers nor injects it)
and dies in the teardown. Optional: re-emit cost/latency log lines from
LiteLLMModel later.
Check 2 — CompletionResult conformance: already conforms. ModelResponse
has .text, .ok, .error, so Model.complete satisfies CompletionModel.
Panel calls only .complete/.acomplete → fine. One small gap:
ModelGuardrail.describe() does getattr(self.llm, "describe")(), which a
native Model lacks — give Model an optional describe() or drop the call.
(Neither protocol is @runtime_checkable, so no isinstance change is needed.)
- Native backends — one
Modelper adapter type incompletions/,acomplete → ModelResponse. - Repoint Stack B —
build_modelregistry → native backends; deleteAdapterModel(only once every type has a native backend). - Repoint Stack A —
connectreturns aModel; moveclient.py,for_role, guardrail (Guarded→GuardedModel). - Teardown — delete
dispatch,client.py,envelope(minus relocated helpers),adapters/; relocate helpers; trimModelResponse._from_mapping.
The adapter types are not independent leaves:
- CLI family —
claude.py,codex.py,hermes.pyall subclasscli_agent.SubprocessCLIAgent, which registers a per-instance LiteLLM custom provider and callslitellm.completion. They areLiteLLMModel+ a provider-registration step; migrate them as one unit (completions/cli.py). - ADK (
adk.py, 521 lines) — standalone, ADK-session based; a real custom backend. - Web (
web.py, 883 lines; usesbrowser.py) — standalone, heaviest.
Consequence: a single "migrate Hermes and delete AdapterModel" commit is not
possible — AdapterModel can only go once ADK and Web also have native
backends. Pick slicing per family:
- Horizontal (map default): all native backends first (Stack B), then Stack A, then delete. Safe/additive; deletions only at the end.
- Vertical (per family, both stacks): migrate one family across A and B and
delete its old adapter immediately. More visible cleanup per commit; touches
the riskier Stack-A
connect/dispatchsurface sooner.
