Add the composition root and a runnable entry point
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m14s

Nothing had ever assembled the agent. main.py was still the hello-world
placeholder from the scaffold, and no code outside the test suite
constructed an Orchestrator — which is how two fully built, fully
tested components came to be reachable from nothing: the delegation
harness, and the Gitea comment poller. Unit tests that construct a
component directly cannot catch that; only assembly can.

- config.py validates every variable before anything is built and
  names all of them at once, rather than failing at first use twenty
  minutes into a milestone on the call that mattered. Whitespace-only
  counts as missing, since a token of spaces passes a truthiness check
  and fails at the first request instead
- agent.py builds the whole graph and owns startup and shutdown order:
  reconcile before accepting commands, and on stop, stop watching,
  then stop talking, then close the store last
- The Gitea account is read via whoami rather than configured: it names
  the identity every permission is evaluated against, and proves the
  token works before anything depends on it
- Poller lifecycle is now a state-machine concern: watch the plan issue
  while a gate stands, the PR while it is open, and stop on leaving.
  The orchestrator owns when to watch; an injected factory owns how to
  build one
- The executor reports each turn, because it runs synchronously inside
  the transport callback: without it the agent is silent for a whole
  milestone and a working run looks exactly like a hung one
- main.py loads .env, builds, and shuts down cleanly on SIGINT/SIGTERM

The entry-point tests changed shape: nisse-dev no longer greets, it
refuses to start unconfigured and says everything that is missing. Both
tests now run with a deliberately empty environment and a .env path
that does not exist, because the working directory's .env holds live
credentials and a test must never be one cwd away from starting a real
agent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
dongarandClaude Opus 5 committed 2026-09-08 22:26:26 +00:00
1 parent 6331fcafd9
commit 2fb25b8195
11 files changed
+694 -15

No files matched your search

+40 -5
View File
@@ -1,11 +1,46 @@
"""Placeholder entry point for the dev agent."""
"""Entry point for the dev agent.
from nisse_core.greeting import greeting
Loads configuration, builds the agent, and stays up until told to stop.
Shutdown is deliberate rather than abrupt: A2's durability guarantees assume
the process may die at any moment, but a clean stop still beats relying on
them.
"""
import os
import signal
import sys
from pathlib import Path
from types import FrameType
from nisse_dev.agent import build_agent
from nisse_dev.config import AgentConfig, ConfigError
from nisse_dev.envfile import load_env_file
def main() -> None:
print(greeting("nisse-dev"))
def main() -> int:
load_env_file(Path(os.environ.get("NISSE_ENV", ".env")).expanduser())
try:
config = AgentConfig.from_env()
except ConfigError as exc:
# Fail here rather than twenty minutes into a milestone on the one
# call that needed the missing value.
print(f"nisse-dev: {exc}", file=sys.stderr)
return 2
agent = build_agent(config)
def shutdown(_signum: int, _frame: FrameType | None) -> None:
print("nisse-dev: stopping", file=sys.stderr)
agent.stop()
signal.signal(signal.SIGINT, shutdown)
signal.signal(signal.SIGTERM, shutdown)
agent.start()
print(f"nisse-dev: ready as {agent.account} on {config.project}", file=sys.stderr)
agent.wait()
return 0
if __name__ == "__main__":
main()
raise SystemExit(main())