Ekho-Labs/ai-coworker · PR #44
MERGE · APPROVED
Pull request review dashboard

PR #44 — Typed intake policies with a research-only automation lane

The control plane previously accepted intake from exactly one repository (Ekho-Labs/infra-work). This PR declares intake repositories with typed capabilities via AICOWORKER_INTAKE_POLICIES: exactly one full human control surface, plus research-only intake repositories (Ekho-Labs/data-scraping-central) whose automation-armed kind:research issues run the paired research lane with no code-writing authority.

branch feat/research-intake-policies base main 72 files +1082 / −324 companion Ekho-Labs/data-monitoring-loop#36 · auto-arms QA/QC expansion issues

01 Overview

Intake stops being a single hard-coded repository and becomes a typed capability set. A repository is either the full human control surface or a research-only lane — and nothing else is accepted.

BEFORE

The control plane accepted intake from exactly one repository, Ekho-Labs/infra-work, configured through AICOWORKER_INTAKE_REPOSITORY. Every intake lookup was keyed by issue number alone.

THIS PR

AICOWORKER_INTAKE_POLICIES declares each intake repository with a typed capability: exactly one full human control surface, plus research-only repositories such as Ekho-Labs/data-scraping-central.

Automation-armed kind:research issues there run the paired research lane with no code-writing authority, and every intake identity becomes composite: (intake_repository, intake_issue_number).

72
files changed
+1082 / −324
lines
2
intake capabilities
3
independent gates
green
build · vet · test -race

02 End-to-end flow

A daily expansion loop arms a research issue; the controller runs it as research and only research. Solid edges carry events and data, dashed edges are rejection paths. Hover or focus any node to trace its edges.

event / data flow classification block policy rejection hover a node to trace · scroll for the full lane
data-monitoring-loop Airflow, daily
A1
expansion loop
finds coverage gap
A2
gh issue create
--label qa-qc, expansion, kind:research, agent:ready · authorized member PAT
GitHub
B1
issue created
in data-scraping-central
B2
issues.labeled
one webhook per creation label · verified against live capture
ai-coworker controller
C1
signed ingress
signed webhook ingress → durable delivery
C2
processor
policy lookup (research) · bot senders + comments on research intake dropped
C3
intake authorization
non-bot actor · triage+ on event repo · control-team member · research intake allows ONLY agent:ready / pause / stop
C4
claim
composite key (intake_repository, intake_issue_number) · paired budget reserved
C5
classify
kind:research required
C6
plan + validate
research manifest · research-only repos readable, never change targets
C7
dispatch paired arms
Claude + Codex
X1
run BLOCKED
kind:change on research intake → explicit diagnostic
publisher
D1
fence
composite intake identity + lease / generation / revision
D3
two research reports
posted as comments on the gap issue
D4
agent:handoff label
D2
policy gate
change work from non-full intake → UNAUTHORIZED
ONE WEBHOOK PER LABEL Creating an issue with four labels emits opened plus one issues.labeled event per label — behaviour verified against a live capture, not assumed.
CLASSIFICATION BLOCK A kind:change issue on a research-only intake repository is blocked at classification, before any planning, with an explicit diagnostic.
PUBLISHER RE-CHECK The publisher independently rejects change work whose intake repository lacks full capability, even if it reached the queue.

03 Three independent gates

Research-only intake is not enforced in one place. Three layers reject independently, so no single missed check grants a research repository code-writing authority.

  • An unknown repository has zero policy, and zero policy is denied.
  • A research policy denies comment wake events and every control label except agent:ready, agent:pause and agent:stop.
  • The actor must be non-bot, hold triage or higher on the event repository, and be a member of @Ekho-Labs/infrastructure.
  • After classification, a non-research kind coming from a research-only intake repository blocks the run before any planning.

"research-only intake repository: only kind:research work is accepted there"

  • An independent re-check: a change job whose manifest intake repository lacks full capability is rejected with unauthorized, even if it reached the queue.
  • Research-only repositories are also excluded from the publication allowlist (TargetProfile.Allowed=false) and rejected as change targets by planner validation.
  • Each agent:ready claims one paid paired generation (~$200 cap, Claude + Codex arms).
  • Org daily spend / token / run caps and the organization-wide circuit breaker still apply, and are shared across intake repositories.

04 Change map

72 files, grouped by the job they do. Each group expands to the per-file reason it changed.

8 groups
internal/intake/policy.goNEW
Policy is full or research; ParsePolicies is fail-closed; the research control-label whitelist lives here.
internal/controller/config.go
AICOWORKER_INTAKE_POLICIES replaces AICOWORKER_INTAKE_REPOSITORY, defaulting to Ekho-Labs/infra-work:full.
cmd/controller/{main,assemble}.go cmd/publisher/main.go
Policy wiring; budget organization and circuit reset stay bound to the single full repository.
internal/ledger/migrations/000006_intake_repository.{up,down}.sqlNEW
Adds an intake_repository column to intake_issues, received_events, runs and budget_reservations; composite primary keys and foreign keys; backfills 'Ekho-Labs/infra-work', then drops the defaults.
internal/ledger/{claims,store,admission,budgets}.go internal/controller/{runtime_store,reconcile_store,fence,approval}.go internal/publisher/ledger.go
Every intake lookup now binds repository and number; contracts.IntakeRef is introduced.
internal/intake/processor.go
Accepts any configured policy repository; drops bot senders and comments on research intake.
internal/intake/authorization.go
Per-event-repository actor access, plus policy-based restrictions.
internal/controller/controller.go
The research classification gate; approval and circuit reset are reachable only from full intake — defense in depth; a per-run intake repository for snapshots, fences and halt checks.
internal/inventory/service.go
Full intake is excluded from targets; research intake is included with ResearchOnly=true.
internal/config/config.go
RepositoryProfile.ResearchOnly.
internal/planner/validation.go
Change manifests cannot target research-only repositories.
internal/controller/arms.go
The publication allowlist is built from !ResearchOnly.
internal/publisher/publisher.go
Independent rejection of change work from non-full intake, plus IntakePolicies configuration.
testdata/webhooks/issues_labeled_agent_ready_research_intake.json
Mirrors the live 2026-08-18 capture: creation labels emit per-label issues.labeled events.
test/integration/research_intake_test.goNEW
An armed research-intake issue runs the research lane through to handoff; a kind:change issue there blocks.
ledger test
Proves the same issue number in two intake repositories claims independent runs.
policy / authorization / processor / publisher / inventory / planner
Unit tests across each gate and each policy-aware component.
README.md docs/using-the-agent.md docs/operations.md
AICOWORKER_INTAKE_POLICIES documented.

05 Verification & verdict

TEST SUITE

go build, go vet and a full go test -race ./... are green, including the Docker-backed Postgres integration suite.

LIVE WEBHOOK BEHAVIOUR

Verified empirically against a scratch repository capture (2026-08-18): gh issue create --label fires opened plus one labeled event per label.

MANUAL SMOKE TEST

A controller built against Postgres with AICOWORKER_INTAKE_POLICIES accepted and qualified a signed research-intake webhook; denial came only at the live GitHub credential check.

APPROVED FOR MERGE
Three independent gates, a composite intake identity in the ledger, and empirical verification of the webhook behaviour the automation lane depends on.

Deploy prerequisites

  • REQUIRED AICOWORKER_INTAKE_POLICIES set on the controller and the publisher (infrastructure-tf-kubernetes).
  • DONE kind:research and agent:ready labels exist in data-scraping-central.
  • CONFIRMED GitHub App installed on data-scraping-central, confirmed by the requester.
  • REQUIRED Canary one issue before enabling the daily auto-arm.