Imported from gzocche/levi (
AGENTS.md). Install upstream withnpx skills add gzocche/levi. Copyright stays with the author.
AGENTS.md
Status
This repository currently contains only .git.
Use this file as the starter engineering contract for agents working here.
No .cursor/rules/, .cursorrules, or .github/copilot-instructions.md files were found.
If any are added later, merge their guidance into this file.
Planning files in this repo:
idea.mdfor the broad product direction and high-level ideas..features/README.mdfor the feature-planning workflow..features/<feature-name>/feature.mdfor concrete feature design and implementation notes..features/<feature-name>/tasks.mdfor an execution checklist when needed..features/<feature-name>/runbook.mdfor a repeatable implementation workflow when needed.
Before substantial implementation work, agents should read idea.md and the relevant .features/ entries.
If a feature includes tasks.md or runbook.md, read those too.
Goal
Build a cross-platform developer tool with:
- A TUI orchestrator for local dev services and scripts.
- Real-time stdout and stderr streaming in the UI.
- Plugin-based extensibility.
- Async execution of multiple tasks.
- Start, stop, and restart support.
- Clear separation between UI, orchestration, and execution.
Preferred stack: Python with uv, pwsh, JSON or YAML.
Architecture
Use Python as the orchestration source of truth and PowerShell as an execution adapter.
Important command-system rule:
- Custom product commands such as
run,status,restart,logs,check, anddoctorbelong in Python. - TUI behaviors such as split views belong in the UI layer, not in PowerShell.
- PowerShell should be used for thin task execution wrappers, not for orchestration, state management, or command routing.
Layers:
ui/: Textual app, widgets, log panes, keybindings, status views.application/: orchestration use cases, scheduling, lifecycle, dependency handling.domain/: task models, states, dependency graph, interfaces, typed errors.infrastructure/: subprocess execution, stream readers, config parsing, plugin loading.plugins/: builtin task providers and external extensions.
Boundary rules:
- UI must not spawn processes directly.
- Domain must not know about Textual or shell syntax.
- Infrastructure implements interfaces defined by domain or application.
- PowerShell scripts stay thin; orchestration logic belongs in Python.
- Prefer structured args and env vars over large inline shell scripts.
Suggested layout:
src/levi/
app.py
cli.py
domain/
application/
infrastructure/
shell/
config/
plugins/
ui/
plugins/builtin/
tests/
unit/
integration/
scripts/
docs/
Planning layout:
idea.md
.features/
README.md
cli-command-system/
feature.md
runtime-supervisor/
feature.md
tasks.md
runbook.md
Build, Lint, Test
The repo is empty today, so these are target default commands agents should preserve when scaffolding.
uv venv
uv sync --all-extras --dev
uv run python -m levi
uv run ruff format .
uv run ruff check .
uv run ruff check . --fix
uv run mypy src
uv run pytest
uv run pytest tests/unit/test_task_runner.py
uv run pytest tests/unit/test_task_runner.py::test_runs_dependencies_before_task
uv run pytest -k dependency
uv run pytest tests/integration
Code Style
Imports:
- Group imports: standard library, third-party, local.
- Separate groups with one blank line.
- Use absolute imports from
levi. - Avoid wildcard imports.
Formatting:
- Use
ruff formatas the source of truth. - Target line length 100 unless config says otherwise.
- Do not hand-format against the formatter.
Types:
- Type all public functions and methods.
- Use
from __future__ import annotationsin new modules. - Prefer
dataclass,Protocol,TypedDict, andLiteralover loose dicts. - Avoid
Anyunless unavoidable.
Project tooling:
- Use
uvfor environment setup, dependency installation, and running project commands. - Keep dependency metadata in
pyproject.toml. - Prefer
uv run <tool>over activating a virtual environment in automation.
Naming:
snake_casefor variables, functions, modules.PascalCasefor classes.UPPER_SNAKE_CASEfor constants.- Prefer explicit names like
task_definition, notdata.
Errors and async:
- Raise typed exceptions for domain and infrastructure failures.
- Never swallow exceptions silently.
- Include task name, command, and exit code in failures when relevant.
- Treat cancellation as distinct from failure.
- Prefer async end-to-end for orchestration and process management.
- Use
asyncio.create_subprocess_execfor long-running tasks. - Stream stdout and stderr incrementally.
- Avoid blocking the Textual event loop.
Config and plugins:
- Load YAML or JSON into typed config models.
- Validate config eagerly at startup.
- Keep config declarative.
- Prefer composition and registration over inheritance-heavy plugin designs.
- New task types should extend the orchestrator through interfaces, not UI branching.
Core Abstractions
TaskRunner: resolve dependencies, schedule tasks, emit lifecycle events.ProcessManager: spawn processes, stream logs, stop processes, return exit status.PluginSystem: discover plugins, validate compatibility, register task providers.ConfigLoader: parse config, validate schema, resolve defaults, return typed models.
Command-related design guidance:
- Expose product commands through a Python CLI or package API.
- Keep command handlers thin; they should delegate to application services.
- Share the same runner and state model between CLI and TUI.
Event flow:
- UI dispatches command to application.
- Application asks the dependency graph for an execution plan.
- Task runner delegates execution to process manager.
- Process manager emits stream events and state changes.
- UI renders events without owning process state.
Spikes
-
Async subprocess streaming. Goal: verify separate stdout and stderr streaming with cancellation. Output: prototype runner plus tests.
-
Textual multi-log rendering. Goal: confirm UI remains responsive with several noisy tasks. Output: benchmark or demo screen.
-
Dependency graph semantics. Goal: validate ordering, restart rules, and failure propagation. Output: domain model plus unit tests.
-
Plugin discovery. Goal: validate simple registration and version checks. Output: minimal plugin loader.
-
pwshportability. Goal: verify command invocation on Windows and Unix-like systems. Output: compatibility notes and wrapper implementation.
Roadmap
- MVP: config loader, async process runner, dependency execution, log streaming, basic TUI.
- V1: restart support, better error UX, plugin API, persistent log history.
- V2: Docker and Azure CLI task types, richer plugin packaging, metrics or tracing.
Risks and mitigations:
- Cross-platform shell differences: isolate shell logic in one adapter.
- UI lag under heavy output: batch UI updates and test with noisy processes.
- Overengineered plugin API: start with builtin plugins and one narrow extension point.
Tradeoffs
pwsh vs bash:
pwshis the better default for Windows-first cross-platform scripting.- Bash is common on Unix-like systems but less uniform on Windows.
Python orchestration vs full PowerShell:
- Python is better for async scheduling, testing, TUI composition, and plugins.
- PowerShell is better as an execution target for existing scripts.
Sync vs async:
- Sync is simpler but weak for concurrent services and live logs.
- Async is the right default here.
Extensibility
- Discover plugins from a known module path or entry points.
- Require a manifest with name, version, and capabilities.
- Register task factories instead of allowing plugins to mutate core state directly.
- Define a common task spec with command, env, cwd, dependencies, and restart policy.
- Implement adapters for
process,docker,az, or custom providers.
Feature-planning guidance:
- Put new major work in
.features/<feature-name>/feature.mdbefore implementation. - Use
idea.mdfor broad ideas, product direction, and future concepts. - Use feature files for scope, design, validation, and open questions.
- Add
tasks.mdwhen the work benefits from a checkbox-style execution list. - Add
runbook.mdwhen the work needs a standard sequence of implementation and verification steps.
Minimal Examples
async def run_command(*cmd: str) -> int:
proc = await asyncio.create_subprocess_exec(*cmd, stdout=PIPE, stderr=PIPE)
...
async def run_task(task, tasks, seen=None):
...
Testing Expectations
- Unit test graph logic and state transitions.
- Integration test subprocess execution and
pwshwrappers. - Add regression tests for bug fixes.
- Cover cancellation, restart, dependency ordering, and stream handling.
Agent Behavior
- Prefer simple, observable implementations over speculative abstractions.
- Keep layers separate.
- Add new abstractions only when two real use cases need them.
- Update this file when tooling or conventions become real in the repo.