Skip to content

feat: agent simulations - #141

Open
TEJASNARAYANS wants to merge 25 commits into
mainfrom
EFF-74
Open

TEJASNARAYANS wants to merge 25 commits into
mainfrom
EFF-74

Conversation

@TEJASNARAYANS

@TEJASNARAYANS TEJASNARAYANS commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

What Changed?

  • Agent simulations (Harbor): Simulation runs API, Harbor worker queue, task export, trial scoring, trajectories, and UI to configure runs (scenarios, personas, metrics, success criteria).
  • Pre/post-prod chat eval: LLM-to-LLM simulation loop with production chat leg (internal LLM, customer API, provider chat), tool loop, and KB tools on test/production sides.
  • Knowledge bases: CRUD, ingest/embed/search, agent links, voice search_knowledge_base tool, download/view fixes, serialized ingest with advisory locks.
  • Connected tools: Tool connections (MCP/HTTP/Postgres), agent toolsets, scenario expected-tool mocks, simulation catalog.
  • Metrics: Scenario/agent scope, auto metric generation from scenarios, Harbor rubric export, workspace-scoped metric selection, draft link persistence.
  • Ops: Dedicated efficientai-worker-harbor image (Python 3.12 + harbor extra), workspace capability checks on simulation/KB/tool routes.

Why?

Teams need to batch-evaluate agents against scenarios and personas with the same metrics and gates used in production, without manual call scripting. Harbor runs trials at scale; the platform owns export, scoring, and artifacts.

KB + tools let simulations and voice agents use grounded answers and mocked or live integrations. Workspace scoping and metric filters prevent cross-workspace leaks (scenarios, personas, private metrics, rubrics).

How to Test?

  1. Simulations: Create a chat agent → link scenarios/personas/metrics → start a simulation run → confirm trials complete and scores/trajectories download.
  2. Harbor worker: docker compose build worker-harbor && docker compose up worker-harbor (or deployed efficientai-worker-harbor image) → run queues on harbor-sim.
  3. KB: Upload a doc → wait for ready → chat/voice search returns chunks; download original in a non-default workspace (link includes workspace_id).
  4. Tools: Attach a tool connection to an agent; pre-prod sim uses mocks when mock_response is set; live tools when not mocked.
  5. Metrics: Studio draft with selected agents/scenarios → promote → links preserved; delete a metric mid-run → scoring skips missing IDs (no crash).
  6. CI: make test-parallel (or targeted harbor/KB/tool/metrics modules).

Release Label

minor — backward-compatible features (simulations, KB, tools, metric generation); no intentional breaking API changes.

Checklist

  • I have read the CONTRIBUTING.md guide.
  • My code follows the project's style guidelines.
  • I have added tests that prove my fix is effective or my feature works.
  • I have updated documentation where needed. (docker-compose harbor image note; extend README/k8s if you ship harbor worker there.)

@TEJASNARAYANS
TEJASNARAYANS marked this pull request as ready for review October 22, 2026 16:27
@greptile-apps

greptile-apps Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 2/5

[High impact] The PR adds several workspace-wide agent workflows that run in both chat and voice paths.

The PR still needs fixes for analysis jobs overwriting newer results, ignored credential choices, and skipped failure rules before merging.

Findings

  1. P1 Cancelled jobs overwrite new results ▶
  2. P1 Clustering uses the wrong credential ▶
  3. P1 Some failed rules get ignored ▶
  4. P2 PDFs include unchecked sections ▶

Summary

Adds Harbor simulation runs, knowledge bases, connected tools, and agent/scenario metric selection.

  • A user can configure a simulation run and inspect its scores, conversations, failures, and exported reports.
  • Teams can upload documents and link searchable knowledge bases to their agents.
  • Agents can use connected tools during chat and simulation runs.
  • Metrics can be linked to agents and scenarios, and drafted from scenarios.

Reviews (22) · Last reviewed commit: "fix: updating simulations stuff" · Reviewed by Greptile

