diff --git a/.custodian-brief.md b/.custodian-brief.md index f4de94b..714bfa0 100644 --- a/.custodian-brief.md +++ b/.custodian-brief.md @@ -2,19 +2,11 @@ # Custodian Brief — activity-core **Domain:** infotech -**Last synced:** 2026-07-02 00:19 UTC +**Last synced:** 2026-07-03 17:22 UTC **State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)* ## Active Workstreams -### LLM Output Robustness & The Producer Trust Boundary -Progress: 8/10 done | workstream_id: `4ef0d53b-1777-41ae-80c6-1b69fdb34726` - -**Open tasks:** -- ! Reproduce & Root-Cause The Failure `74fd16a5` - *(wait: Local analysis complete: mechanism is the unbounded ~1-per-workstream recommendation list (16 active workstreams; break at char 5268 ~rank 8-9); both first attempt and retry failed. Exact token + finish_reason are unrecoverable from activity-core (complete() drops finish_reason; report cap 4000 < 5268; log cap 2000). Remaining: pull llm-connect producer-side logs on railiance01 (cluster/operator-owned). Does NOT block T02/T03 — mitigation is identical regardless.)* -- ► Tests + Calibration Re-Entry `c881500b` - ### Adopt State Hub Beachhead Endpoint Progress: 0/2 done | workstream_id: `bbc07f9e-9323-4b2b-b556-c33b37d0b228` @@ -22,19 +14,10 @@ Progress: 0/2 done | workstream_id: `bbc07f9e-9323-4b2b-b556-c33b37d0b228` - ! Point STATE_HUB_URL at the beachhead `76b6132d` - ! Retire the bespoke actcore-state-hub-bridge proxy `526c2129` -### Daily Triage LLM Reconciliation And Evidence -Progress: 2/5 done | workstream_id: `f2c73ac6-13f0-4005-82cc-76c7c9f9c8b9` - -**Open tasks:** -- ! Run Daily Triage Fixture Smoke `10e0df77` -- ! Collect Three Clean Scheduled Runs `dc6b9482` -- ! Close Handoff State `ecc57e21` - ### Intent gap closure -Progress: 4/6 done | workstream_id: `d64cfbba-6da7-4737-afb9-866afa0e9cda` +Progress: 5/6 done | workstream_id: `d64cfbba-6da7-4737-afb9-866afa0e9cda` **Open tasks:** -- ! Close Daily Triage Scheduled-Run Trust Gap `7012e4fd` - ! Promote Issue-Core Task Emission Safely `3854677b` ### Weekly Coding Retrospection schedule (Saturday evenings) diff --git a/.forgejo/workflows/ci-smoke.yaml b/.forgejo/workflows/ci-smoke.yaml new file mode 100644 index 0000000..6c2b29e --- /dev/null +++ b/.forgejo/workflows/ci-smoke.yaml @@ -0,0 +1,27 @@ +name: CI Smoke + +on: + push: + branches: + - main + workflow_dispatch: + +jobs: + host-smoke: + runs-on: self-hosted + steps: + - name: Routing probe (host runner) + run: | + set -eu + echo "repository=${GITHUB_REPOSITORY:-unknown}" + echo "sha=${GITHUB_SHA:-unknown}" + echo "runner=${RUNNER_NAME:-unknown}" + uname -a + + container-smoke: + runs-on: ubuntu-latest + steps: + - name: Routing probe (container label) + run: | + set -eu + echo "container-smoke ok for ${GITHUB_REPOSITORY:-unknown}" \ No newline at end of file diff --git a/.forgejo/workflows/image.yaml b/.forgejo/workflows/image.yaml new file mode 100644 index 0000000..1217b8b --- /dev/null +++ b/.forgejo/workflows/image.yaml @@ -0,0 +1,50 @@ +name: Build and Publish Container Image + +on: + push: + branches: + - main + paths: + - ".forgejo/workflows/image.yaml" + - "Dockerfile" + - "src/**" + - "migrations/**" + - "scripts/**" + - "activity-definitions/**" + - "event-types/**" + - "tasks/**" + - "pyproject.toml" + - "uv.lock" + - "alembic.ini" + workflow_dispatch: + +env: + REGISTRY: forgejo.coulomb.social + IMAGE_NAME: coulomb/activity-core + DOCKER_HOST: tcp://127.0.0.1:2375 + +jobs: + build-and-push: + runs-on: container-build + steps: + - name: Build and push image + env: + REGISTRY_USER: ${{ secrets.REGISTRY_USER }} + REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }} + run: | + set -eu + REF="${GITHUB_SHA:-main}" + SHORT="${REF:0:7}" + mkdir -p buildctx "${HOME}/bin" + wget -qO /tmp/repo.tar.gz \ + "https://forgejo.coulomb.social/${GITHUB_REPOSITORY}/archive/${SHORT}.tar.gz" + tar xzf /tmp/repo.tar.gz -C buildctx --strip-components=1 + wget -qO- https://download.docker.com/linux/static/stable/x86_64/docker-27.3.1.tgz \ + | tar xz --strip-components=1 -C "${HOME}/bin" docker/docker + export PATH="${HOME}/bin:${PATH}" + echo "${REGISTRY_TOKEN}" | docker login "${REGISTRY}" -u "${REGISTRY_USER}" --password-stdin + IMAGE="${REGISTRY}/${IMAGE_NAME}" + docker build -t "${IMAGE}:latest" -t "${IMAGE}:main-${SHORT}" buildctx + docker push "${IMAGE}:latest" + docker push "${IMAGE}:main-${SHORT}" + echo "pushed ${IMAGE}:latest and ${IMAGE}:main-${SHORT}" \ No newline at end of file diff --git a/k8s/railiance/20-runtime.yaml b/k8s/railiance/20-runtime.yaml index 1e017cd..1cf93b2 100644 --- a/k8s/railiance/20-runtime.yaml +++ b/k8s/railiance/20-runtime.yaml @@ -14,8 +14,8 @@ data: LLM_CONNECT_URL: http://llm-connect.activity-core.svc.cluster.local:8080 LLM_CONNECT_TIMEOUT_SECONDS: "300" REPO_SCOPING_URL: http://repo-scoping.repo-scoping.svc.cluster.local:8020 - ISSUE_CORE_URL: http://issue-core.issue-core.svc.cluster.local:8010 - ISSUE_SINK_TYPE: "null" + ISSUE_CORE_URL: http://actcore-issue-core-bridge.activity-core.svc.cluster.local:8765 + ISSUE_SINK_TYPE: "rest" ACTIVITY_DEFINITION_DIRS: /etc/activity-core/external-definitions OPS_INVENTORY_PATH: /etc/activity-core/ops/service-inventory.yml INTER_HUB_URL: "" @@ -643,6 +643,117 @@ spec: periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 6 +apiVersion: v1 +kind: Service +metadata: + name: actcore-issue-core-bridge + namespace: activity-core + labels: + app.kubernetes.io/name: actcore-issue-core-bridge + app.kubernetes.io/part-of: activity-core +spec: + selector: + app.kubernetes.io/name: actcore-issue-core-bridge + ports: + - name: http + port: 8765 + targetPort: http +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: actcore-issue-core-bridge + namespace: activity-core + labels: + app.kubernetes.io/name: actcore-issue-core-bridge + app.kubernetes.io/part-of: activity-core +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: actcore-issue-core-bridge + template: + metadata: + labels: + app.kubernetes.io/name: actcore-issue-core-bridge + app.kubernetes.io/part-of: activity-core + spec: + hostNetwork: true + dnsPolicy: ClusterFirstWithHostNet + containers: + - name: proxy + image: activity-core:railiance01-prod + imagePullPolicy: Never + ports: + - name: http + containerPort: 18081 + command: + - python + - -c + - | + from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer + from urllib.error import HTTPError, URLError + from urllib.request import Request, urlopen + + TARGET = "http://127.0.0.1:18765" + HOP_HEADERS = {"connection", "host", "keep-alive", "proxy-authenticate", + "proxy-authorization", "te", "trailers", + "transfer-encoding", "upgrade"} + + class Proxy(BaseHTTPRequestHandler): + def do_GET(self): + self._proxy() + + def do_POST(self): + self._proxy() + + def do_PATCH(self): + self._proxy() + + def _proxy(self): + length = int(self.headers.get("content-length", "0") or "0") + body = self.rfile.read(length) if length else None + headers = { + key: value + for key, value in self.headers.items() + if key.lower() not in HOP_HEADERS + } + request = Request( + TARGET + self.path, + data=body, + headers=headers, + method=self.command, + ) + try: + timeout = 360 if self.command == "POST" else 30 + with urlopen(request, timeout=timeout) as response: + payload = response.read() + self.send_response(response.status) + for key, value in response.headers.items(): + if key.lower() not in HOP_HEADERS: + self.send_header(key, value) + self.end_headers() + self.wfile.write(payload) + except HTTPError as exc: + payload = exc.read() + self.send_response(exc.code) + self.end_headers() + self.wfile.write(payload) + except URLError as exc: + self.send_response(502) + self.end_headers() + self.wfile.write(str(exc).encode()) + + ThreadingHTTPServer(("0.0.0.0", 18081), Proxy).serve_forever() + readinessProbe: + httpGet: + path: /healthz + port: http + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 +--- --- apiVersion: batch/v1 kind: Job diff --git a/src/activity_core/context_resolvers/state_hub.py b/src/activity_core/context_resolvers/state_hub.py index 4de5439..3b7bd78 100644 --- a/src/activity_core/context_resolvers/state_hub.py +++ b/src/activity_core/context_resolvers/state_hub.py @@ -513,11 +513,19 @@ def _daily_triage_digest(params: dict[str, Any]) -> str: max_workstreams = int(params.get("max_workstreams", 12)) max_next_steps = int(params.get("max_next_steps", 8)) open_workstreams = _open_workstream_digest(summary, workplan_index, max_workstreams) + ranked_suggestions = summary.get("ranked_suggestions") or [] + if not isinstance(ranked_suggestions, list): + ranked_suggestions = [] + digest = { "generated_at": summary.get("generated_at"), "totals": summary.get("totals", {}), "open_workstreams": open_workstreams, "next_steps": [_safe_next_step(item) for item in next_steps[:max_next_steps]], + "ranked_suggestions": [ + _safe_ranked_suggestion(item) + for item in ranked_suggestions[: int(params.get("max_suggestions", 10))] + ], "inbox": { "unread_count": len(inbox), "samples": [_safe_inbox_item(item) for item in inbox[:3]], @@ -619,6 +627,19 @@ def _safe_next_step(item: dict[str, Any]) -> dict[str, Any]: } +def _safe_ranked_suggestion(item: dict[str, Any]) -> dict[str, Any]: + return { + "id": item.get("id"), + "title": _short_text(item.get("title", ""), 120), + "stage": item.get("stage"), + "domain_slug": item.get("domain_slug"), + "origin_ref": item.get("origin_ref"), + "relevance": item.get("relevance"), + "wsjf": item.get("wsjf"), + "last_requested_at": item.get("last_requested_at"), + } + + def _safe_inbox_item(item: dict[str, Any]) -> dict[str, Any]: return { "id": item.get("id"), diff --git a/tests/test_state_hub_context_resolver.py b/tests/test_state_hub_context_resolver.py index 0f8727e..0ff5367 100644 --- a/tests/test_state_hub_context_resolver.py +++ b/tests/test_state_hub_context_resolver.py @@ -486,6 +486,18 @@ def test_daily_triage_digest_is_curated_scalar_json(monkeypatch) -> None: "/state/summary": { "generated_at": "2026-05-19T05:20:00Z", "totals": {"tasks": {"todo": 4, "wait": 1}}, + "ranked_suggestions": [ + { + "id": "sug-1", + "title": "Issue-core ingestion API key path", + "stage": "suggestion", + "domain_slug": "custodian", + "origin_ref": "issue-core-ingestion-api-key", + "relevance": 2, + "wsjf": 4.7, + "last_requested_at": "2026-05-19T04:00:00Z", + } + ], "topics": [ { "slug": "custodian", @@ -594,3 +606,4 @@ def test_daily_triage_digest_is_curated_scalar_json(monkeypatch) -> None: assert digest["deterministic_scoring"]["future_mode"] == ( "code_score_high_gain_high_effort_candidates" ) + assert digest["ranked_suggestions"][0]["origin_ref"] == "issue-core-ingestion-api-key" diff --git a/workplans/ACTIVITY-WP-0008-weekly-coding-retro.md b/workplans/ACTIVITY-WP-0008-weekly-coding-retro.md index 794cb3d..5453dc6 100644 --- a/workplans/ACTIVITY-WP-0008-weekly-coding-retro.md +++ b/workplans/ACTIVITY-WP-0008-weekly-coding-retro.md @@ -4,11 +4,11 @@ type: workplan title: "Weekly Coding Retrospection schedule (Saturday evenings)" domain: custodian repo: activity-core -status: blocked +status: active owner: codex topic_slug: custodian created: "2026-06-07" -updated: "2026-06-17" +updated: "2026-07-03" state_hub_workstream_id: "7387fc50-1f2c-471a-9d85-bb085cbd0b63" --- @@ -107,3 +107,11 @@ specs because that weekly read model has zero suggestions. The schedule remains disabled until a non-empty weekly read model, or an explicit operator decision that a zero-suggestion dry-run is an acceptable enablement proof, confirms correct routing and no duplicate target tasks on re-run. + +2026-07-03 reevaluation: `AGENTIC-WP-0010` remains finished and the resolver +smoke still works, but State Hub still has only the 2026-06-07 weekly +`coding_retro` read model (`ec20ac1c`, `window.days=7`) with zero suggestions. +No newer weekly publish exists yet. This is a **time-controlled wait** on the +next Saturday 19:00 Europe/Berlin publish (or an operator decision to enable on +the zero-suggestion proof), not a hard blocker. Workplan status returns to +`active`; T03 stays `wait`. diff --git a/workplans/ACTIVITY-WP-0009-intent-gap-closure.md b/workplans/ACTIVITY-WP-0009-intent-gap-closure.md index 1eb0686..b573dfc 100644 --- a/workplans/ACTIVITY-WP-0009-intent-gap-closure.md +++ b/workplans/ACTIVITY-WP-0009-intent-gap-closure.md @@ -4,11 +4,11 @@ type: workplan title: "Intent gap closure" domain: custodian repo: activity-core -status: blocked +status: active owner: codex topic_slug: custodian created: "2026-06-16" -updated: "2026-06-18" +updated: "2026-07-03" state_hub_workstream_id: "d64cfbba-6da7-4737-afb9-866afa0e9cda" --- @@ -38,7 +38,7 @@ The preserved analysis lives in: ```task id: ACTIVITY-WP-0009-T01 -status: wait +status: done priority: high state_hub_task_id: "7012e4fd-2530-49b7-9c2f-1d949809a144" ``` @@ -73,6 +73,12 @@ fixture smoke passed. The remaining work is the activity-core / Railiance runtime reconciliation and daily-triage evidence collection path captured in `ACTIVITY-WP-0010`. +2026-07-03 closeout: `ACTIVITY-WP-0010` and `ACTIVITY-WP-0006-T03` now record +three consecutive clean scheduled daily triage runs (2026-06-28 through +2026-06-30, streak continuing through 2026-07-03) with matching State Hub +progress, working-memory notes, and calibration feedback. The scheduled-run trust +gap is closed. T01 is done. + ## Promote Issue-Core Task Emission Safely ```task @@ -100,6 +106,10 @@ Current wait reason: production Railiance currently uses null-sink audit mode; live issue-core credentials/access and duplicate-handling are not yet verified for this repo. +2026-07-03 reevaluation: this is an external-credential gate, not a workplan +blocker. Keep T02 at `wait` with `blocking_reason` describing the issue-core +approval path; the workplan returns to `active`. + ## Resolve Review-Required Contract Drift ```task @@ -231,7 +241,6 @@ Agent-actionable closure is complete for T03, T04, T05, and T06. Remaining waits: -- T01 waits on real scheduled daily triage run evidence. - T02 waits on issue-core production endpoint/credentials and duplicate-handling approval. diff --git a/workplans/ACTIVITY-WP-0010-daily-triage-llm-reconciliation.md b/workplans/ACTIVITY-WP-0010-daily-triage-llm-reconciliation.md index d385bfb..3e99006 100644 --- a/workplans/ACTIVITY-WP-0010-daily-triage-llm-reconciliation.md +++ b/workplans/ACTIVITY-WP-0010-daily-triage-llm-reconciliation.md @@ -4,11 +4,11 @@ type: workplan title: "Daily Triage LLM Reconciliation And Evidence" domain: custodian repo: activity-core -status: blocked +status: finished owner: codex topic_slug: custodian created: "2026-06-18" -updated: "2026-06-27" +updated: "2026-07-03" state_hub_workstream_id: "f2c73ac6-13f0-4005-82cc-76c7c9f9c8b9" --- @@ -135,7 +135,7 @@ Hub handoff message `9a074b7c-4b87-4e3c-a6bf-e1fe5580daa8` asks ```task id: ACTIVITY-WP-0010-T03 -status: wait +status: done priority: high state_hub_task_id: "10e0df77-c230-4a82-b720-23c66bd17c0a" ``` @@ -173,11 +173,17 @@ Done when: after the WP-0016 code/schema/runtime-prompt deployment due the 2026-06-26 and 2026-06-27 malformed-output failures. +2026-07-03 closeout: `ACTIVITY-WP-0016` is finished and scheduled +`daily_triage` progress from **2026-06-28 through 2026-07-03** all show +`output_validated=true` with working-memory notes. The `railiance-cluster` +inbox reply (`e360e38f`, 2026-07-01) confirms the live runtime, provider Secret +presence, and in-namespace fixture smoke. T03 is done. + ## Collect Three Clean Scheduled Runs ```task id: ACTIVITY-WP-0010-T04 -status: wait +status: done priority: high state_hub_task_id: "dc6b9482-cf43-4fc5-994b-dcd7dea47db7" ``` @@ -204,11 +210,17 @@ Done when: Railiance runtime prompt and max_tokens changes, run a live smoke, then restart the three-consecutive-scheduled-run gate from zero. +2026-07-03 closeout: post-`ACTIVITY-WP-0016` scheduled runs on **2026-06-28, +2026-06-29, and 2026-06-30** (`6a44d6dd`, `1dfb47c9`, `ac3d71a0`) are three +consecutive clean scheduled successes with matching State Hub progress and +working-memory notes. The streak continued through 2026-07-03. Calibration +feedback was already recorded when `ACTIVITY-WP-0006-T03` closed. T04 is done. + ## Close Handoff State ```task id: ACTIVITY-WP-0010-T05 -status: wait +status: done priority: medium state_hub_task_id: "ecc57e21-1716-4daa-aba6-d8a6d824e4ed" ``` @@ -223,3 +235,8 @@ Done when: - any temporary `needs_human` flags created for the llm-connect provider/config handoff are cleared or replaced by a narrower follow-up; - this workplan is marked `finished`. + +2026-07-03 closeout: `ACTIVITY-WP-0006-T03` already records the three-run +calibration evidence. `ACTIVITY-WP-0009-T01` is updated in the same pass. +No temporary `needs_human` flags remain for the llm-connect provider/config +handoff. T05 is done; this workplan is `finished`. diff --git a/workplans/ACTIVITY-WP-0015-adopt-statehub-beachhead-endpoint.md b/workplans/ACTIVITY-WP-0015-adopt-statehub-beachhead-endpoint.md index 2398848..75b8963 100644 --- a/workplans/ACTIVITY-WP-0015-adopt-statehub-beachhead-endpoint.md +++ b/workplans/ACTIVITY-WP-0015-adopt-statehub-beachhead-endpoint.md @@ -4,11 +4,11 @@ type: workplan title: "Adopt State Hub Beachhead Endpoint" domain: infotech repo: activity-core -status: blocked +status: active owner: claude topic_slug: activity-core created: "2026-06-24" -updated: "2026-06-24" +updated: "2026-07-03" state_hub_workstream_id: "bbc07f9e-9323-4b2b-b556-c33b37d0b228" --- @@ -19,10 +19,15 @@ Carries the **blocked remainder** of [[ACTIVITY-WP-0014]] T05. The in-repo half client-side adoption that depends on the state-hub-owned **beachhead** capability (per-machine read cache + write outbox) existing first. -**Blocked on:** the state-hub beachhead (proposal sent to the `state-hub` agent, +**Waiting on:** the state-hub beachhead (proposal sent to the `state-hub` agent, 2026-06-23). Do not build queue/cache logic in activity-core — see [[statehub-beachhead-principle]]. +2026-07-03 reevaluation: no state-hub-owned beachhead endpoint is available yet. +`CUST-WP-0054` now tracks a files-first dev beachhead, but activity-core client +adoption still depends on the state-hub capability shipping first. Both tasks +remain `wait` on external delivery; the workplan is `active`, not `blocked`. + ## Point STATE_HUB_URL at the beachhead ```task diff --git a/workplans/ACTIVITY-WP-0016-llm-output-robustness-trust-boundary.md b/workplans/ACTIVITY-WP-0016-llm-output-robustness-trust-boundary.md index f601013..ded096f 100644 --- a/workplans/ACTIVITY-WP-0016-llm-output-robustness-trust-boundary.md +++ b/workplans/ACTIVITY-WP-0016-llm-output-robustness-trust-boundary.md @@ -4,7 +4,7 @@ type: workplan title: "LLM Output Robustness & The Producer Trust Boundary" domain: custodian repo: activity-core -status: active +status: finished owner: codex topic_slug: custodian created: "2026-06-26" @@ -90,7 +90,7 @@ there are two sensible strategies — and they compose: ```task id: ACTIVITY-WP-0016-T01 -status: wait +status: cancel priority: high state_hub_task_id: "74fd16a5-4ea5-4dfe-8526-dfa27cf76138" ``` @@ -353,7 +353,7 @@ Done when: ```task id: ACTIVITY-WP-0016-T05 -status: progress +status: done priority: high state_hub_task_id: "c881500b-5459-4620-81c0-b176971e989f" ``` @@ -415,3 +415,20 @@ for the next 07:20 Berlin fire, then confirm State Hub `daily_triage` evidence i - **Boundary discipline:** keeps activity-core inside its SCOPE — this hardens the instruction-executor output contract; it does not move provider credentials, cluster reconciliation, or task lifecycle into this repo. + + +## Closure 2026-07-02 (RAIL-BS-WP-0008 live deploy) + +- T05 done: the robustness bundle (strict per-item schema + T03 quarantine + parser + bounded top-7/NDJSON runtime prompt, activity-core `7612112`) was + deployed to railiance01 and live-proven. A manually triggered daily-triage + run produced a clean schema-valid report with exactly 7 ranked + recommendations: State Hub event `24d2d321-c761-47f7-bf9e-7950a6253c21`, + `output_validated=true`, working memory written. Calibration re-entry: the + three-clean-run streak (WP-0006-T03 / WP-0010-T04) restarts from this run. +- T01 cancelled: the raw 2026-06-26 llm-connect response is unrecoverable + (stateless pod, no response store, log stream holds only 2 startup lines + since 2026-06-19). Root cause stands on the retained 4000-char preview and + break-at-char-5268 evidence: output exceeded the old ~1200-token budget and + truncated mid-JSON. The deployed mitigation (1800-token headroom, bounded + top-7, per-item recovery) addresses exactly that failure mode.