Skip to content

Execution Log: instances scoped to their own work items

Append-only log of progress for the user's visibility.

Phase transitions

PhaseEnteredReviewed/approved byNotes
phase-selection2026-09-08Tier 4 (human-approves-pr, and a named human security sign-off at humanSignOffMinTier: 4): a seam on the dispatch path — the one place an event becomes a session — plus an additive block in cli-config.schema.json (an autonomy.sensitivePaths entry). Brainstorming skipped: the ticket's bullets are the requirements and its three questions are answered in requirements.md § Open questions. No authorized the-loop execute reaches this cloud session, so the selection is recorded here and every phase is walked
requirements-definition2026-09-08requirements.md — five requirements, six abuse cases
design2026-09-08design.md — one block, one seam, one token, one route; decision-110
test-planning2026-09-08testing-plan.md — thirteen rows, seven applicable
tasks-breakdown2026-09-08tasks.md — seven tasks
implementation2026-09-08On claude/github-issue-322-2yely4
verification2026-09-08evidence/verification.md — rows T1, T2, T3, T8, T10, T12; evidence/security-review.md — six abuse cases, six closed
needs-review2026-09-08PR raised; awaiting the owner (tier 4: human-approves-pr + the named security sign-off)
complete

Pull requests

PRScope / tasksStatus
#324tasks 1–7: the whole work itemopen

Progress entries

