Skip to content

fix(devtools): pytest slot ownership is the pytest pool, entered through agentctl - #4681

Open
Sinity wants to merge 6 commits into
masterfrom
feat/agentctl-pytest-slot
Open

fix(devtools): pytest slot ownership is the pytest pool, entered through agentctl#4681
Sinity wants to merge 6 commits into
masterfrom
feat/agentctl-pytest-slot

Conversation

@Sinity

@Sinity Sinity commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Two changes so the Sinnix runtime rename can deploy and the pytest slot cannot be bypassed.

  • Every consumer reads AGENTCTL_* runtime variables first and falls back to SINNIXD_*; devtools/sinnixd_service_context.py becomes devtools/agentctl_service_context.py; both unit and slice families are recognised.
  • holds_pytest_slot() is true only inside the pytest pool (cgroup under the pytest slice, AGENTCTL_POOL=pytest, or the explicit POLYLOGUE_PYTEST_SLOT=held escape). A job id is never ownership. The direct pueue client is deleted; a caller outside the pool submits the new declared operation pytest_focused through agentctl job start … --wait and cancels through agentctl job cancel.
  • devtools verify selects no pytest step for change sets inside .agentctl/**, *.md, .github/** and records the reason; the hosted verify job fails unless the newest receipt succeeded with a pytest step or a recorded "none" selection. Managed runs dispose of their scratch trees on every exit.

Why correct: ownership is derived from the cgroup the executor places the process in, which no environment edit can forge; the queue entry is the same agentctl job start route every other heavy operation uses.

Residual risk: the [workspace].verify / publish descriptor table is deliberately not in this PR because the deployed agentctl still rejects unknown workspace fields; it follows the deploy in a one-line change.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YHwAE9cH8DjXFSorzfSxD8

Sinity and others added 2 commits September 5, 2026 04:06
The Sinnix runtime is renamed from sinnixd to agentctl. Every consumer of its
job environment (pytest slot, verify receipts, declared operation guards,
agent-job bounds, the service-context proofs) reads AGENTCTL_<NAME> first and
falls back to SINNIXD_<NAME> through devtools.agent_env.runtime_env, so the
fallback can be removed in one place. The cgroup classifiers recognise both
slice families (agentctl-<pool>.slice, sinnixd-pueue-<pool>.slice) and the
queue runner is found under either name.

devtools/sinnixd_service_context.py is renamed to
devtools/agentctl_service_context.py. The descriptor cross-contract test now
runs against pkgs/agentctl's parser; it no longer asserts the retired
parameters/service operation fields, which that parser rejects at parse time.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHwAE9cH8DjXFSorzfSxD8
…ough agentctl

holds_pytest_slot() is true only inside the pytest pool: a cgroup under
agentctl-pytest.slice (or sinnixd-pueue-pytest.slice), AGENTCTL_POOL=pytest
(or SINNIXD_QUEUE_POOL), or the explicit POLYLOGUE_PYTEST_SLOT=held escape.
A job id, principal or operation name is never ownership, so a lane queues
like everything else and a managed lane running bare pytest is refused with
the devtools test route.

The direct pueue client is gone. A caller outside the pool submits the new
declared operation pytest_focused (pool pytest, result pytest) through
`agentctl job start <checkout> pytest_focused --workspace <checkout> --
<launch file>`, waits on `agentctl job get`, and reads the exit status from
the job view plus the typed timeout receipt beside the log; a killed waiter
cancels with `agentctl job cancel`. The operation also accepts a devtools
test selection and runs it in place.

Descriptor: [workspace] keeps verification_operations = ["verify_quick"] and
declares verify = {focused, candidate = "hosted:verify", corpus} and
publish = "pr"; verify_affected stays declared for manual use and is in no
publication list.

devtools verify: a change set inside .agentctl/**, *.md and .github/** selects
no pytest step and records selection "none" with its reason in the receipt;
the hosted verify job now fails unless the newest run receipt succeeded with
a pytest step or a recorded "none" selection (devtools.verify_receipt_check).
Managed runs dispose of both temporary trees on every exit except a failed
run's, including a signalled waiter.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHwAE9cH8DjXFSorzfSxD8
@coderabbitai

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Team

Run ID: 4e1d369c-eae1-471d-bc09-8d2a5d6124b9


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 5, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-05T19:52:01.068364Z 28d91a6 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b8c0a3800b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread devtools/pytest_slot.py
Comment on lines +383 to +385
job_id = _document(started, verb="job start").get("job_id")
if not isinstance(job_id, int) or isinstance(job_id, bool):
raise PytestSlotUnavailableError(REFUSAL.format(reason=f"`{AGENTCTL} job start` returned no job id"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Accept the runtime's UUID job identifiers

When agentctl job start --json returns its normal UUID job identifier (the same identifier this commit requires for AGENTCTL_JOB_ID and agentctl-job-<UUID>.service), this integer-only check rejects the successful submission as having no job ID. Consequently, every devtools test or hosted devtools verify invocation outside the pytest pool fails before polling the queued job, instead of waiting for the required pytest_focused run. Accept the runtime's string UUID and pass it unchanged to job get/job cancel.

AGENTS.md reference: AGENTS.md:L151-L155

Useful? React with 👍 / 👎.

Comment thread devtools/pytest_slot.py
if receipt is None or receipt.get("status") != "timed_out":
raise
returncode = 124
with _on_exit(lambda: _reap_job(job_id, env=client, launch_path=launch_path), on_exit):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve scratch paths when cancellation is refused

If the waiter receives a signal or _wait_for raises while agentctl job cancel is refused or temporarily unavailable, _reap_job deliberately leaves the launch file because the queued job may still start, but this second exit action unconditionally deletes the launch document's TMPDIR and basetemp paths. The surviving job then runs with missing temporary directories, causing tempfile users to fall back outside containment or pytest to fail; cleanup should occur only after cancellation is confirmed, while a job that may still run must retain all resources referenced by its launch file.

Useful? React with 👍 / 👎.

Sinity added a commit to Sinity/sinnix that referenced this pull request Sep 5, 2026
Polylogue's deployed devtools still read SINNIXD_* until Sinity/polylogue#4681
lands; the executor exports both names until then.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHwAE9cH8DjXFSorzfSxD8
Sinity and others added 3 commits September 5, 2026 16:28
- pytest_slot reads agentctl's terminal phases: `timeout` (not
  `timed-out`); cancelled, vanished, slot_occupied and the launch-side
  phases each refuse with their meaning.
- The declared-operation guard (Python and the Node proof) checks the
  exported job id, project and operation and that the process cgroup is
  under the declared pool's slice; job ids are references, not UUIDs, and
  units carry no `env -i` to introspect.
- The hosted verify receipt check is bound to the run id `devtools verify`
  wrote to current-run.json (`--run-id`), never to the newest file.
- `.agentctl/project.toml` declares `workspace.verify` and `publish`;
  the keys agentctl ignores and nothing here reads are gone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHwAE9cH8DjXFSorzfSxD8
- `.agentctl/project.toml` drops `[conflicts]`, `[owner_adapters]` and the
  environment preflight; agentctl on the host refuses unknown tables and
  fields, and nothing in this repository reads them.
- `.gitignore` keeps `.agentctl/project.toml` tracked and ignores what
  agentctl writes beside it.
- Tests: the descriptor contract asserts the `verify` and `publish`
  workspace keys; the receipt test derives its future mtime from the file,
  not the host clock; the pytest_slot verb helper keeps the job id.

Folds #4689.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHwAE9cH8DjXFSorzfSxD8
… directly

The held slot launches pytest as a process group through `Popen`; the rerun
tests stand in for that process instead of `subprocess.run`. The agent-tier
refusal test places itself in the agent pool, since the test corpus itself
runs inside the pytest pool that owns the slot.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHwAE9cH8DjXFSorzfSxD8
…ough agentctl

The slot is owned by the pytest pool cgroup or AGENTCTL_POOL=pytest, never
a bare job id. Runs outside a task queue through the declared
pytest_focused operation instead of a private pueue client. The descriptor
carries the verify table and publish mode agentctl reads; retired keys are
gone. Runtime names read AGENTCTL_* with the SINNIXD_* fallback.

Merged onto master 31c723d (runner rename, corpus width, slot timeout).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHwAE9cH8DjXFSorzfSxD8
@Sinity Sinity reopened this Sep 5, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 28d91a6187

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +23 to +25
const units = [`agentctl-job-${jobId}.service`, `sinnixd-job-${jobId}.service`];
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(jobId)) {
throw new Error("shared-Chrome dev-loop proof requires a Sinnixd job UUID");
throw new Error("shared-Chrome dev-loop proof requires a runtime job UUID");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Accept deployed job references in the dev-loop child

When dev_loop_proof runs in the deployed context modeled by the new service tests—such as AGENTCTL_JOB_ID=polylogue-dev_loop_proof-8e3c63a7 under agentctl-interactive.slice—the Python context guard accepts the job and launches this Node child, but this UUID check rejects it before any Chrome proof runs. The same function then expects a legacy per-job unit rather than the pool-slice contract used by agentctl_service_context.py and live_provider_proof.mjs, so the declared operation always reports shared-Chrome control proof failed on the current runtime; use the same opaque-job-id and pool-slice checks in this child.

AGENTS.md reference: AGENTS.md:L180-L184

Useful? React with 👍 / 👎.

Comment thread devtools/pytest_slot.py
Comment on lines +407 to 409
receipt = _read_timeout_receipt(log_path)
returncode = _job_exit_status(view, receipt=receipt)
launch_path.unlink(missing_ok=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Delete launch documents after terminal refusals

When agentctl job get returns a terminal non-execution phase such as refused, slot_occupied, vanished, or launch-failed, this call raises PytestSlotUnavailableError before the following launch_path.unlink() executes. Because the launch document contains the complete resolved pytest environment—including credentials such as the API key exercised by test_the_client_environment_carries_only_the_allowed_keys—each refused submission leaves a secret-bearing .cache/verify/pytest-slot-<pid>.json behind indefinitely; remove it in a terminal-job cleanup path even when _job_exit_status refuses the run.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant