Commit Graph
23 Commits
Author SHA1 Message Date
dongarandClaude Opus 5.5 eafe1b9f4a The agent host's vault and SilverBullet (M5.4)
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m50s
CI / e2e (pull_request) Successful in 4m45s
deploy/agent-host sets up the vault on nisse-host (infra/tofu#77) and
SilverBullet over it: the operator writes the vault, the agents' memory/
is read-only to it, and only the operator's Caddy reaches it, which
serves vault.dongar.net to the LAN. scripts/agent-host.sh installs and
checks it from here.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-18 20:55:21 +00:00
dongarandClaude Opus 5.5 fe9c7c19a1 CI runs nisse-review's live test
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 2m25s
CI / e2e (pull_request) Successful in 12m10s
scripts/e2e.sh ran only test_end_to_end_loop.py, and the integration step
excludes e2e-marked tests, so test_review_live.py (M7.3) never ran in CI.
The script now runs every e2e-marked test under tests/integration, and the
review test is a smoke test: about a minute on the local model, and the only
check that the review account, its token and its read-only refusal work.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-16 20:25:54 +00:00
dongarandClaude Opus 5.5 f59d974a95 M7.3: nisse-review's first slice
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m49s
CI / e2e (pull_request) Successful in 3m18s
A new package over nisse-core: it reviews every ready PR in the
repositories its account can read, and posts one COMMENT review.

- scheduler: each round, open PRs per readable repository (or the
  configured repositories); an item per PR, ready unless draft or WIP;
  closed with the PR. One review at a time, oldest first.
- reader: the repository at head and base from Gitea archives, unpacked
  and read in Python (read_file, list_files, search_code), and its issues;
  bound to the item's repository; no git, no shell.
- review: starts from the PR, commits, diff, comments, closed issues, CI;
  comment drafts a point on any existing line; post_review posts one
  COMMENT review at the head, inline where Gitea takes it, else as links in
  the summary. ask_expert; vault and memory (memory/nisse-review).
- L8: posting is an intent; recovery asks Gitea for the marked review and
  never posts twice; with Gitea down, the item waits.
- Failures retried, then said on the PR until the next push.
- Startup refuses an account that can push to or administer any repo.
- nisse-core gitea: readable_repositories, open_pull_requests, diff,
  commits, archive, issue, search_issues, reviews, post_review; PR fields.
- Bring-up: scripts/review-bringup.sh; nisse-review.example.yaml.
- Tests: unit over a fake Gitea and scripted model; a live test
  (NISSE_REVIEW_GITEA_TOKEN) reviews a PR in a fresh test repository.
- Docs: Development Plan M7.3, Tuning Guide (tuning.review.*).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-13 22:34:02 +00:00
dongarandClaude Opus 5.5 f32ccf9881 M7.2: share the loop's parts
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 2m21s
CI / e2e (pull_request) Successful in 11m45s
Shared parts, not a shared scheduler (agreed): nisse-review will have its
own small scheduler; a common one waits until two exist to compare.

- nisse_core.loop.driver: the driver run, moved; the agent names its
  waiting tools per run (nisse-dev's are WAITING_TOOLS), and execution
  goes to any Executor (ToolOutcome moves with it).
- nisse_core.loop.tools: the tool helper and the tools any agent offers
  (notes, memory, memory_done, ask_expert); nisse_core.loop.expert.
- nisse_core.knowledge.handlers: Knowledge, the vault and memory as
  control tools, the index and prompt text, out of LoopAgent.
- nisse_core.config: file reading, references, people, tuning sections of
  the agent's own, LLM numbers, the vault.
- New core tests: a driver run with a non-workspace executor and the
  agent's own waiting set; Knowledge alone; the shared config pieces.

Docs: the Development Plan marks M7.2 done; Architecture Part L and the
nisse-core README name the new parts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-13 21:31:29 +00:00
dongarandClaude Opus 5.5 e351d0a74e nisse-core: group modules into packages by architecture section
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m37s
CI / e2e (pull_request) Successful in 4m5s
A pure move; behaviour unchanged.

- models (L9, L7): llm, catalog, probe, render, render_anthropic, context
- log (L7, L6): canonical, views
- state (L8): store (was state), reconcile, worker, cancel, dump
- knowledge (L10, L6.4): vault, memory
- conversation (L5): transport, matrix
- execution (L13): ssh (was execution), testing
- authority (P3, P4, L4): actions
- gitea, oplog, envfile stay at the top until each gains a second module.

Every import, monkeypatch target, script and doc reference rewritten.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-13 21:16:09 +00:00
dongarandClaude Opus 5.5 154a0c3fa8 M7.1: extract nisse-core
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m42s
CI / e2e (pull_request) Successful in 4m0s
What isn't the dev agent's moves to nisse-core, unchanged in behaviour,
with its tests: canonical, render, render_anthropic, llm, catalog, probe,
context, views, state, reconcile, worker, cancel, dump, transport, matrix,
execution, testing, vault, memory, oplog, envfile and gitea (whole).

- nisse_core.state.StateStore holds the tables every agent has; an
  agent's subclass adds its tables (version, migrations), roles and
  views. nisse_dev.state.StateStore adds the build VM blocks (v5).
- views.project takes what each role is shown, rather than a fixed table.
- Vault takes the agent's name (agent=), for provenance.
- A test that nisse_core never imports an agent.
- The loop, config, git, delegation, expert, tools and evals stay in
  nisse-dev until nisse-ops has a loop to share.

Docs: the Development Plan marks M7.1 done and notes that Phase 6 waits on
the agent host and Terraform; Architecture Part L names the package.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-13 18:37:01 +00:00
dongarandClaude Opus 5.5 089af0c391 M3.2: the action classifier
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 2m20s
CI / e2e (pull_request) Successful in 12m31s
What the fast path calls unknown is judged by an independent model call
on the item's classifier role (L4), before it runs.

- nisse_dev.loop.classifier: it sees the action and the agreed scope
  (project, repository, issue number, the restatement the operator said
  go to), built in code; never the transcript, the issue's text, tool
  output or the driver's reasoning. Its verdict is a tool call: safe
  runs; in_scope runs and is reported in the thread; needs_discussion
  asks, as a dangerous action does. An error, no verdict or an unknown
  one fails closed. Verdicts are cached per item, agreed revision and
  action. A helper is refused what needs discussion.
- action_verdict gains source, model and the classifier's exact input;
  details shows the source. An allow after the classifier asked is an
  override. scripts/classifier-evals.py exports the judgements from a
  store, read-only; tests/integration/classifier_cases.yaml is the seed
  set, run per model with --capability-models.
- The fast path's delete rule: deleting on the build VM is contained;
  only what breaks the host (system and home directories) stays
  dangerous, since in tests the build VM is the test machine.
- The classifier role is the local model in the example config.
- The property walk gains a classifier answering at random, junk and
  failures included; dangerous_only_when_allowed now covers its
  verdicts (checked against a mutant that runs them all).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-08 12:09:38 +00:00
dongarandClaude Opus 5.5 6ae2375194 M2.8: switch over to the new loop
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 2m16s
CI / e2e (pull_request) Successful in 10m51s
nisse-dev now runs only the conversational loop. The gated state machine
it was built alongside is gone:

- Removed control.py, planner.py, executor.py, approvals.py, gates.py
  (command_form moves to loop/reader.py), publish.py and poller.py, the
  old assembly in agent.py (its shared helpers move to loop/build.py),
  the store's state-machine view (get_state, transition), their tests,
  the old end-to-end and live planning/milestone/gate tests, and
  specs/orchestrator.qnt with its scenarios.
- Roles are driver, helper, expert and classifier; driver is required.
  loop:, roles.planner, roles.executor and tuning.executor are refused
  with what to use instead; tuning.driver takes the turn cap and failure
  limit.
- The benchmark and the capability matrix measure the driver: the
  loop's own system prompt and tools, the task's plan as the agreed
  restatement. Task plans end with finish_milestone, so earlier runs
  show as (old); stored runs' executor tuning reads as the driver's.
- A delegation on the loop records what its helper's alias resolved to
  (L9), as the old path's did.
- The dump shows a work item's revision and what it waits for.
- Behaviour the old tests covered and the loop shares stays covered:
  driver runs that fail on a model call, stop mid-turn, reset the
  failure count; go-aheads ignoring case and punctuation; a request too
  big to fit is never sent; the assembly tests, now on build_loop_agent.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-08 03:12:58 +00:00
dongarandClaude Opus 5.5 659915d309 Fixes from the first live session of the new loop
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 2m33s
CI / e2e (pull_request) Successful in 10m38s
Found live on 2026-10-07 (bring-up on nisse-testing-operator/nisse-bringup,
Opus as driver):

- The driver's prose nudge went into the system prompt, so the retry ended
  on the model's own reply. Opus 5.5 refuses that ("does not support
  assistant message prefill. The conversation must end with a user
  message"). The nudge is now a harness fact in the conversation. The
  delegation harness's prose path did the same and is fixed likewise.
- A helper's first request ended with its own delegation_started event,
  rendered as an assistant message: every delegation would have failed on
  Opus. The helper now sees it as the harness handing over its task.
- model_contract checks that every request ends on the user's side, so
  scripted tests catch this from now on (each fix is mutation-checked).
- `status` (and `details`) in an item's thread were taken as replies: they
  woke the driver and cancelled a bug's countdown. They now answer at once
  and change nothing.
- The driver had no way to answer a remark without changing the item, so it
  answered in prose. Every run woken by a message (or a review comment) now
  offers `reply`, which answers where asked and returns to the very wait
  that was interrupted.

Also: scripts/bringup-repo.sh makes the bring-up's test repository
(make_repo/delete_repo, split out of fresh_repo), and made repositories get
the agent's labels (bug, discuss, design).

Tests: test_loop_live_fixes.py (one per finding); the walk sends `status`
into threads (status_changes_nothing) and its scripted driver sometimes
answers remarks with reply.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-08 00:02:50 +00:00
dongarandClaude Opus 5.5 d7e4fd0938 M1.5 accepted: the tests run over a published image
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 2m10s
CI / e2e (pull_request) Successful in 2m54s
- The Dockerfile's test stage stacks on TEST_BASE: this build's agent by
  default, or a published image. It adds only the test tools (uv export
  of the dev group into the image's environment), never reinstalling the
  agent, so the suite tests the image as published.
- scripts/e2e.sh --image passes NISSE_E2E_MODEL into the container.
- The plan marks M1.5 done: v0.1.0's published image passed the whole e2e
  suite (9 of 9, 11m37s), with the release's own tests over it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-07 23:09:08 +00:00
dongarandClaude Opus 5.5 ff5a7a644f CI: install sshd from a fast mirror, or not at all
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 2m10s
CI / e2e (pull_request) Successful in 9m32s
archive.ubuntu.com has been crawling (a few KB/s), taking the sshd install
in every job from 8 seconds to 3.5-9.5 minutes (another agent measured it;
the runner and inference-4090 were healthy). scripts/ci-sshd.sh skips the
install when the image has sshd, and otherwise points apt at
mirrors.edge.kernel.org first (both source layouts, 22.04 and 24.04).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-07 22:29:41 +00:00
dongarandClaude Opus 5.5 0660fa3c51 The agent as a container image (M1.5)
CI / checks (push) Skipped
CI / e2e (pull_request) Failing after 49m48s
CI / checks (pull_request) Successful in 10m0s
CI / image (pull_request) Successful in 27s
- Dockerfile: built with uv into /opt/nisse (installed, not editable),
  run on python:3.12-slim with openssh-client as user nisse; stateless,
  with state in a /var/lib/nisse-dev volume, config mounted at
  /etc/nisse-dev/nisse-dev.yaml, secrets as environment variables, keys
  mounted read-only. 202 MB. Its `test` stage adds the tests and their
  tools over the installed package (UV_NO_SYNC, so `uv run` never swaps
  in the source tree); the default target is the agent.
- scripts/e2e.sh --image REF runs the suite inside an image's test stage,
  on its own sshd.
- CI builds both images on every PR (nothing pushed); release.yml pushes
  gitea.dongar.net/dongar/nisse-dev:<tag> and :latest on a v* tag, with
  the REGISTRY_TOKEN secret.
- scripts/bringup.sh builds the code under test into an image and runs it
  in the systemd unit (root, $E2E mounted at its own path); `plan` prints
  what it would run; the build-VM set-aside reads the projects: config.

Checked: the image refuses to start without a config, runs as uid 10001;
one e2e test passed from the test stage (2m45s, $0).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-07 19:50:59 +00:00
dongarandClaude Opus 5.5 6db80dfb4b End-to-end tests on a local model, and in CI on every PR
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 5m26s
CI / e2e (pull_request) Successful in 17m27s
- Every e2e role runs on qwen3.8-27b on the RTX 4090 (e2e_roles();
  NISSE_E2E_MODEL overrides, e.g. claude-haiku-4-5). The first full run
  passed 7 of 8 in 26 minutes for $0.0000; the eighth was a real finding,
  fixed with the new-project PR.
- scripts/e2e.sh reports a run's spend on the test key and the day's total
  against its budget: above $0 on the local model means a paid fallback.
- CI runs the suite as its own job on pull requests (not pushes to main),
  on a throwaway sshd (--local), cancelling a run a newer push supersedes,
  so the checks job still reports in minutes.
- The plan says so: e2e on every PR; locally only -k / --lf.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-07 15:14:43 +00:00
dongarandClaude Opus 5.5 679ace1fd8 A repository per test run instead of the shared sandbox
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 5m17s
Every live test that touches a repository gets one of its own from
e2e_support.fresh_repo, made by the test operator as an operator makes a
project's (D4, D8): seeded from tests/integration/seed/ (the sandbox's
package, without past runs' leftovers), the agent's account a collaborator
with write access, main protected (pushes only by the owner, an approval to
merge), and a deploy key generated for the run. It is deleted afterwards
(NISSE_E2E_KEEP_REPOS keeps it); scripts/e2e-cleanup.sh deletes leftovers.

- Both e2e fixtures use it; per-run base branches are gone. The merge
  helper approves first, as the protection now requires.
- test_gitea_live uses it, so CI needs NISSE_OPERATOR_TOKEN and no longer
  writes the sandbox deploy key (NISSE_PUSH_KEY).
- A live test of the helper: seeded, shared with the agent, protected
  against it, deleted.
- nisse-test.yaml's project entry is a placeholder; pytest and ruff skip
  the seed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-06 05:10:27 +00:00
dongarandClaude Opus 5.5 37965aefca Fix the run-breaking tool-result order; e2e test runs the slice (M2.1, PR D)
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 2m17s
The first live run of the new loop's e2e test found that a fact recorded
while start_milestone was in flight landed between the tool call and its
result; both API formats reject that, so every later request in the run
failed. Views now keep each result right after its call. Scripted test
models check the APIs' ordering rules, so this can't pass unit tests
again.

The e2e tests no longer depend on the sandbox's history: each run asks for
a uniquely named function and works against a base branch of its own,
deleted afterwards. The review step asks for a change that can't already
be there. The scripted operator gives up after five fruitless answers, and
each run keeps its state store for inspection. The driver's prompt says
review fixes stay in the current milestone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-03 15:11:06 +00:00
dongarandClaude Opus 5.5 897181b0f4 Add the end-to-end test of the new loop's slice (M2.1, PR D part 2)
CI / checks (push) Skipped
From an issue the scripted operator files and assigns in Gitea, through
the restatement, go-ahead, milestone and PR, a review comment answered
with a pushed fix, to an approved and merged PR and a closed item and
issue. The operator acts in Gitea through its own test account
(NISSE_OPERATOR_TOKEN) and in the item's thread through the in-memory
transport. Runs from scripts/e2e.sh.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-03 14:20:07 +00:00
dongarandClaude Opus 5.5 b9f52be42c Add the end-to-end test, run after each milestone (M1.6)
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m48s
tests/integration/test_end_to_end.py drives the whole assembled agent
(real models, the Gitea sandbox, a real build host) through a milestone
with a scripted operator, and checks what bring-up run 3 checked by hand:
a PR holding only the work, pushed with the push credential; no
credential in the clone; the planner's view carrying facts, not the
executor's turns. It cleans up after itself on the sandbox and the build
host.

scripts/e2e.sh runs it on the bring-up build VM (or --local). It has its
own marker, e2e, kept out of check.sh and CI since it spends tokens.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-02 22:59:13 +00:00
dongarandClaude Opus 5.5 761ebdb857 Run every command on the build VM over SSH (M1.3)
CI / checks (push) Skipped
CI / checks (pull_request) Failing after 55s
execution.SSHHost.execute is now the only way the agent runs anything
(L13). Commands travel as a script on SSH's stdin, so the secrets handed
to a call are never on a command line, and every value handed out is
redacted from the output. Timeouts kill the remote process group, and
an unreachable host is reported as such, not as a failed command.

Workspace and GitRepo run on the build VM. The clone uses the
repository's SSH URL, so no credential is stored in it (#89); fetches
and pushes get the push key over stdin, in a short-lived ssh-agent,
and PRs are still opened from the agent's host with its API token.
The bench runs on the build host too.

Tests reach "the build VM" through a throwaway local sshd (nisse_dev
.testing); CI installs openssh-server. The live push test now pushes
with the sandbox's deploy key (NISSE_PUSH_KEY).

Fixes #89.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-02 21:39:46 +00:00
dongarandClaude Opus 5.5 a18321b4b0 Add a bring-up script and a transcript dump; let nisse-dev install on its own VM (M0.2)
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m16s
Development Plan M0.2.

- scripts/bringup.sh: start [REF] sets the previous run aside under
  /root/nisse-e2e/runs/, exports the ref under test and starts the agent
  as the transient systemd unit nisse-bringup, so it outlives the shell
  that started it; stop (SIGTERM, clean shutdown), status, logs, and
  collect (the journal log and a readable transcript).
- nisse_dev.dump (python -m nisse_dev.dump STATE_DB [--full]): a readable
  dump of a state store's state, pending intents and canonical log. Opens
  the store read-only, so it is safe against a live agent.
- The agent runs as root on this development VM, as it will on its own
  VM. Architecture D3/D6/D7 corrected to match the operator's intent:
  installing packages and toolchains on its own VM is class 2, recorded
  so the environment can be rebuilt (it was class 3 with provisioning an
  open question); egress names OS package mirrors. The plan's M0.2, M4.1,
  M4.2 and bring-up guards follow.

Acceptance: a smoke run started, answered status in the test room,
stopped cleanly, and collect produced the log and transcript.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-01 16:16:32 +00:00
dongarandClaude Opus 5.5 50b46821d7 Add an executor benchmark that keeps its results
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m11s
Choosing a local executor model, and tuning its knobs, needs measured
success rates across repeated runs, comparable with every model
measured before.

- Four canned milestones under nisse_dev/bench/tasks: write a function,
  fix a bug a failing test exposes, thread an option through several
  files, and find one failure in output far past max_output_chars. Each
  is an approved plan over a seed project, judged by a hidden check the
  model never sees; a reference solution proves each is solvable and
  that its check tells the difference
- Runs use the agent's own client wiring, now shared as
  make_llm_clients, with delegation and ask_expert off: an escalation is
  a failure of the model under test
- Each run is stored as one JSON line in bench/results/, committed, with
  the model's backing name and declared facts, a note on how it was
  served, the task's content hash, the agent's commit and tuning, the
  outcome, turns, misbehaviour counts, context trims, tokens, time and
  LiteLLM's cost; a fallback answer can never count as a success
- The report rebuilds the comparison from every stored run, with Wilson
  95% intervals, marking runs against older task versions
- scripts/benchmark.py runs and reports; scripts/evaluate_model.sh takes
  a new model through the context probe, the capability matrix and the
  benchmark in one command
- Clients keep LiteLLM's per-call cost, and BudgetedClient counts the
  requests it had to trim
- The tuning guide gains "Benchmarking models"

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 00:35:24 +00:00
dongarandClaude Opus 5.5 b934c46714 Enforce the tool-call contract, asking servers only where it works
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 56s
The executor ended a milestone on any reply without a tool call, and
nothing held a model to the offered tools. Measured on the 4070:
tool_choice "required" is enforced by vLLM (local-fast) and Anthropic,
but qwen3-14b (local-coder) under it cannot stop and writes until the
output ceiling, a known model bug. So enforcement is asked for per
model, and the loop holds the contract itself everywhere.

- The catalog reads supports_tool_choice from LiteLLM's model_info;
  BudgetedClient sends tool_choice only to models declared true, as
  "required" on the normalized route and {"type": "any"} on the
  Anthropic passthrough, and only when tools are offered
- The executor gains an explicit finish tool, the only way to end a
  milestone. Finishing with nothing changed is refused once; a prose
  reply gets one nudge, then escalates; a call to a tool not offered
  counts toward failure_limit, like a failed command; calls after
  finish are recorded as not run
- Clients record the model that actually answered. ModelFacts.answered
  recognises a proxy fallback, which once credited Claude's answers to
  a local model that could not call tools at all
- The context probe reports a fallback instead of trusting it; rerun,
  both local models now refuse over-long prompts, as they should
- The capability matrix checks every answer came from the model asked,
  that models call only offered tools, and that a declared
  supports_tool_choice is true
- Spec A15 and the tuning guide say how enforcement is requested,
  declared and backed up

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 22:54:49 +00:00
dongarandClaude Opus 5.5 494715efa9 Fit every model request to its context window
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 55s
Every turn re-sent the whole transcript with nothing checking it against
the model's window. On a local model that fails silently: Ollama drops
the start of an over-long prompt, which is where the system prompt and
the plan are. Measured on the Jetson's llama3.2, cut at 4096 tokens with
no error.

- The model catalog reads max_input_tokens and max_output_tokens from
  LiteLLM's model_info, and startup refuses a role on a model with no
  declared window
- BudgetedClient wraps both routes. It fits the canonical events before
  rendering: older tool results cut to head and tail, then the oldest
  turns dropped with a note, keeping calls with their results; a request
  that still cannot fit fails with ContextOverflow and is never sent.
  The output ceiling is the smaller of config and the model's limit
- Tokens are estimated from characters, pessimistically, and the
  estimate is corrected from reported prompt sizes, only ever upward
- The executor takes its scope from the log: the latest plan, which is
  the approved milestone plan, goes in its system prompt, and it sees
  only events after it
- Tool output keeps its head and, mostly, its tail, so a test runner's
  failure summary survives truncation
- tuning.context.keep_recent_turns and safety_margin are new knobs
- scripts/probe_context.py checks a declared window against what the
  server does; it confirmed llama3.2 cuts at the declared 4096
- The tuning guide covers the budget, declaring a local model's limits,
  and the probe

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 00:26:30 +00:00
dongarandClaude Fable 5 c1041a5ba1 Scaffold hello-world apps, test frameworks, and Gitea CI
CI / checks (push) Successful in 45s
CI / checks (pull_request) Successful in 6s
- uv workspace tying nisse-core, nisse-dev, nisse-ops together
  (src layouts, workspace-sourced nisse-core dependency)
- Placeholder hello-world entry points for both agents, calling
  through nisse-core to exercise the package boundary
- pytest unit tests per package; integration tests (marker-gated)
  running the installed console scripts as subprocesses
- ruff lint + format, nisse_* declared first-party
- Gitea Actions workflow running lint, format check, unit and
  integration tests; scripts/check.sh mirrors it locally

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-05 18:48:51 +00:00