git:20260729.d9f5272 to git:20260730.51b7e7b

42 added, 256 removed. Audit A to A.

---
- name: telegram-task-card-manual
+ name: telegram-task-card-projection-notice
description: |
- Nested telegram-mcp-manual reference for the programmable Telegram Task Card
- (`task_card` tool). Read this when a task needs a truthful, task-specific
- watcher: inspect the actual task and producer evidence, design a small
- renderer, and use the start | inspect | retry | stop lifecycle without
- prescribing a fixed layout or data source.
- last_changed_at: 2026-07-19T00:00:00Z
+ Shipped retained-legacy/projection notice for Telegram Task Card files. The
+ public `task_card` tool is intrinsic and documented at
+ src/lingtai/tools/task_card/manual/SKILL.md; Telegram only projects the
+ intrinsic taskcard/status + taskcard/taskcard.md artifact read-only.
+ last_changed_at: 2026-07-29T00:00:00Z
related_files:
- src/lingtai/mcp_servers/telegram/SKILL.md
- - src/lingtai/mcp_servers/telegram/task_card/controller.py
- - src/lingtai/mcp_servers/telegram/task_card/interface.py
- src/lingtai/mcp_servers/telegram/task_card/ANATOMY.md
+ - src/lingtai/mcp_servers/telegram/task_card/CONTRACT.md
+ - src/lingtai/tools/task_card/manual/SKILL.md
maintenance: |
- Tracks the Telegram Task Card renderer/controller behavior and task-specific
- watcher method it documents; update when that integration or its guidance changes.
+ Keep this shipped subpackage manual aligned with the intrinsic Task Card
+ owner. Do not reintroduce the retired Telegram controller schema, endpoint,
+ ceiling, or lifecycle as active behavior.
---
- # Programmable Telegram Task Card — task-specific watcher manual
-
- The `task_card` tool lets you surface a truthful, latest-reported snapshot on
- Telegram's one tracked resident Task Card. You supply a small Python renderer
- under your working directory. The controller runs it on an interval and forwards
- only its validated data to the programmable slot beside Telegram's automatic
- activity slot. The renderer is a view of evidence you keep current; it is not an
- autonomous progress monitor.
-
- This manual teaches a method, not a renderer template. The task, its producer, and
- the evidence they expose determine the watcher's fields, update cadence, and
- presentation. Do not copy a fixed layout or assume one data source works for every
- job.
-
- ## When to reach for a watcher
-
- During a Telegram-originated turn, use a watcher when a meaningful task will run
- long enough that a person would otherwise see a silent gap—for example, while you
- wait for a background job, daemon, build, or other producer. First inspect the
- actual task and producer evidence. A quick or invisible operation does not need a
- watcher, and collecting updates on every turn is not a goal.
-
- The watcher should make the current task understandable without pretending to know
- more than its producer reports. Choose only facts that demonstrate movement or an
- honest lack of movement, and say when a fact is unavailable.
-
- ## The watcher information contract
-
- For the current task, design a concise frame that communicates these facts when
- reliably available:
-
- 1. **Purpose** — what the watcher is for and what outcome the task is pursuing.
- 2. **Time lapse** — how long the task or current stage has been running, based on a
- reliable recorded start or stage time. If that evidence is unavailable, say
- `unavailable`; do not infer duration from renderer refreshes.
- 3. **Recent activity / last meaningful movement** — what actually changed most
- recently, where that evidence came from, and when it happened or was last
- checked. A refreshed card is not itself activity.
- 4. **Token usage** — show usage only from a trustworthy producer/ledger. If it is
- unavailable, say `unavailable` rather than guessing or substituting a counter.
- 5. **Current state, next gate, and blocker** — name the state the producer supports,
- the next evidence-based transition or review gate, and any blocker. Do not call
- work `running`, `done`, or `healthy` without evidence; say `unknown` or
- `unavailable` when that is the truth.
- 6. **Feedback and improvement** — after meaningful real use, ask whether the
- watcher helped (see **Feedback and reuse loop**).
-
- These are information requirements, not a visual template. The frame may use any
- layout that keeps the facts legible and bounded. Do not add decorative fields just
- to make a card look active.
-
- ## Inspect the task and producer before writing the renderer
-
- Before choosing a source, identify:
-
- - the task's purpose, stages, gates, and known blockers;
- - the producer that owns state (job, daemon, build system, queue, or another
- workflow) and its documented read/check surface;
- - the authoritative start/activity/token fields, their units, and their freshness;
- - what the producer does **not** expose, so the watcher can state `unavailable`;
- - how you will record a small, orchestrator-owned snapshot after launch and after
- each meaningful producer event, justified check, or terminal result.
-
- Prefer the producer's documented status/check result or an orchestrator-owned
- snapshot over private tool internals. Never let a passive renderer consume a
- one-shot poll, reach into versioned forensic state, or treat a wall-clock redraw as
- proof of progress. If the task has no reliable activity or token signal, report
- that limitation plainly and choose another truthful signal rather than inventing
- one. Write snapshots atomically when the orchestrator owns them, so a renderer
- never mistakes a half-written update for a real state.
-
- ## Renderer contract
-
- A renderer is an ordinary Python file inside the agent working directory. Each run
- must exit `0` and print exactly one JSON object to stdout:
-
- - `title` is a string (optional);
- - `lines` is an array of at most 20 strings (optional);
- - `footer` is a string (optional);
- - at least one of those values must be non-empty.
-
- Nonzero exit, timeout, empty or multi-object output, a non-object, or wrong field
- types are handled failures. The controller uses `sys.executable`, the working
- directory as `cwd`, a per-run timeout, and symlink-resolved containment for
- `renderer_path`. Telegram receives only the validated card data, never renderer
- code. Keep secrets, credentials, raw logs, and unbounded output out of the frame;
- the manager also redacts at its boundary but cannot make an unsafe source safe.
-
- ## Safe custom renderer example
-
- The following is a runnable, stdlib-only example—not a required data source or
- layout. It reads an orchestrator-owned `task_snapshot.json`; replace that source,
- labels, and arrangement only after inspecting the actual producer. The example
- clips strings, accepts an object only, uses `unavailable` for absent facts, and
- prints exactly one bounded Task Card object. Keep the renderer in your working
- directory, and keep secrets and raw logs out of its snapshot.
-
- ```python
- #!/usr/bin/env python3
- import json
- from pathlib import Path
-
- STATE = Path("task_snapshot.json") # example only; choose evidence for this task
- MAX_SNAPSHOT_BYTES = 16_384
- MAX_TEXT = 160
-
-
- def text(value):
- if not isinstance(value, str):
- return "unavailable"
- value = "".join(ch for ch in value if ch == " " or ch.isprintable()).strip()
- if not value:
- return "unavailable"
- return value[: MAX_TEXT - 1] + "…" if len(value) >= MAX_TEXT else value
-
-
- def load_snapshot():
- try:
- with STATE.open("rb") as stream:
- raw = stream.read(MAX_SNAPSHOT_BYTES + 1)
- if len(raw) > MAX_SNAPSHOT_BYTES:
- return {}
- data = json.loads(raw.decode("utf-8"))
- except (OSError, UnicodeDecodeError, ValueError, RecursionError):
- return {}
- return data if isinstance(data, dict) else {}
-
-
- def main():
- data = load_snapshot()
-
- labels = (
- ("purpose", "purpose"),
- ("time lapse", "time_lapse"),
- ("last movement", "last_movement"),
- ("token usage", "token_usage"),
- ("state", "state"),
- ("next gate", "next_gate"),
- ("blocker", "blocker"),
- )
- lines = [f"{label}: {text(data.get(key))}" for label, key in labels]
- card = {
- "title": text(data.get("title")) if data.get("title") else "Task watcher",
- "lines": lines[:20],
- "footer": "snapshot reported by orchestrator",
- }
- print(json.dumps(card, ensure_ascii=False))
-
-
- if __name__ == "__main__":
- main()
- ```
-
- A real renderer may use another safe source, calculate a duration from an
- authoritative timestamp, or omit a field that has no truthful value. It must still
- print exactly one JSON object and avoid claiming progress that the producer did
- not report. Start it with the controller only after adapting and testing it:
-
- ```json
- {"action": "start", "renderer_path": "watcher.py", "interval_s": 5, "timeout_s": 10}
- ```
-
- ## Lifecycle: `start` | `inspect` | `retry` | `stop`
-
- - **`start`** validates the path and runs the renderer once synchronously. A bad
- first run returns an error and creates no watch. On an accepted first frame it
- projects the programmable slot and returns a `watch_id`; the watcher then runs
- at the requested interval (minimum 1 second, default 5). Each run uses
- `timeout_s` as its renderer timeout (minimum 0.1 second, default 10). A validated
- visible frame whose durable resident-id write failed is surfaced as a retryable partial,
- not mislabeled as full success. An indeterminate or malformed transport id is a
- plain error and no unknown card is adopted.
- - **`inspect`** reports `watching`, `error`, `stopping`, or `stop_failed`, including
- the last valid frame, its UTC ISO-8601 `last_valid_frame_at`, and the current
- error. A failed attempt does not overwrite the last valid frame or timestamp.
- - **`retry`** runs the renderer immediately for an active watch. After `stop` has
- been requested it continues only the stop path; it never starts another render.
- - **`stop`** quiesces the watcher and clears only the programmable slot. It returns
- `stopped` and removes the watch only after the watcher thread is quiescent and
- the backend accepts the clear. A live thread or transient clear failure returns
- a retryable `stop_failed` and retains the watch; call the same stop again. A late
- result after stop is dropped and compensated if it could have landed. Renderer
- files are never deleted, and the automatic slot is not disturbed.
-
- The Telegram manager remains the single owner of the tracked resident, composition,
- transport, persistence, and the independent automatic slot. `/taskcard off` hides
- both slots at presentation time while everything here keeps running — see
- **TASKCARD STATE** in the parent [`telegram` manual](../SKILL.md).
-
- ## Terminal cleanup and fail-loud behavior
-
- A passive renderer cannot stop itself. When the producer reaches a terminal state,
- record that terminal evidence and immediately call
- `task_card(action="stop", watch_id="<watch_id>")`. Terminal evidence must still
- distinguish success, failure, cancellation, timeout, or an unavailable outcome
- according to the producer's contract. Do not restart or duplicate the watch, and
- do not leave a completed watcher resident merely because its card can still
- refresh.
-
- After a handle exists, a renderer or backend failure preserves the last valid frame
- and emits one deduplicated, fail-loud `task_card.error` wake per failure episode,
- then one recovery wake after the next good frame. Use `inspect` to read the safe
- error and `retry` after correcting the producer snapshot or renderer. Raw renderer
- output and secrets never belong in a wake.
-
- ## Feedback and reuse loop
-
- After a meaningful real use-cycle or stage boundary, ask the user a focused question,
- for example: “Did this watcher help you understand the task, and what should change?”
- Do not ask every turn or turn feedback collection into ritual or harassment. First
- adapt this watcher to the answer—its facts, freshness, wording, or noise level—then
- consider whether the lesson is genuinely reusable. Deposit only that reusable,
- source-grounded method as a skill; do not turn a one-off task layout into a product
- contract or a fixed template.
-
- ## Why these boundaries exist
+ # Telegram Task Card Projection Notice
- A watcher is useful when it makes producer evidence legible during a meaningful wait.
- It is harmful when a static renderer, guessed token count, or redraw timestamp
- creates the appearance of movement. The controller therefore owns validation,
- confinement, lifecycle, and fail-loud recovery, while the Agent owns judgment about
- the task, evidence source, facts to show, and how to improve the current watcher.
+ This shipped file is retained so the historical Telegram `task_card` subpackage
+ still has packaged guidance beside its governed Anatomy/Contract documents. It
+ is **not** the active public Task Card manual.
- The paired [`CONTRACT.md`](CONTRACT.md) defines the stable interface and behavior
- promises; this manual defines how an Agent should design and operate a watcher
- without weakening those promises. The paired [`ANATOMY.md`](ANATOMY.md) maps the
- controller, resident, transport, and producer connections.
+ The authoritative public capability owner is
+ [`src/lingtai/tools/task_card`](../../../tools/task_card/ANATOMY.md), and its
+ model-facing instructions live at
+ [`src/lingtai/tools/task_card/manual/SKILL.md`](../../../tools/task_card/manual/SKILL.md).
+ Use that intrinsic manual for renderer authoring, the
+ `start | inspect | retry | stop | manual` actions, refresh limits, failure
+ handling, and terminal cleanup.
- ## Public LTP-v2 invocation and refresh fuse
+ Current contract summary:
- The public tool is `task_card` (the Telegram command and preference remain
- spelled `taskcard`). Use the strict envelope with required `action`, `input`, and
- `reasoning`, plus optional root `summarize`; `input` is an action-owned closed
- object. Actions are exactly `start`, `inspect`, `retry`, `stop`, and `manual`.
- `manual` takes `{}` and is owned by this packaged manual. Do not send the retired
- flat shape, `_reasoning`, an alias, or Telegram action fields to this family.
+ - A renderer is a Python file inside the agent working directory.
+ - Each successful renderer run exits `0` and prints a nonempty full
+ Markdown/text Task Card body to stdout.
+ - The intrinsic producer atomically writes that body to
+ `<workdir>/taskcard/taskcard.md`, then writes exact `active` to
+ `<workdir>/taskcard/status`.
+ - `stop` and agent shutdown write exact `inactive`; the last body remains on
+ disk.
+ - At most one intrinsic-owned watch may be active per agent.
+ - Telegram is a read-only projector for the intrinsic artifact. It polls
+ `taskcard/status` and `taskcard/taskcard.md`, projects exact active +
+ nonempty bodies into the resident programmable slot, preserves the automatic
+ event-journal slot, and treats missing/invalid/inactive/unchanged producer
+ state as a no-op.
- `start.input.max_refreshes` is optional and can only lower the agent-wide fuse.
- The configured value is read from `<workdir>/telegram/taskcard.json`; missing or
- invalid state defaults to 1000, and there is no null/unlimited form. Initial
- creation does not consume a refresh. Every later interval or `retry` attempt
- consumes one count whether rendering or Telegram projection succeeds or fails.
- Results expose `refreshes_used`, `max_refreshes`, `refreshes_remaining`, and
- `stop_reason`. On `stop_reason=max_refreshes`, the controller stops safely,
- clears only the programmable slot, and sends one content-safe normal-priority
- `task_card.limit` notice. Refresh/reinspect the underlying task state before
- starting a new watch, and start anew only if it is useful. Never place renderer
- output, paths, credentials, or provider errors in that notice. If finalization
- fails, retry `stop`/`retry`; the handle remains `stop_failed` and the interval
- loop cannot restart or hammer the backend.
+ Do not use this retained Telegram package as an active schema, endpoint,
+ controller lifecycle, JSON-card renderer contract, private reverse-MCP route, or
+ refresh-ceiling source. Those were the retired ownership path and are kept only
+ as historical compatibility code while the current product surface lives in the
+ intrinsic tools layer.