TASK
SERVICE CONTRACT · VIEW: GOV
Constraints
MUST: Discover task types via ## TASK Contract in service CANON.md files (not a parallel tree)
MUST: Walk SERVICES/*/CANON.md and CAMPAIGNS/*/CANON.md for ## TASK Contract sections
MUST: Parse TASK Contract tables for task, schedule, scope, write, model fields
MUST: Extract ### Prompt section beneath each TASK Contract table
MUST: Compile discovered tasks to FLEET trigger prompt + TRIGGERS.json registry
MUST: Reconcile compiled triggers against live Anthropic API state
MUST: Warn on orphan triggers (runtime exists, GOV declaration missing)
MUST: Carry _generated marker on compiled outputs (TRIGGERS.json, INTEL.md)
MUST: Budget-control frequency (minimum 1 hour per agent, platform limit)
MUST: All agents read-only observers unless TASK Contract explicitly declares write scope
MUST: All agent prompts reference their service CANON.md as governance contract
MUST: Ledger every agent execution to TIMELINE via TASK lane
MUST: Deploy as 1 FLEET trigger (Option B) — 1 trigger, N task phases
MUST NOT: Maintain a parallel TASK/*/CANON.md tree (services own their monitoring)
MUST NOT: Hardcode trigger IDs in GOV (IDs are runtime artifacts)
MUST NOT: Allow agents to modify files outside their declared write scope
MUST NOT: Allow agents to post, comment, or engage on external platforms (unless declared)
MUST NOT: Store API credentials in GOV surfaces
TASK Contract Schema
Each service declares ## TASK Contract in its CANON.md (same pattern as TIMELINE Contract). The compiler discovers tasks by walking the service tree.
## TASK Contract
| Field | Value |
|-------|-------|
| task | {TASK_NAME} |
| schedule | {cron expression, minimum hourly} |
| scope | {files or URLs this task monitors} |
| write | {output path, typically SERVICES/TASK/_data/{TASK_NAME}.json} |
| model | claude-haiku-4-5 |
### Prompt
{The monitoring prompt — what the agent does each run}
A service may declare multiple TASK Contracts (e.g., CAMPAIGN declares both CAMPAIGN and HN-SCOUT). Instances (e.g., CAMPAIGNS/LINKEDIN) inherit the service contract and specialize with their own TASK Contract.
Runtime
Two execution paths coexist. GitHub Actions is the primary (active, hourly). The Anthropic RemoteTrigger is secondary (disabled, pending schedule reconciliation).
| Component | Location | Status |
|---|---|---|
| GOV contracts | hadleylab-canonic/SERVICES/*/CANON.md (## TASK Contract) | LIVE |
| FLEET orchestrator | SERVICES/TASK/FLEET/CANON.md | LIVE |
| GitHub Actions | .github/workflows/fleet.yml (hourly at :07) | PRIMARY |
| RemoteTrigger | trig_011DvxkZiBk1MnrQU1Kzh5E8 (daily 18:57 UTC) | DISABLED |
| Bash scripts | SERVICES/TASK/_scripts/*.sh | 8 compiled |
| Results | SERVICES/TASK/_data/*.json | Committed on change |
| Alerts | Non-zero exit code → ALERT in commit message | LIVE |
VAULT Provisioning Contract
Tasks that require VAULT data (MINT-READ, LINKEDIN-EMIT) run in GitHub Actions where ~/.canonic/vault/ does not exist. The fleet workflow provisions VAULT at runtime via the governed pattern below.
MUST: Provision VAULT via sparse-checkout of canonic-canonic/.canonic (private, GOV_TOKEN)
MUST: Only checkout vault/USERS/*/TIMELINE.jsonl (timelines) and vault/linkedin/ (OAuth)
MUST: Never checkout vault/USERS/*/KEY.priv (private keys stay out of CI)
MUST: Credentials (OAuth tokens) that cannot live in GOV go in GitHub Secrets
MUST: LinkedIn token stored as LINKEDIN_TOKEN_JSON secret (overrides repo copy)
MUST: VAULT data is always live from the runtime repo (not static snapshots)
MUST: vault-export runs on local machine before push to keep runtime repo current
MUST NOT: Store private keys, passphrases, or API secrets in GOV surfaces
MUST NOT: Store raw VAULT data in GitHub Secrets (stale, size-limited, unchainable)
MUST NOT: Chunk secrets across multiple keys (fragile, no integrity guarantee)
Provisioning Flow
1. fleet.yml checks out canonic-canonic/.canonic with sparse-checkout
(GOV_TOKEN provides access to private runtime repo)
2. Sparse filter: vault/USERS/*/TIMELINE.jsonl, vault/WALLET.json
(no KEY.priv, no identity.json, no WITNESSES, no EPOCHS)
3. LinkedIn token injected from LINKEDIN_TOKEN_JSON secret
(overrides repo copy — secrets rotate independently of git)
4. ~/.canonic/vault/ symlinked or copied to runner HOME
5. MINT-READ reads timelines, LINKEDIN-EMIT reads token
6. Results committed to hadleylab-canonic (SERVICES/TASK/_data/)
Secrets
| Secret | Purpose | Rotation |
|---|---|---|
| GOV_TOKEN | GitHub PAT — checkout private canonic-canonic/.canonic | Annual |
| ANTHROPIC_API_KEY | Claude API for FLEET agent execution | As needed |
| LINKEDIN_TOKEN_JSON | LinkedIn OAuth token (access_token, refresh_token) | On expiry |
Keeping VAULT Current
The runtime repo (canonic-canonic/.canonic) must contain current VAULT data. Every vault command that mutates timelines (mint, spend, transfer, close) must be followed by a push to the runtime repo. The vault-export script handles this: it copies timeline-safe data (no keys) and pushes.
vault mint ... → VAULT/USERS/*/TIMELINE.jsonl updated locally
vault-export → copies timelines to ~/.canonic, commits, pushes to GitHub
fleet.yml → sparse-checkout gets latest timelines from GitHub
Discovery
SERVICES/*/CANON.md → parse ## TASK Contract tables → task list
CAMPAIGNS/*/CANON.md → parse ## TASK Contract tables → append
(future: TALKS/*/CANON.md, USERS/*/CANON.md — any scope can declare a TASK)
→ Claude compiles bash scripts once
→ hadleylab-canonic/fleet/scripts/*.sh (_generated, governed)
→ GitHub Actions cron (free)
→ fleet/_data/*.json (results, committed on change)
Deployed Tasks
14 tasks compiled (14 active, VAULT provisioned via runtime sparse-checkout)
| Service | Task | Schedule | Source | Status |
|---|---|---|---|---|
| TASK | FLEET | hourly | SERVICES/TASK/FLEET/CANON.md | ACTIVE |
| BUILD | BUILD | hourly | SERVICES/BUILD/CANON.md | ACTIVE |
| CAMPAIGN | CAMPAIGN | hourly | SERVICES/CAMPAIGN/CANON.md | ACTIVE |
| CAMPAIGN | HN-SCOUT | every 2h | SERVICES/CAMPAIGN/CANON.md | ACTIVE |
| CITATION | CITATIONS | weekly Mon | SERVICES/CITATION/CANON.md | ACTIVE |
| COIN | MINT-READ | hourly | SERVICES/COIN/CANON.md | ACTIVE (VAULT provisioned) |
| GRANT | GRANTS | daily | SERVICES/GRANT/CANON.md | ACTIVE |
| BLOG | LINKS | daily | SERVICES/BLOG/CANON.md | ACTIVE |
| SEO | SEO | daily | SERVICES/SEO/CANON.md | ACTIVE |
| UPTIME | UPTIME | hourly | SERVICES/UPTIME/CANON.md | ACTIVE |
| CAMPAIGN | LINKEDIN-EMIT | daily 10 UTC | CAMPAIGNS/LINKEDIN/CANON.md | ACTIVE (VAULT provisioned) |
| CAMPAIGN | SUBSTACK-SYNC | daily 11 UTC | CAMPAIGNS/SUBSTACK/CANON.md | ACTIVE |
| CAMPAIGN | HN-EMIT | weekday 14 UTC | CAMPAIGNS/HACKERNEWS/CANON.md | ACTIVE |
| CAMPAIGN | CAMPAIGN MONITOR | hourly | ~/.canonic/bin/build-campaigns-metrics | ACTIVE |
TIMELINE Contract
| Field | Value |
|---|---|
| lane | TASK |
| event_types | POLL, ALERT, SLA_BREACH, AGENT_FAIL |
| source | SERVICES/TASK/_data/*.json |
| adapter | read_task_lane() |
| primitive | INTEL |
| auth | PRIVATE |
| color | #94a3b8 |
| icon | ⚙ |
| label | Task |
| *TASK | CANON | SERVICES* |