Comment thread app/api/v1/routes/simulation_runs.py Resolved
Comment thread app/api/v1/routes/simulation_runs.py Resolved
Comment thread app/services/tools/toolset.py Resolved
Comment thread app/api/v1/routes/knowledge_bases.py Resolved
Comment thread docker-compose.yml Resolved
Comment thread app/services/tools/loop.py Resolved
… export functionality with workspace_id filtering
Comment thread app/api/v1/routes/knowledge_bases.py Resolved
Comment thread frontend/src/lib/api.ts Resolved
Comment thread app/services/harbor/trial_store.py Resolved
Comment thread app/services/knowledge_bases/ingest.py Resolved
@MSami625

Copy link
Copy Markdown
Collaborator

@greptile-apps

Comment thread docker-compose.yml Resolved
Comment thread app/api/v1/routes/metrics.py Resolved
Comment thread app/services/metrics/option_labels.py Resolved
… config coercion and improving option label normalization
Comment thread app/services/metrics/option_labels.py Resolved
Comment on lines +52 to +55
def _reload_cancelled() -> bool:
db.expire(run, ["metric_clusters"])
db.refresh(run)
return metric_clusters_raw_is_cancelled(run.metric_clusters)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Cancelled jobs overwrite new results

Cancelling and immediately regenerating an analysis can let the old job continue. _reload_cancelled checks only metric_clusters.status, which regeneration changes back to "running". If the old job is waiting for an LLM reply, it misses the cancellation and runs alongside its replacement. Both jobs can then overwrite the same results, while charging for both sets of calls.

Give each analysis a unique generation ID and reject writes from older jobs.

Comment on lines +68 to +75
state = generate_simulation_metric_clusters(
db,
run,
provider=provider_enum,
model=model_str,
on_progress=on_progress,
max_llm_calls=max_llm_calls,
is_cancelled=_reload_cancelled,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Clustering uses the wrong credential

The selected credential_id never reaches the clustering calls. The worker uses it to choose the provider and model, but passes only those values to generate_simulation_metric_clusters. The shared LLM helper then chooses the default credential instead.

If an organization has two credentials for the same provider, clustering uses the wrong key or gateway. It can fail when the model is available only through the selected credential. Pass the credential through every clustering call, as the prompt-suggestion step already does.

Comment on lines +276 to +282
source_rows=source_rows,
metrics=metrics,
policies={},
on_progress=on_progress,
max_llm_calls=max_llm_calls,
is_cancelled=is_cancelled,
failure_predicate=simulation_failure_predicate(run, metrics_by_id),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Some failed rules get ignored

Simulation success rules can use user_insight metrics, but this call sends them through a helper that always filters them out. A run that fails only those rules gets no failure analysis or prompt suggestions. It is instead told that no completed trial missed a rule.

Let the simulation path cluster every metric that has a success rule, rather than applying the call-import quality-only filter.

Comment on lines +337 to +349
clusters = payload.get("metric_clusters")
if isinstance(clusters, dict) and clusters.get("status") == "completed":
lines.extend(["", "04 Failure Diagnostics"])
for group in clusters.get("groups") or []:
if not isinstance(group, dict):
continue
lines.append(f"{group.get('metric_name')}: {group.get('flagged_count')} flagged")
for cluster in group.get("clusters") or []:
if isinstance(cluster, dict):
lines.append(f" - {cluster.get('label')} ({cluster.get('count')})")
improvements = payload.get("prompt_improvements")
if isinstance(improvements, dict) and improvements.get("status") == "completed":
lines.extend(["", "05 Prompt Improvement Recommendations"])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 PDFs include unchecked sections

If WeasyPrint is unavailable or fails, _simulation_plain_text_lines ignores the selected sections. The downloaded PDF includes the summary, completed failure diagnostics, and prompt suggestions even when the user unchecked them.

Apply the same section checks used by the HTML report so users get the limited report they requested.

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.

2 participants