Skip to content
Merged
54 changes: 51 additions & 3 deletions examples/research_demo/agents.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
"""Agent configuration for the Research Demo application.

Multi-agent architecture with three agents:
Multi-agent architecture with four agents:
- research_coordinator: Root agent that owns workflow state (planning, tasks, HITL)
and delegates to the arXiv specialist and data analyst.
and delegates to the arXiv specialist, data analyst, and report writer.
- arxiv_specialist: Leaf agent focused on arXiv paper search, analysis, and ingestion.
- data_analyst: Leaf agent that runs multi-step data analysis in a stateful executor.
- report_writer: Leaf agent that turns analysis artifacts into a compiled LaTeX PDF.

Uses framework-provided tools exclusively — no app-specific tools needed.
"""

from pathlib import Path

from agentic_cli.workflow import AgentConfig
from agentic_cli.tools import (
memory_tools,
Expand All @@ -26,6 +29,7 @@
diff_compare,
grep,
glob,
compile_document,
)
from agentic_cli.tools.sandbox import sandbox_execute

Expand Down Expand Up @@ -125,6 +129,40 @@
"""


# ---------------------------------------------------------------------------
# Report Writer (leaf agent)
# ---------------------------------------------------------------------------

def report_writer_prompt() -> str:
"""Build the prompt from the *active* settings, so the artifacts/build paths
follow whatever workspace is configured (default ~/.research_demo, a tmp dir
under test, or a user override) instead of a hard-coded path. AgentConfig
calls this (get_prompt()) at agent-assembly time, when settings are set."""
from agentic_cli.config import get_settings

settings = get_settings()
artifacts_dir = str(
settings.sandbox_outputs_dir or (Path(settings.workspace_dir) / "artifacts")
)
reports_dir = str(Path(settings.workspace_dir) / "reports")
return f"""You are a report writer. You turn a completed data analysis into a compiled PDF report using LaTeX.

## Where things are
- Analysis deliverables (figures as .png, tables as .csv) are in the artifacts directory: {artifacts_dir}
- Author your LaTeX source in the build directory: {reports_dir}
- Deliver the final PDF to: {artifacts_dir}/report.pdf

## How to work
You have a `report-writer` skill — call `load_skill("report-writer")` for the report structure, the LaTeX template, the compile steps, and the error-recovery guide. In short:
1. `glob("{artifacts_dir}/*.png")` to learn the exact figure filenames.
2. Load the template, fill it in, and `write_file` it to {reports_dir}/report.tex (reference figures by BARE filename).
3. Compile with `compile_document(source_path="{reports_dir}/report.tex", output_pdf="{artifacts_dir}/report.pdf", assets_dir="{artifacts_dir}")`.
4. If it fails, read the returned errors/log_tail, fix the .tex, and recompile (at most 3 tries).

