regent-cli 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- regent_cli-0.1.0/LICENSE +21 -0
- regent_cli-0.1.0/PKG-INFO +52 -0
- regent_cli-0.1.0/README.md +36 -0
- regent_cli-0.1.0/pyproject.toml +30 -0
- regent_cli-0.1.0/setup.cfg +4 -0
- regent_cli-0.1.0/src/regent/__init__.py +3 -0
- regent_cli-0.1.0/src/regent/cli.py +40 -0
- regent_cli-0.1.0/src/regent/doctor.py +47 -0
- regent_cli-0.1.0/src/regent/initcmd.py +135 -0
- regent_cli-0.1.0/src/regent/templates/skills/regent/SKILL.md +60 -0
- regent_cli-0.1.0/src/regent/templates/skills/regent-stop/SKILL.md +27 -0
- regent_cli-0.1.0/src/regent_cli.egg-info/PKG-INFO +52 -0
- regent_cli-0.1.0/src/regent_cli.egg-info/SOURCES.txt +16 -0
- regent_cli-0.1.0/src/regent_cli.egg-info/dependency_links.txt +1 -0
- regent_cli-0.1.0/src/regent_cli.egg-info/entry_points.txt +2 -0
- regent_cli-0.1.0/src/regent_cli.egg-info/top_level.txt +1 -0
- regent_cli-0.1.0/tests/test_doctor.py +52 -0
- regent_cli-0.1.0/tests/test_initcmd.py +72 -0
regent_cli-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Flavio Alvim
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: regent-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Autonomous conduction and mediated adversarial deliberation between AI agents, pluggable into any project.
|
|
5
|
+
Author-email: Flavio Alvim <flavioalvim@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/flavioalvim/regent
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# regent
|
|
18
|
+
|
|
19
|
+
Autonomous conduction and mediated adversarial deliberation between AI agents, pluggable
|
|
20
|
+
into any project.
|
|
21
|
+
|
|
22
|
+
**regent** governs turns between agents (Claude, Codex, human mediators) under a frozen
|
|
23
|
+
protocol: atomic turn mutex, CAS-versioned state, deliberation rounds with versioned
|
|
24
|
+
acceptances, and a conduction daemon that executes production batches with a confined agent,
|
|
25
|
+
test gates and evidence proof.
|
|
26
|
+
|
|
27
|
+
Extracted from the tool proven end-to-end in the ArtNFT project (IMP-003: first product
|
|
28
|
+
batch fully conducted by the daemon, deliberated, accepted and deployed to production).
|
|
29
|
+
|
|
30
|
+
- Requirements: [`docs/PRD.md`](docs/PRD.md)
|
|
31
|
+
- Extraction scope and decisions: [`docs/ESCOPO.md`](docs/ESCOPO.md) (PT-BR, pre-rename)
|
|
32
|
+
- Deliberation rounds: [`docs/brainstorm/`](docs/brainstorm/) (mediator's language, PT-BR)
|
|
33
|
+
- Status: **pre-extraction** (scope closed 2026-07-20; code not yet migrated)
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install <path-to-this-repo> # package: regent-cli; CLI: regent (not on PyPI yet)
|
|
39
|
+
cd <your-project>
|
|
40
|
+
regent init # seeds .regent/ + .claude/skills symlinks (atomic, idempotent)
|
|
41
|
+
regent doctor # checks executor (claude) and advisor (codex) CLIs
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Then open a Claude Code session in the project — `/regent` and `/regent-stop` are available
|
|
45
|
+
(`/regent brainstorm "<question>"` opens the first round). v0 capability is file-driven
|
|
46
|
+
(rounds under `.regent/brainstorm/rodadas/`); the advisor requires the `codex` CLI.
|
|
47
|
+
|
|
48
|
+
Development: `PYTHONPATH=src python3 -m unittest discover -s tests`. Canonical skill content
|
|
49
|
+
lives in `src/regent/templates/` (ships inside the wheel); the repo's own `.regent/skills/`
|
|
50
|
+
symlinks into it (dogfood without duplication).
|
|
51
|
+
|
|
52
|
+
MIT License © 2026 Flavio Alvim.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# regent
|
|
2
|
+
|
|
3
|
+
Autonomous conduction and mediated adversarial deliberation between AI agents, pluggable
|
|
4
|
+
into any project.
|
|
5
|
+
|
|
6
|
+
**regent** governs turns between agents (Claude, Codex, human mediators) under a frozen
|
|
7
|
+
protocol: atomic turn mutex, CAS-versioned state, deliberation rounds with versioned
|
|
8
|
+
acceptances, and a conduction daemon that executes production batches with a confined agent,
|
|
9
|
+
test gates and evidence proof.
|
|
10
|
+
|
|
11
|
+
Extracted from the tool proven end-to-end in the ArtNFT project (IMP-003: first product
|
|
12
|
+
batch fully conducted by the daemon, deliberated, accepted and deployed to production).
|
|
13
|
+
|
|
14
|
+
- Requirements: [`docs/PRD.md`](docs/PRD.md)
|
|
15
|
+
- Extraction scope and decisions: [`docs/ESCOPO.md`](docs/ESCOPO.md) (PT-BR, pre-rename)
|
|
16
|
+
- Deliberation rounds: [`docs/brainstorm/`](docs/brainstorm/) (mediator's language, PT-BR)
|
|
17
|
+
- Status: **pre-extraction** (scope closed 2026-07-20; code not yet migrated)
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install <path-to-this-repo> # package: regent-cli; CLI: regent (not on PyPI yet)
|
|
23
|
+
cd <your-project>
|
|
24
|
+
regent init # seeds .regent/ + .claude/skills symlinks (atomic, idempotent)
|
|
25
|
+
regent doctor # checks executor (claude) and advisor (codex) CLIs
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Then open a Claude Code session in the project — `/regent` and `/regent-stop` are available
|
|
29
|
+
(`/regent brainstorm "<question>"` opens the first round). v0 capability is file-driven
|
|
30
|
+
(rounds under `.regent/brainstorm/rodadas/`); the advisor requires the `codex` CLI.
|
|
31
|
+
|
|
32
|
+
Development: `PYTHONPATH=src python3 -m unittest discover -s tests`. Canonical skill content
|
|
33
|
+
lives in `src/regent/templates/` (ships inside the wheel); the repo's own `.regent/skills/`
|
|
34
|
+
symlinks into it (dogfood without duplication).
|
|
35
|
+
|
|
36
|
+
MIT License © 2026 Flavio Alvim.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "regent-cli"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Autonomous conduction and mediated adversarial deliberation between AI agents, pluggable into any project."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Flavio Alvim", email = "flavioalvim@gmail.com" }]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Repository = "https://github.com/flavioalvim/regent"
|
|
22
|
+
|
|
23
|
+
[project.scripts]
|
|
24
|
+
regent = "regent.cli:entrypoint"
|
|
25
|
+
|
|
26
|
+
[tool.setuptools.packages.find]
|
|
27
|
+
where = ["src"]
|
|
28
|
+
|
|
29
|
+
[tool.setuptools.package-data]
|
|
30
|
+
regent = ["templates/**/*"]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""regent CLI entry point: init and doctor subcommands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from . import __version__
|
|
10
|
+
from .doctor import run_doctor
|
|
11
|
+
from .initcmd import run_init
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main(argv: list[str] | None = None) -> int:
|
|
15
|
+
parser = argparse.ArgumentParser(
|
|
16
|
+
prog="regent",
|
|
17
|
+
description="Autonomous conduction and mediated adversarial deliberation "
|
|
18
|
+
"between AI agents, pluggable into any project.")
|
|
19
|
+
parser.add_argument("--version", action="version", version=f"regent {__version__}")
|
|
20
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
21
|
+
|
|
22
|
+
p_init = sub.add_parser("init", help="seed .regent/ and managed integrations here")
|
|
23
|
+
p_init.add_argument("--project", default=".", help="host project root (default: cwd)")
|
|
24
|
+
|
|
25
|
+
p_doctor = sub.add_parser("doctor", help="diagnose agent CLI capabilities")
|
|
26
|
+
p_doctor.add_argument("--project", default=".", help="host project root (default: cwd)")
|
|
27
|
+
|
|
28
|
+
args = parser.parse_args(argv)
|
|
29
|
+
root = Path(args.project)
|
|
30
|
+
if args.command == "init":
|
|
31
|
+
return run_init(root)
|
|
32
|
+
return run_doctor(root)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def entrypoint() -> None:
|
|
36
|
+
sys.exit(main())
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
if __name__ == "__main__":
|
|
40
|
+
entrypoint()
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""regent doctor — capability diagnostics via safe non-interactive probes.
|
|
2
|
+
|
|
3
|
+
Contract (PRD REQ-003 §6): exit 0 iff every capability is usable; structured
|
|
4
|
+
per-capability report. Probes never open interactive sessions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import shutil
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
EXIT_OK = 0
|
|
15
|
+
EXIT_UNAVAILABLE = 1
|
|
16
|
+
|
|
17
|
+
CAPABILITIES = (
|
|
18
|
+
("executor", "claude", ["claude", "--version"]),
|
|
19
|
+
("advisor", "codex", ["codex", "--version"]),
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _default_probe(argv: list[str]) -> tuple[str, str]:
|
|
24
|
+
"""Returns (status, detail): OK, MISSING or BROKEN."""
|
|
25
|
+
if shutil.which(argv[0]) is None:
|
|
26
|
+
return "MISSING", f"'{argv[0]}' not found on PATH"
|
|
27
|
+
try:
|
|
28
|
+
proc = subprocess.run(argv, capture_output=True, text=True, timeout=30)
|
|
29
|
+
except (subprocess.TimeoutExpired, OSError) as exc:
|
|
30
|
+
return "BROKEN", f"probe failed: {exc}"
|
|
31
|
+
if proc.returncode != 0:
|
|
32
|
+
return "BROKEN", f"exit {proc.returncode}: {proc.stderr.strip()[:200]}"
|
|
33
|
+
return "OK", proc.stdout.strip().splitlines()[0] if proc.stdout.strip() else "ok"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def run_doctor(project_root: Path, out=sys.stdout, probe=_default_probe) -> int:
|
|
37
|
+
all_ok = True
|
|
38
|
+
for role, _cli, argv in CAPABILITIES:
|
|
39
|
+
status, detail = probe(argv)
|
|
40
|
+
all_ok = all_ok and status == "OK"
|
|
41
|
+
print(f"{role:<9} {status:<8} {detail}", file=out)
|
|
42
|
+
|
|
43
|
+
initialized = (project_root / ".regent").is_dir()
|
|
44
|
+
print(f"project {'INITIALIZED' if initialized else 'NOT-INITIALIZED':<8} "
|
|
45
|
+
f"{project_root}", file=out)
|
|
46
|
+
|
|
47
|
+
return EXIT_OK if all_ok else EXIT_UNAVAILABLE
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""regent init — seed .regent/ and managed integrations into a host project.
|
|
2
|
+
|
|
3
|
+
Contract (PRD REQ-003 §6, REQ-004): installation is atomic — exit 0 only on
|
|
4
|
+
complete seeding; any failure rolls back every path this run created, leaving
|
|
5
|
+
no partial state. A missing agent CLI is a warning, never an init failure.
|
|
6
|
+
Pre-existing content that diverges from what would be seeded is a conflict:
|
|
7
|
+
nothing is touched. Re-running over an identical installation is a no-op.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import shutil
|
|
13
|
+
import sys
|
|
14
|
+
from importlib import resources
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
SKILL_NAMES = ("regent", "regent-stop")
|
|
18
|
+
AGENT_CLIS = ("claude", "codex")
|
|
19
|
+
|
|
20
|
+
EXIT_OK = 0
|
|
21
|
+
EXIT_CONFLICT = 2
|
|
22
|
+
EXIT_FAILURE = 1
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SeedConflict(Exception):
|
|
26
|
+
"""Pre-existing host content diverges from what init would seed."""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _template_skill(name: str) -> str:
|
|
30
|
+
ref = resources.files("regent").joinpath(f"templates/skills/{name}/SKILL.md")
|
|
31
|
+
return ref.read_text(encoding="utf-8")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _plan(project_root: Path) -> list[tuple[str, Path, str]]:
|
|
35
|
+
"""Returns [(kind, path, payload)] where kind is 'file' or 'symlink'.
|
|
36
|
+
|
|
37
|
+
payload = file content, or the symlink target (relative)."""
|
|
38
|
+
plan: list[tuple[str, Path, str]] = []
|
|
39
|
+
for name in SKILL_NAMES:
|
|
40
|
+
plan.append(("file", project_root / ".regent" / "skills" / name / "SKILL.md",
|
|
41
|
+
_template_skill(name)))
|
|
42
|
+
plan.append(("symlink", project_root / ".claude" / "skills" / name,
|
|
43
|
+
f"../../.regent/skills/{name}"))
|
|
44
|
+
plan.append(("file", project_root / ".regent" / "brainstorm" / "rodadas" / ".gitkeep", ""))
|
|
45
|
+
return plan
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _existing_state(kind: str, path: Path, payload: str) -> str:
|
|
49
|
+
"""'absent' | 'identical' | 'divergent' for one planned entry."""
|
|
50
|
+
if kind == "symlink":
|
|
51
|
+
if not path.is_symlink():
|
|
52
|
+
return "absent" if not path.exists() else "divergent"
|
|
53
|
+
import os
|
|
54
|
+
return "identical" if os.readlink(path) == payload else "divergent"
|
|
55
|
+
if not path.exists():
|
|
56
|
+
return "absent"
|
|
57
|
+
if path.is_file() and path.read_text(encoding="utf-8") == payload:
|
|
58
|
+
return "identical"
|
|
59
|
+
return "divergent"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def run_init(project_root: Path, out=sys.stdout) -> int:
|
|
63
|
+
project_root = project_root.resolve()
|
|
64
|
+
try:
|
|
65
|
+
plan = _plan(project_root)
|
|
66
|
+
except (FileNotFoundError, ModuleNotFoundError) as exc:
|
|
67
|
+
print(f"error: packaged templates unavailable: {exc}", file=out)
|
|
68
|
+
return EXIT_FAILURE
|
|
69
|
+
|
|
70
|
+
states = {path: _existing_state(kind, path, payload) for kind, path, payload in plan}
|
|
71
|
+
divergent = [p for p, s in states.items() if s == "divergent"]
|
|
72
|
+
if divergent:
|
|
73
|
+
print("error: conflict — pre-existing content differs from what regent would seed:",
|
|
74
|
+
file=out)
|
|
75
|
+
for p in divergent:
|
|
76
|
+
print(f" {p.relative_to(project_root)}", file=out)
|
|
77
|
+
print("nothing was changed. Resolve the conflicts and re-run.", file=out)
|
|
78
|
+
return EXIT_CONFLICT
|
|
79
|
+
|
|
80
|
+
todo = [(kind, path, payload) for kind, path, payload in plan
|
|
81
|
+
if states[path] == "absent"]
|
|
82
|
+
if not todo:
|
|
83
|
+
print("already initialized — nothing to do.", file=out)
|
|
84
|
+
_warn_missing_clis(out)
|
|
85
|
+
return EXIT_OK
|
|
86
|
+
|
|
87
|
+
created: list[Path] = []
|
|
88
|
+
try:
|
|
89
|
+
for kind, path, payload in todo:
|
|
90
|
+
for parent in _missing_parents(path):
|
|
91
|
+
parent.mkdir()
|
|
92
|
+
created.append(parent)
|
|
93
|
+
if kind == "file":
|
|
94
|
+
path.write_text(payload, encoding="utf-8")
|
|
95
|
+
else:
|
|
96
|
+
path.symlink_to(payload)
|
|
97
|
+
created.append(path)
|
|
98
|
+
except OSError as exc:
|
|
99
|
+
_rollback(created)
|
|
100
|
+
print(f"error: seeding failed ({exc}); all changes rolled back.", file=out)
|
|
101
|
+
return EXIT_FAILURE
|
|
102
|
+
|
|
103
|
+
for kind, path, _ in todo:
|
|
104
|
+
print(f"seeded {path.relative_to(project_root)}"
|
|
105
|
+
+ (" (symlink)" if kind == "symlink" else ""), file=out)
|
|
106
|
+
_warn_missing_clis(out)
|
|
107
|
+
print("regent initialized. Open a Claude Code session here and use /regent.", file=out)
|
|
108
|
+
return EXIT_OK
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _missing_parents(path: Path) -> list[Path]:
|
|
112
|
+
missing = []
|
|
113
|
+
for parent in path.parents:
|
|
114
|
+
if parent.exists():
|
|
115
|
+
break
|
|
116
|
+
missing.append(parent)
|
|
117
|
+
return list(reversed(missing))
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _rollback(created: list[Path]) -> None:
|
|
121
|
+
for path in reversed(created):
|
|
122
|
+
try:
|
|
123
|
+
if path.is_symlink() or path.is_file():
|
|
124
|
+
path.unlink()
|
|
125
|
+
elif path.is_dir():
|
|
126
|
+
path.rmdir()
|
|
127
|
+
except OSError:
|
|
128
|
+
pass # best effort; leftover paths are reported by the failure message
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _warn_missing_clis(out) -> None:
|
|
132
|
+
for cli in AGENT_CLIS:
|
|
133
|
+
if shutil.which(cli) is None:
|
|
134
|
+
print(f"warning: agent CLI '{cli}' not found on PATH "
|
|
135
|
+
f"(run 'regent doctor' for capability diagnostics)", file=out)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: regent
|
|
3
|
+
description: Start or resume the regent activity in this repo (brainstorm round, planning, implementation). Single state-driven entry point — detects what is open and drives the next step. Use when the owner says /regent, "retome o brainstorm", "próxima rodada", or asks to continue regent work.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /regent — start or resume the current activity
|
|
7
|
+
|
|
8
|
+
> **Capability level: v0 (file-driven).** State detection is based on round files only.
|
|
9
|
+
> There is no `control.json`, no turn lock, no daemon, no atomic abort and no structured
|
|
10
|
+
> checkpoint yet — those arrive with REQ-004's full implementation. Do not claim
|
|
11
|
+
> capabilities beyond this file.
|
|
12
|
+
|
|
13
|
+
## 0. Locate the rounds directory (deterministic, never guess)
|
|
14
|
+
|
|
15
|
+
- Host projects: `ROUNDS = .regent/brainstorm/rodadas/` (created on first use).
|
|
16
|
+
- The regent product repo itself keeps its own deliberation in `docs/brainstorm/rodadas/`
|
|
17
|
+
(legacy dogfood location).
|
|
18
|
+
- Resolution: if `.regent/brainstorm/` exists use it; else if `docs/brainstorm/` exists use
|
|
19
|
+
that; if BOTH exist, report the ambiguity and STOP; if neither, this is a fresh host —
|
|
20
|
+
`ROUNDS = .regent/brainstorm/rodadas/`.
|
|
21
|
+
|
|
22
|
+
## 1. Detect state (never guess)
|
|
23
|
+
|
|
24
|
+
- Open round = a `ROUNDS/RODADA-NNN/` directory WITHOUT `DECISAO.md`.
|
|
25
|
+
- Suspended round = an open round containing `SUSPENSAO.md`.
|
|
26
|
+
- If the state is ambiguous or corrupted (two open rounds, missing PERGUNTA.md), REPORT the
|
|
27
|
+
anomaly and STOP. Default-deny: never repair silently, never pick one arbitrarily.
|
|
28
|
+
|
|
29
|
+
## 2. Decide by command argument × state
|
|
30
|
+
|
|
31
|
+
- **No argument + exactly one open/suspended round** → resume it (step 3).
|
|
32
|
+
- **No argument + nothing open** → report the state (last closed round, PRD requirement log)
|
|
33
|
+
and ask what to start. Never start anything implicitly.
|
|
34
|
+
- **`brainstorm "<question>"` + nothing open** → create the next `ROUNDS/RODADA-NNN/` with
|
|
35
|
+
`PERGUNTA.md` (owner's question verbatim) and proceed to step 3.
|
|
36
|
+
- **`brainstorm ...` + a DIFFERENT round already open** → error explaining the open state.
|
|
37
|
+
Never ignore the argument silently; never create a second activity.
|
|
38
|
+
- **`plan` / `build`** → not available at v0; say so explicitly and point to REQ-004.
|
|
39
|
+
|
|
40
|
+
## 3. Drive the round (the loop protocol is THIS section — self-contained)
|
|
41
|
+
|
|
42
|
+
Loop: owner asks → Claude responds → Codex (advisor) opines with an explicit
|
|
43
|
+
CONCORDA/DISCORDA verdict → if CONCORDA, Claude executes; if DISCORDA, one rebuttal cycle;
|
|
44
|
+
persistent divergence → the owner arbitrates. Round language: the mediator's (REQ-002).
|
|
45
|
+
|
|
46
|
+
Resume at the first missing artifact, in order:
|
|
47
|
+
1. `RESPOSTA-CLAUDE.md` — Claude's reasoned position + concrete proposal.
|
|
48
|
+
2. `OPINIAO-CODEX-1.md` — headless advisor consultation, saved verbatim:
|
|
49
|
+
`codex --ask-for-approval never --sandbox read-only exec --cd <repo> -o <tmpfile> "<prompt>"`
|
|
50
|
+
(prompt tells Codex which files to read and to end with a line containing only
|
|
51
|
+
CONCORDA or DISCORDA).
|
|
52
|
+
3. If DISCORDA → `REPLICA-CLAUDE.md` then `OPINIAO-CODEX-2.md` (one rebuttal cycle max).
|
|
53
|
+
4. Persistent divergence → the owner arbitrates (they are mediating live).
|
|
54
|
+
5. `DECISAO.md` — what was decided, who closed it, what was executed. Then execute it and
|
|
55
|
+
commit ONLY regent-owned paths (`.regent/`, the `.claude/` symlink integrations, and —
|
|
56
|
+
in the regent repo only — `docs/`) — never unrelated host content. A failed commit does
|
|
57
|
+
not undo the round; report it and leave it pending.
|
|
58
|
+
|
|
59
|
+
If resuming a suspended round, read `SUSPENSAO.md` first, delete it after resuming, and
|
|
60
|
+
continue from the recorded pending step.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: regent-stop
|
|
3
|
+
description: Safely stop/suspend the regent activity in progress (brainstorm round, planning, implementation) so it can be resumed later with /regent. Use when the owner says /regent-stop, "para o brainstorm", "suspende a rodada", or asks to stop regent work.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /regent-stop — safe stop of the current activity
|
|
7
|
+
|
|
8
|
+
> **Capability level: v0 (file-driven).** There is no daemon, lock or in-flight abort yet:
|
|
9
|
+
> stopping means recording a durable suspension marker at the next message boundary. Do not
|
|
10
|
+
> claim atomic cancellation — immediate interruption of a running step is the user's Esc,
|
|
11
|
+
> not this command.
|
|
12
|
+
|
|
13
|
+
## Steps
|
|
14
|
+
|
|
15
|
+
1. Locate the rounds directory exactly as `/regent` does (step 0 of its skill: host =
|
|
16
|
+
`.regent/brainstorm/rodadas/`; the regent repo itself = `docs/brainstorm/rodadas/`; both
|
|
17
|
+
present = ambiguity, stop). Then detect the open round: `RODADA-NNN/` without `DECISAO.md`.
|
|
18
|
+
- Nothing open → report that the state is clean (nothing to stop) and stop.
|
|
19
|
+
- Ambiguous state → report the anomaly; do not modify anything.
|
|
20
|
+
2. Write `SUSPENSAO.md` in the open round with: timestamp, reason given by the owner (ask if
|
|
21
|
+
not stated), which artifact/step was pending (the resume checkpoint), and any partial
|
|
22
|
+
output worth keeping (e.g. an advisor consultation already saved).
|
|
23
|
+
3. Persist everything already produced — never delete partial artifacts; they are evidence.
|
|
24
|
+
4. Commit ONLY regent-owned paths (`.regent/`, the `.claude/` symlink integrations, and —
|
|
25
|
+
in the regent repo only — `docs/`). A failed commit does NOT prevent the suspension —
|
|
26
|
+
report it and leave the commit pending.
|
|
27
|
+
5. Confirm to the owner: what was suspended, where it will resume from (`/regent`).
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: regent-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Autonomous conduction and mediated adversarial deliberation between AI agents, pluggable into any project.
|
|
5
|
+
Author-email: Flavio Alvim <flavioalvim@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/flavioalvim/regent
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# regent
|
|
18
|
+
|
|
19
|
+
Autonomous conduction and mediated adversarial deliberation between AI agents, pluggable
|
|
20
|
+
into any project.
|
|
21
|
+
|
|
22
|
+
**regent** governs turns between agents (Claude, Codex, human mediators) under a frozen
|
|
23
|
+
protocol: atomic turn mutex, CAS-versioned state, deliberation rounds with versioned
|
|
24
|
+
acceptances, and a conduction daemon that executes production batches with a confined agent,
|
|
25
|
+
test gates and evidence proof.
|
|
26
|
+
|
|
27
|
+
Extracted from the tool proven end-to-end in the ArtNFT project (IMP-003: first product
|
|
28
|
+
batch fully conducted by the daemon, deliberated, accepted and deployed to production).
|
|
29
|
+
|
|
30
|
+
- Requirements: [`docs/PRD.md`](docs/PRD.md)
|
|
31
|
+
- Extraction scope and decisions: [`docs/ESCOPO.md`](docs/ESCOPO.md) (PT-BR, pre-rename)
|
|
32
|
+
- Deliberation rounds: [`docs/brainstorm/`](docs/brainstorm/) (mediator's language, PT-BR)
|
|
33
|
+
- Status: **pre-extraction** (scope closed 2026-07-20; code not yet migrated)
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install <path-to-this-repo> # package: regent-cli; CLI: regent (not on PyPI yet)
|
|
39
|
+
cd <your-project>
|
|
40
|
+
regent init # seeds .regent/ + .claude/skills symlinks (atomic, idempotent)
|
|
41
|
+
regent doctor # checks executor (claude) and advisor (codex) CLIs
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Then open a Claude Code session in the project — `/regent` and `/regent-stop` are available
|
|
45
|
+
(`/regent brainstorm "<question>"` opens the first round). v0 capability is file-driven
|
|
46
|
+
(rounds under `.regent/brainstorm/rodadas/`); the advisor requires the `codex` CLI.
|
|
47
|
+
|
|
48
|
+
Development: `PYTHONPATH=src python3 -m unittest discover -s tests`. Canonical skill content
|
|
49
|
+
lives in `src/regent/templates/` (ships inside the wheel); the repo's own `.regent/skills/`
|
|
50
|
+
symlinks into it (dogfood without duplication).
|
|
51
|
+
|
|
52
|
+
MIT License © 2026 Flavio Alvim.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/regent/__init__.py
|
|
5
|
+
src/regent/cli.py
|
|
6
|
+
src/regent/doctor.py
|
|
7
|
+
src/regent/initcmd.py
|
|
8
|
+
src/regent/templates/skills/regent/SKILL.md
|
|
9
|
+
src/regent/templates/skills/regent-stop/SKILL.md
|
|
10
|
+
src/regent_cli.egg-info/PKG-INFO
|
|
11
|
+
src/regent_cli.egg-info/SOURCES.txt
|
|
12
|
+
src/regent_cli.egg-info/dependency_links.txt
|
|
13
|
+
src/regent_cli.egg-info/entry_points.txt
|
|
14
|
+
src/regent_cli.egg-info/top_level.txt
|
|
15
|
+
tests/test_doctor.py
|
|
16
|
+
tests/test_initcmd.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
regent
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Tests for regent doctor: exit codes and per-capability report."""
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
import tempfile
|
|
5
|
+
import unittest
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from regent.doctor import EXIT_OK, EXIT_UNAVAILABLE, run_doctor
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def probe_all_ok(argv):
|
|
12
|
+
return "OK", f"{argv[0]} 1.0.0"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def probe_advisor_missing(argv):
|
|
16
|
+
if argv[0] == "codex":
|
|
17
|
+
return "MISSING", "'codex' not found on PATH"
|
|
18
|
+
return "OK", "claude 1.0.0"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DoctorTest(unittest.TestCase):
|
|
22
|
+
def setUp(self):
|
|
23
|
+
self._tmp = tempfile.TemporaryDirectory()
|
|
24
|
+
self.root = Path(self._tmp.name)
|
|
25
|
+
self.addCleanup(self._tmp.cleanup)
|
|
26
|
+
|
|
27
|
+
def _doctor(self, probe):
|
|
28
|
+
out = io.StringIO()
|
|
29
|
+
code = run_doctor(self.root, out=out, probe=probe)
|
|
30
|
+
return code, out.getvalue()
|
|
31
|
+
|
|
32
|
+
def test_all_capabilities_ok(self):
|
|
33
|
+
code, output = self._doctor(probe_all_ok)
|
|
34
|
+
self.assertEqual(code, EXIT_OK)
|
|
35
|
+
self.assertIn("executor", output)
|
|
36
|
+
self.assertIn("advisor", output)
|
|
37
|
+
self.assertIn("NOT-INITIALIZED", output)
|
|
38
|
+
|
|
39
|
+
def test_missing_capability_fails(self):
|
|
40
|
+
code, output = self._doctor(probe_advisor_missing)
|
|
41
|
+
self.assertEqual(code, EXIT_UNAVAILABLE)
|
|
42
|
+
self.assertIn("MISSING", output)
|
|
43
|
+
|
|
44
|
+
def test_initialized_project_is_reported(self):
|
|
45
|
+
(self.root / ".regent").mkdir()
|
|
46
|
+
code, output = self._doctor(probe_all_ok)
|
|
47
|
+
self.assertEqual(code, EXIT_OK)
|
|
48
|
+
self.assertIn("INITIALIZED", output)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
if __name__ == "__main__":
|
|
52
|
+
unittest.main()
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Tests for regent init: seeding, idempotency, conflict detection, rollback."""
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
import os
|
|
5
|
+
import tempfile
|
|
6
|
+
import unittest
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from regent.initcmd import EXIT_CONFLICT, EXIT_FAILURE, EXIT_OK, run_init
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class InitTest(unittest.TestCase):
|
|
13
|
+
def setUp(self):
|
|
14
|
+
self._tmp = tempfile.TemporaryDirectory()
|
|
15
|
+
self.root = Path(self._tmp.name)
|
|
16
|
+
self.addCleanup(self._tmp.cleanup)
|
|
17
|
+
|
|
18
|
+
def _init(self):
|
|
19
|
+
out = io.StringIO()
|
|
20
|
+
code = run_init(self.root, out=out)
|
|
21
|
+
return code, out.getvalue()
|
|
22
|
+
|
|
23
|
+
def test_fresh_project_seeds_everything(self):
|
|
24
|
+
code, output = self._init()
|
|
25
|
+
self.assertEqual(code, EXIT_OK, output)
|
|
26
|
+
for name in ("regent", "regent-stop"):
|
|
27
|
+
skill = self.root / ".regent" / "skills" / name / "SKILL.md"
|
|
28
|
+
self.assertTrue(skill.is_file())
|
|
29
|
+
self.assertIn(f"name: {name}", skill.read_text())
|
|
30
|
+
link = self.root / ".claude" / "skills" / name
|
|
31
|
+
self.assertTrue(link.is_symlink())
|
|
32
|
+
self.assertEqual(os.readlink(link), f"../../.regent/skills/{name}")
|
|
33
|
+
self.assertTrue((link / "SKILL.md").is_file()) # symlink resolves
|
|
34
|
+
self.assertTrue((self.root / ".regent" / "brainstorm" / "rodadas").is_dir())
|
|
35
|
+
|
|
36
|
+
def test_rerun_is_noop(self):
|
|
37
|
+
self.assertEqual(self._init()[0], EXIT_OK)
|
|
38
|
+
code, output = self._init()
|
|
39
|
+
self.assertEqual(code, EXIT_OK)
|
|
40
|
+
self.assertIn("already initialized", output)
|
|
41
|
+
|
|
42
|
+
def test_divergent_content_is_conflict_and_untouched(self):
|
|
43
|
+
skill = self.root / ".regent" / "skills" / "regent" / "SKILL.md"
|
|
44
|
+
skill.parent.mkdir(parents=True)
|
|
45
|
+
skill.write_text("host content that differs")
|
|
46
|
+
code, output = self._init()
|
|
47
|
+
self.assertEqual(code, EXIT_CONFLICT)
|
|
48
|
+
self.assertIn("conflict", output)
|
|
49
|
+
self.assertEqual(skill.read_text(), "host content that differs")
|
|
50
|
+
self.assertFalse((self.root / ".claude").exists()) # nothing else was created
|
|
51
|
+
|
|
52
|
+
def test_failure_rolls_back_everything(self):
|
|
53
|
+
# A FILE at .claude/skills makes the symlink parent creation fail mid-run.
|
|
54
|
+
(self.root / ".claude").mkdir()
|
|
55
|
+
(self.root / ".claude" / "skills").write_text("not a directory")
|
|
56
|
+
code, output = self._init()
|
|
57
|
+
self.assertEqual(code, EXIT_FAILURE)
|
|
58
|
+
self.assertIn("rolled back", output)
|
|
59
|
+
self.assertFalse((self.root / ".regent").exists())
|
|
60
|
+
|
|
61
|
+
def test_missing_seed_completes_partial_prior_state(self):
|
|
62
|
+
# An identical prior artifact is kept; only the absent ones are seeded.
|
|
63
|
+
self.assertEqual(self._init()[0], EXIT_OK)
|
|
64
|
+
link = self.root / ".claude" / "skills" / "regent"
|
|
65
|
+
link.unlink()
|
|
66
|
+
code, output = self._init()
|
|
67
|
+
self.assertEqual(code, EXIT_OK)
|
|
68
|
+
self.assertTrue(link.is_symlink())
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
if __name__ == "__main__":
|
|
72
|
+
unittest.main()
|