abb7fa021650b6cbb96babbd8c7b0ae3939dcc66
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
abb7fa0216 |
Add the operator kill switch, stop (A4)
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 49s
`stop` could not have worked by adding the command alone. Planning turns, milestones and review responses ran synchronously on the thread that delivered the message, so the Matrix sync loop read nothing until the work finished. The state store was also opened on the main thread and used from the transport and poller threads, which SQLite refuses with its default same-thread check; the in-memory test transport never showed it. A single worker thread now runs every handler and owns the store: - The worker opens the store, so the same-thread check stays on and fails loudly if anything touches the store from elsewhere - Matrix messages are handed to the worker; pollers hand over and wait, so their cursor never passes an unhandled comment - A message that arrives during long work gets an immediate "busy; say 'stop' to halt" reply and is recorded, not applied later in a state the operator was not answering - Tests keep running inline through InlineWorker, which defers a job submitted from inside another as the real worker would queue it `stop` sets a shared Cancel flag straight from the transport's thread. The executor and delegation loops check it before each model call and between tool calls; the planner checks it before each turn. Calls left unrun get a result saying so, so the transcript still renders. A stopped delegation is discarded like a failed one. The orchestrator moves to AWAIT_INPUT and asks why the work was stopped. The reply is recorded for the model, and a stopped planning turn resumes with it as the answer. `stop` with nothing running says so and changes nothing; at a gate it is no longer taken as rejection feedback. Closes #40 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
e38adf40ca |
Discover a model's vendor from LiteLLM instead of its name
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 47s
Route selection sniffed model names: anything starting "claude-" or "anthropic/" took the vendor-native passthrough, everything else took the normalized route. That works only while an alias names its vendor. A semantic role alias like "remote-architecture" is meant to point at Anthropic today and somewhere else tomorrow, and the name says nothing either way -- so it would have taken the normalized route regardless of what was behind it, silently losing the fidelity A13 wants for delegations. LiteLLM already knows the answer. /model/info reports each alias's backing model, provider, and declared tool support, so the agent asks at startup and routes on the provider it discovers. That keeps vendor knowledge where A5 already put it -- in the proxy -- rather than duplicating it as name patterns here. There is deliberately no name-based fallback: PASSTHROUGH_PREFIXES and has_native_adapter are gone, and select_client now requires a catalog. A second heuristic could only ever disagree with the proxy. An unknown model raises instead of guessing a route. Misconfiguration is therefore loud and early. build_agent validates every role against the catalog before it clones anything, naming all the bad ones at once. Discovery is per key -- /model/info returns only what the presented token may use -- so the same check catches a role pointed at a real model this key cannot reach. An unreachable proxy is reported separately, since an outage and a typo call for different responses from the operator. Two things now get recorded rather than inferred: the resolved model is pinned alongside the role mapping at task start, for the reason A11 already pins roles, and DelegationStarted carries backing_model so A16 eval history is not averaged across whatever an alias happened to point at. A model LiteLLM declares tool-less warns at startup rather than refusing; A15 expects weak local models to be accommodated, and they are observed driving tool loops anyway. Verified live: all four aliases the production key can see route correctly through discovery, and each chosen route works. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
2fb25b8195 |
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> |