2026-09-08 — implemented, verified, ready for review

  • Phase: implementation → verification → needs-review
  • Did: tasks 1–7, red first each. the_loop.instance (InstanceConfig, parse_address, decide, the outcome constants); cli_config.apply_instance (the _ghBinary-style fan-out, applied only when a block is declared) and RoutingConfig.instance; the seam in Dispatcher.handle with _manages and _refuse_scope, the four outcomes added to SETTLED_OUTCOMES and to the poll.comment_settled catalogue entry; ControlRecord.instance on every record the dispatcher and core.sessions write; command_comment(address=) on the CLI's posted keyword, announcement_body(instance=), TmuxRunner.instance with the one-time tmux -V probe and the -e THE_LOOP_INSTANCE=<name> argv; core.sessions refusing a start on a locked instance before anything is recorded or posted; core/instance.py::describe_instance behind GET /api/v1/instance, the get_instance MCP tool, loop.instance() and the status line; the schema block (authored + packaged, byte-identical, purely additive), the template and this repository's config; the docs.
  • Checkpoint/tests: make check — see evidence/verification.md. New tests: 60 unit (test_instance.py), 14 two-dispatcher scenarios (test_instance_integration.py), 1 status-line test. Existing assertions changed: one — the settled-vocabulary test in test_routing.py, which by its own docstring is where a new settlement is added.
  • Self-review: three passes over the diff. Pass one found the fan-out stamping _instance onto every routing dict, which broke three equality tests on configs with no block — made conditional. Pass two verified three design claims against the code: only comment and review bodies reach parse_address (event_body returns None for an issue's own body, so an issue body cannot address); the receiver's and the poller's reload paths build from load_cli_config, so the block is hot-reloaded; the dashboard's config writer reads raw YAML, so the private key never reaches the file. Pass three found nothing new.
  • Next: the owner's review and the tier-4 security sign-off.
  • Blockers: none.

2026-09-08 — spec chain drafted

  • Phase: requirements-definition → tasks-breakdown
  • Did: read webhook/dispatcher.py (handle, _on_unmatched, _apply_control, _spawn_for, reload), control.py, cli_config.py, state.py, runner.py, announce.py, core/sessions.py, core/lifecycle.py, api/routes.py and the two daemons at 10a55b3; found that every instance judges every labelled event identically and that the one seam where an event becomes a session is handle; found the _ghBinary fan-out precedent for a top-level block reaching RoutingConfig; found the standing-session name grammar and the collaborator @login argument as the precedents for a validated token. Wrote the four artifacts and the decision.
  • Checkpoint/tests: baseline — test_control.py green (57 passed).
  • Next: task 1 (the block), red first.
  • Blockers: none.

Verification results

Only when this work item declared test-planning away. It did not: results live in testing-plan.md.

What was verifiedCommandOutcomeEvidence
see testing-plan.md

Design critic review

Not selected for this work item.

RoundCritic (<harness>/<model>)OutcomeFindings → dispositionLink

Review cycles

CycleType (self/critic/security)ReviewerOutcomeLink
1selfthe-loop (this session)new findings — the unconditional _instance fan-out broke three equality tests on block-less configs: made conditional; the settled vocabulary needed the four outcomes in its test and catalogue entry: addedthis log
2selfthe-loop (this session)zero new findings — three design claims verified against the code (issue bodies never parsed; both reload paths carry the block; the config writer never sees the private key)this log
3selfthe-loop (this session)zero (converged)this log
criticunavailable — reviews.critics is empty in this repository's config; does not count toward criticReviewCount
4securitythe-loop checklistpass on the autonomous review; human sign-off pending (tier 4)evidence/security-review.md

Security review (gate)

  • Mechanism: the-loop checklist (security.review.mechanism: auto; no security-review skill is invocable from this session's plugin set)
  • Outcome: pass — evidence/security-review.md, six abuse cases closed
  • Human sign-off: required and pending (tier 4 ≥ humanSignOffMinTier: 4) — the owner's approval of the PR is the named sign-off; requested in the PR briefing

Final validation evidence

RequirementProof
R1.1 the block's shapetest_the_block_reads_name_mode_and_declared_refs; make validate against the authored schema
R1.2 unset = 13.3.1, additivetest_the_block_unset_is_an_unnamed_open_instance, test_a_bare_routing_mapping_is_an_unnamed_open_instance, Scenario: An open, unnamed instance is 13.3.1; test_control_integration.py + test_graphlink_integration.py unchanged and green; CURRENT_CONFIG_VERSION unchanged
R1.3 in cli-config.yaml, hot-reloadedtest_the_block_reaches_routing_config_through_the_loaded_config; both daemons' reload paths build from load_cli_config (verified in self-review pass two)
R1.4, R1.5 narrowingtest_the_block_refuses_a_name_outside_the_grammar, test_an_unknown_mode_resolves_to_locked, test_the_block_addressed_without_a_name_is_locked, test_the_block_skips_a_bad_declaration_by_index_and_keeps_the_rest
R2.1 managed = as before, every modeScenario: A managed work item is in scope whatever the mode (×3), Scenario: A stopped work item is still this instance's; decide rows 3
R2.2 open = 13.3.1decide row 4; the open-unnamed scenario
R2.3 addressedScenario: An addressed start reaches only the instance it names, Scenario: An unaddressed start on two addressed instances reaches neither
R2.4 lockedScenario: A locked instance takes only what its config declares; test_an_address_does_not_unlock_a_locked_instance
R2.5 command → control.rejectedthe addressed and unaddressed scenarios assert control.rejected with the reason and delivery_outcome == control-rejected
R2.6 no markScenario: A non-owner leaves nothing behind (no portable/, no session, no spawn, no delivery, one dispatch.dropped)
R2.7 sessions startScenario: sessions start on a locked instance, Scenario: sessions start on an addressed instance
R3.1, R3.4 the tokentest_the_token_is_read_as_a_whole_word_with_a_case_insensitive_prefix (six bodies), test_a_malformed_token_is_not_an_address_and_reaches_no_record (eleven)
R3.2 address is authoritativeScenario: An explicit address wins over the managed set; decide row 2
R3.3 ambiguitytest_two_different_addresses_are_ambiguous, Scenario: A comment naming two instances is ambiguous
R3.5 both ingressesthe seam reads event_body, the one body accessor both ingresses feed (parse_control_command reads the same); the poller's RoutedEvent shape is what the scenarios use (labeled=False)
R4.1 control.instancetest_the_control_record_carries_the_instance_and_reads_old_records_as_unnamed; every scenario that spawns asserts the record names the instance
R4.2 the posted keywordtest_the_posted_keyword_carries_the_address; the addressed sessions start scenario
R4.3 the announcementtest_the_announcement_names_the_instance
R4.4 the routetest_describe_instance_merges_declared_session_and_control_sources, test_describe_instance_for_an_unset_block_is_unnamed_open_and_empty; test_api_contract_parity.py; test_mcp_integration.py; test_sdk_docs_parity.py
R4.5 statustest_status_prints_the_instance_line
R4.6 THE_LOOP_INSTANCEtest_a_named_instance_spawns_with_the_environment_variable, test_an_unnamed_instance_spawns_with_the_argv_it_used_before, test_an_old_tmux_gets_no_environment_flag_and_one_warning
R5.1–R5.3 docstest_docs_parity.py (P3, P4, P5 over instance-options.md); make validate (template + this repo's config); markdownlint over 977 files
A1–A6evidence/security-review.md

Capability docs

Capability docWhat changedHistory row
instances.mdminted — the block, the managed set, the modes, the address token, the refusal, the record, the surface, what it does not do, and the seams left for the manager and the ticket-managed instanceissue-322 row
capabilities.mdthe index row for instances
webhook-triggers.mda current-behaviour bullet: the scope decision runs before anything else acts on an event, and can only refuseissue-322 row
cli.mda current-behaviour bullet: the status line, the locked refusal of sessions start, the address on posted keywordsissue-322 row
control-plane.mda current-behaviour bullet: GET /api/v1/instance, get_instance, loop.instance() — the manager seamissue-322 row

Documentation

DocumentWhat changed
docs/config/cli/instance-options.mdnewname, scope.mode, scope.workItems with type, default, grammar and the three rules (parity-tested)
docs/config/cli/index.mdthe options-by-area row
docs/cli/instances.mdnew guide — the shape, the three modes, addressing, pinning and locking, what the record says, what the design does not do
docs/cli/index.mda "you want to…" row pointing at the guide
docs/cli/state.mdcontrol.instance in the field table and the example record
docs/cli/commands/status.mdthe instance line, first in the example, and its JSON form
docs/.vitepress/config.mtssidebar entries for the guide and the options page
docs/api-specs/openapi/the-loop.v1.yamlGET /api/v1/instance (getInstance)
docs/sdk/reference.mdthe instance() row
skills/the-loop/reference/automation.mda bullet beside the control keywords: the block, the modes, the address token
skills/the-loop/templates/cli-config.yaml, .the-loop/cli-config.yamlthe instance block at its defaults, commented
.the-loop/cli-config.schema.json, cli/the_loop/schemas/cli-config.schema.jsonthe instance block (byte-identical copies; purely additive)
docs/decisions/decision-110.md, decisions.mdthe decision and its index row
README.md, skills/the-loop/SKILL.mdunchanged — neither enumerates the daemon's configuration blocks; the operating model itself did not change (an instance is still armed by a label and started by an authorized keyword)

Released under the MIT License.