Report the final PDF path to the user. You only use these tools — you do not run arbitrary code.
"""


# ---------------------------------------------------------------------------
# Research Coordinator (root agent)
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -178,6 +216,7 @@
7. **WAIT for user confirmation** before executing tasks.
8. For arXiv paper research, **delegate to arxiv_specialist** (it has KB writer access and writes concept pages when 3+ related papers accumulate).
- For multi-step data analysis (datasets, DataFrames, plots), delegate to **data_analyst**. Use `execute_python` only for quick one-off calculations.
- To produce a written/PDF **report** of a completed analysis, delegate to **report_writer** (it compiles a LaTeX report from the analysis artifacts).
9. Execute ONE task at a time, updating the plan after each.
10. Use `web_fetch` to extract information from specific URLs found during research.
11. Use `execute_python` for quick calculations and data validation.
Expand Down Expand Up @@ -248,6 +287,15 @@
tools=[sandbox_execute, read_file, write_file, ask_clarification],
description="Stateful data-analysis specialist: loads datasets and runs multi-step pandas/plotting analysis in an isolated executor.",
),
# Leaf agent: report writer (must be listed before coordinator)
AgentConfig(
name="report_writer",
prompt=report_writer_prompt,
include_state_tools=False,
tools=[write_file, read_file, glob, compile_document, ask_clarification],
skills=["report-writer"],
description="Report writer: turns the analysis figures/tables in the artifacts dir into a compiled LaTeX PDF report.",
),
# Root agent: research coordinator (owns workflow state, delegates arXiv work)
AgentConfig(
name="research_coordinator",
Expand All @@ -273,7 +321,7 @@
grep,
diff_compare,
],
sub_agents=["arxiv_specialist", "data_analyst"],
sub_agents=["arxiv_specialist", "data_analyst", "report_writer"],
description="Research coordinator with memory, planning, task management, knowledge base, and HITL capabilities",
),
]
4 changes: 4 additions & 0 deletions examples/research_demo/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,7 @@ def model_post_init(self, __context):
object.__setattr__(self, "sandbox_data_mounts", [f"{data_dir}:samples"])
if "sandbox_outputs_dir" not in self.model_fields_set:
object.__setattr__(self, "sandbox_outputs_dir", str(Path(self.workspace_dir) / "artifacts"))
if "skills_dirs" not in self.model_fields_set:
object.__setattr__(
self, "skills_dirs", [str(Path(__file__).parent / "skills")]
)
62 changes: 62 additions & 0 deletions examples/research_demo/skills/report-writer/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
name: report-writer
description: Write and compile a LaTeX analysis report to PDF from figures and tables already produced in the run's artifacts directory. Use when the user wants a written or PDF report of a completed analysis.
---

# report-writer

Produce a compiled PDF analysis report with LaTeX. You author a `.tex` file and
compile it with the `compile_document` tool. You do NOT run arbitrary code.

## Inputs

Analysis deliverables (figures as `.png`, tables as `.csv`) live in the run's
**artifacts directory** — the concrete path is in your instructions. Before
writing, list it with `glob` to learn the exact figure filenames:

glob("<artifacts_dir>/*.png")

Reference figures by **bare filename** (e.g. `accuracy.png`), never full paths —
`compile_document`'s `assets_dir` makes them resolvable.

## Report structure

Write these sections, in order:

1. **Introduction** — the question and why it matters.
2. **Methods** — the data and how it was analyzed.
3. **Results** — the findings, with figures/tables embedded and referenced.
4. **Discussion** — what the results mean; limitations.
5. **References** — a plain list of sources (no citation engine).

## Authoring

1. Load the template: `load_skill_resource("report-writer", "assets/report_template.tex")`.
2. Fill the placeholders (title, author/date, section prose, figure includes, table rows).
3. Escape LaTeX specials in prose: `% & _ # $ { }` → `\% \& \_ \# \$ \{ \}`.
4. `write_file` the filled source to the build directory as `report.tex`.

## Compile

Call the tool:

compile_document(
source_path="<build_dir>/report.tex",
output_pdf="<artifacts_dir>/report.pdf",
assets_dir="<artifacts_dir>",
)

- On `success: true`, tell the user the report is at `pdf_path`. Done.
- On `success: false`, read `errors` and `log_tail`, fix the `.tex`, and recompile.
**Retry at most 3 times**, then report the failure with the error if still failing.

## Common errors → fixes

- `! Undefined control sequence` — a command from a package you did not
`\usepackage`. Add the package or use a base-LaTeX equivalent.
- `! LaTeX Error: File '<name>' not found` — a figure name is wrong; re-`glob`
the artifacts dir and use the exact filename.
- `! Missing $ inserted` / `! You can't use ...` — an unescaped special
character in prose; escape it.
- `No LaTeX engine on PATH` — the host has no TeX install; tell the user to
install TeX Live or MacTeX. You cannot compile without it.
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
\documentclass[11pt]{article}
\usepackage{graphicx}

% === Fill in: title / author / date ===
\title{TITLE_PLACEHOLDER}
\author{AUTHOR_PLACEHOLDER}
\date{DATE_PLACEHOLDER}

\begin{document}
\maketitle

\section{Introduction}
% Fill in: the question and why it matters.
INTRO_PLACEHOLDER

\section{Methods}
% Fill in: the data and how it was analyzed.
METHODS_PLACEHOLDER

\section{Results}
% Fill in: findings. Embed figures by BARE filename, e.g.:
% \begin{figure}[h]
% \centering
% \includegraphics[width=0.8\textwidth]{accuracy.png}
% \caption{Caption text.}
% \end{figure}
%
% Simple table (base LaTeX, no extra packages):
% \begin{table}[h]
% \centering
% \begin{tabular}{l r}
% \hline
% Metric & Value \\
% \hline
% Accuracy & 0.789 \\
% \hline
% \end{tabular}
% \caption{Caption text.}
% \end{table}
RESULTS_PLACEHOLDER

\section{Discussion}
% Fill in: interpretation and limitations.
DISCUSSION_PLACEHOLDER

\section*{References}
% Fill in: a plain list of sources.
\begin{itemize}
\item REFERENCE_PLACEHOLDER
\end{itemize}

\end{document}
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ asyncio_default_fixture_loop_scope = "function"
markers = [
"llm: tests that require real LLM API calls (deselect with -m 'not llm')",
"docker: tests that require a real container runtime (select with -m docker; set SANDBOX_REQUIRE_DOCKER=1 to fail instead of skip when absent)",
"latex: tests that require a host TeX engine (select with -m latex; set LATEX_REQUIRE=1 to fail instead of skip when absent)",
]

[tool.ruff]
Expand Down
2 changes: 2 additions & 0 deletions src/agentic_cli/tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
fetch_arxiv_paper,
)
from agentic_cli.tools.execution_tools import execute_python
from agentic_cli.tools.document import compile_document
from agentic_cli.tools.interaction_tools import ask_clarification

# Long-running job tools (generic observe-only management). Typed long-running
Expand Down Expand Up @@ -126,6 +127,7 @@
"search_arxiv",
"fetch_arxiv_paper",
"execute_python",
"compile_document",
"ask_clarification",
# Long-running jobs (observe-only; typed starters are app-provided)
"job_status",
Expand Down
5 changes: 5 additions & 0 deletions src/agentic_cli/tools/document/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Document-generation tools (LaTeX → PDF)."""

from agentic_cli.tools.document.compile import compile_document

__all__ = ["compile_document"]
Loading
Loading