Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ maintainers for the latest code.

<p align="center"><strong>Community Group 1 is full. Please join Group 2.</strong></p>

## Tutorials

- **[Getting started](docs/getting-started.md)** — install, first task, three ways in: command line, web UI, desktop app.
- **[Best practices](docs/best-practices.md)** — writing an objective, changing direction mid-run, choosing the model and backend, controlling spend, what suits Argus.
- **[Building a vertical](docs/building-a-vertical.md)** — a small real vertical from stages and skills to the Vertical Store.

## Quick Install

Choose the section for your operating system. Do not mix commands between
Expand Down
1 change: 1 addition & 0 deletions docs/LAYOUT.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ shipped" means no CI job, no test, and no wheel content comes from the directory
- `deploy/` - systemd units and Dockerfiles for the hosted trial (`deploy/trial/`).
- `desktop-tauri/` - the Tauri desktop shell and the PyInstaller spec (`argus_backend.spec`) for the frozen `argus-backend` binary (the spec's `name=`).
- `docs/` - operator and developer documentation; `docs/audits/` holds dated audit reports and their data attachments.
- `examples/` - worked examples the guides refer to: `verticals/` holds the small `lab_notebook` vertical built in `docs/building-a-vertical.md` and the helper that packages a vertical into a local store catalog. Not built, not tested, not shipped.
- `experiments/` - historical PR regression-study forwarding entry points and documentation (`pr_regression_50/`). The maintained implementation and offline tests live in `argus/release_tools/pr_gate/regression/` and `tests/tools/`; model/Docker studies are opt-in, not CI runs. This directory is not built or shipped in the wheel or sdist.
- `frontend/` - `core` (shared TypeScript), `tui` (Ink terminal cockpit), `web` (React web cockpit). `frontend/web/dist` is committed on purpose and force-included into the wheel.
- `integrations/` - the `agent-skills` package for external agent hosts (`SKILL.md` plus per-host adapters). Not the Python package `argus/integrations/`.
Expand Down
328 changes: 328 additions & 0 deletions docs/best-practices.md

Large diffs are not rendered by default.

431 changes: 431 additions & 0 deletions docs/building-a-vertical.md

Large diffs are not rendered by default.

419 changes: 419 additions & 0 deletions docs/getting-started.md

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions examples/verticals/argus_verticals/lab_notebook/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""``lab_notebook``: the worked example from docs/building-a-vertical.md.

A two-stage vertical for small measurement tasks: measure something on this
machine, then write the result up so another person can rerun it.
"""
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
name: "Measurement Record"
description: "How to run a small measurement so the result can be checked and rerun: save raw output, repeat, report the spread, and record the exact command."
---

# Measurement record

Run the measurement from a script saved in the work directory, never from an
interactive shell you cannot show later. Write the raw output to a file under
`results/` before computing any aggregate.

Repeat the run. Report the median (or mean) together with the spread (min/max or
standard deviation) and the number of repeats. A single run is not a result.

Record in `NOTEBOOK.md`:

- what was measured and on which hardware
- the exact command to rerun it
- the aggregate, the spread and the number of repeats
- the path of the raw output every number came from
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
name: "Measurement Review"
description: "How to review a measurement task: open the raw output files, recompute one aggregate, and confirm the rerun command is complete before accepting."
---

# Measurement review

Do not accept the Engineer's summary as evidence. Open the raw output files
named in `NOTEBOOK.md` and recompute at least one aggregate yourself.

Confirm that the rerun command in `NOTEBOOK.md` names the script, its arguments
and the working directory. A command that cannot be pasted and run is
incomplete.

Return `continue` when a number has no raw file behind it, and `done` only when
every checklist item of the current stage has evidence you inspected.
81 changes: 81 additions & 0 deletions examples/verticals/argus_verticals/lab_notebook/stages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"""Stages and checklists of the ``lab_notebook`` example vertical.

Everything Argus needs to know about a vertical is declared at module level in
this file; there is no base class to inherit from. The framework reads the
attributes below through ``argus.core.vertical_contract.vertical_contract`` and
``argus.verticals._registry._validated_plugin``.
"""

from __future__ import annotations

from argus.skills.stage_machine import ChecklistItem

# Required for every vertical that is not built in. Argus refuses any other
# API version, and an empty purpose hides the vertical from the Manager.
ARGUS_VERTICAL_API_VERSION = 1
VERTICAL_PURPOSE = (
"small measurement tasks on this machine: run the measurement, record how "
"it was run, and write a notebook entry another person can reproduce"
)

# The stage order is the tuple order. Every stage that is not optional needs a
# non-empty checklist below.
CHECKLIST_STAGE_ORDER: tuple[str, ...] = ("measure", "report")
CHECKLIST_OPTIONAL_STAGES: tuple[str, ...] = ()
# Names the Manager may use for a stage; they are canonicalized to the real one.
STAGE_ALIASES = {"experiment": "measure", "writeup": "report"}

# "none": the project is complete when the last stage's checklist is met.
# "metric" and "certified" are the other two values the contract accepts.
completion_gate = "none"
WORKFLOW_MODE = "staged" # staged | direct | proportional
MISSION_KIND = "custom" # custom | optimize | research | software
REQUIRE_INDEPENDENT_REVIEW = True

CHECKLIST_ITEMS: dict[str, tuple[ChecklistItem, ...]] = {
"measure": (
ChecklistItem(
id="measure.ran-here",
statement=(
"The measurement was executed on this machine in this project, "
"and its raw output is saved in the work directory."
),
evidence_hint="the command that was run and the path of its raw output",
),
ChecklistItem(
id="measure.repeated",
statement=(
"The measurement was repeated, and the reported number is a "
"median or mean with its spread; a single run is not a result."
),
evidence_hint="number of repeats, the aggregate and the spread",
),
),
"report": (
ChecklistItem(
id="report.notebook-entry",
statement=(
"NOTEBOOK.md in the work directory states what was measured, "
"how, the result with its spread, and the exact command to rerun it."
),
evidence_hint="the NOTEBOOK.md section and the rerun command",
),
ChecklistItem(
id="report.numbers-traceable",
statement=(
"Every number in NOTEBOOK.md can be traced to a saved raw output file."
),
evidence_hint="raw file paths next to each number",
),
),
}


def role_banner(role: str) -> str:
"""One paragraph every role reads before its task; keep it about the field."""
return (
"LAB NOTEBOOK VERTICAL: measure first, then write. A number without a "
"saved raw output and a rerun command is not a result. The Reviewer "
"checks the notebook entry against the raw files, not against the "
"Engineer's summary."
)
97 changes: 97 additions & 0 deletions examples/verticals/build_local_catalog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""Package one vertical directory as a Vertical Store archive plus a local catalog.

Usage:

python examples/verticals/build_local_catalog.py NAME --version 0.1.0 --out DIR

Reads ``examples/verticals/argus_verticals/NAME`` (or ``--source``), writes
``DIR/NAME-VERSION.zip`` with members under ``argus_verticals/NAME/`` (the layout
``argus.verticals.store._verify_tree`` expects), and writes ``DIR/catalog.json``
in the shape ``argus.verticals.store._validate_entry`` accepts. Point
``ARGUS_VERTICAL_CATALOG`` at that file and ``argus verticals install NAME`` uses
the archive next to it instead of downloading anything.

The catalog is minimal on purpose: the fields the store requires, nothing else.
See docs/building-a-vertical.md for the walk-through.
"""

from __future__ import annotations

import argparse
import hashlib
import json
import re
import sys
import zipfile
from pathlib import Path

_NAME = re.compile(r"^[a-z][a-z0-9_]{0,47}$")


def _purpose(stages: Path) -> str:
"""Read VERTICAL_PURPOSE from stages.py without importing it."""
text = stages.read_text(encoding="utf-8")
match = re.search(r"VERTICAL_PURPOSE\s*=\s*\(?\s*((?:\"[^\"]*\"\s*)+)\)?", text)
if match is None:
raise SystemExit(f"{stages}: VERTICAL_PURPOSE not found")
return " ".join("".join(re.findall(r"\"([^\"]*)\"", match.group(1))).split())


def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
parser.add_argument("name", help="vertical name, e.g. lab_notebook")
parser.add_argument("--version", default="0.1.0")
parser.add_argument("--source", type=Path, default=None,
help="vertical directory (default: examples/verticals/argus_verticals/NAME)")
parser.add_argument("--out", type=Path, required=True, help="directory for the zip and catalog.json")
args = parser.parse_args(argv)

name = args.name
if not _NAME.fullmatch(name):
raise SystemExit(f"{name!r} is not a valid vertical name (^[a-z][a-z0-9_]{{0,47}}$)")
source = args.source or (Path(__file__).resolve().parent / "argus_verticals" / name)
stages = source / "stages.py"
if not stages.is_file():
raise SystemExit(f"{source} has no stages.py")

out = args.out.resolve()
out.mkdir(parents=True, exist_ok=True)
archive_name = f"{name}-{args.version}.zip"
archive = out / archive_name
tree = f"argus_verticals/{name}"
with zipfile.ZipFile(archive, "w", compression=zipfile.ZIP_DEFLATED) as zf:
for path in sorted(p for p in source.rglob("*") if p.is_file()):
if "__pycache__" in path.parts:
continue
zf.write(path, f"{tree}/{path.relative_to(source).as_posix()}")

data = archive.read_bytes()
catalog = {
"schema": 1,
"verticals": {
name: {
"name": name,
"version": args.version,
"module": f"argus_verticals.{name}.stages",
"paths": [tree],
"purpose": _purpose(stages),
"has_skills": (source / "skills").is_dir(),
"archive": {
"file": archive_name,
"url": archive.as_uri(),
"sha256": hashlib.sha256(data).hexdigest(),
"size": len(data),
},
}
},
}
catalog_path = out / "catalog.json"
catalog_path.write_text(json.dumps(catalog, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
print(f"archive : {archive} ({len(data)} bytes)")
print(f"catalog : {catalog_path}")
print(f"install : ARGUS_VERTICAL_CATALOG={catalog_path} argus verticals install {name}")
return 0


if __name__ == "__main__":
sys.exit(main())
Loading