DataTalksClub / DataTalksClub/dataops
Ship approved conversational todo creation
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 2
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Description
Ship approved conversational todo creation
Status: source implementation, independent verification, PM acceptance, commits, and original default-off deployment are complete; accepted #179/#168 canonical Task/Card follow-ups are committed on local main but unpushed/unshipped, and the only remaining #126 outcome is the HUMAN text/todo subset owned by #128 after #166/#182
Tags: enhancement, assistant, work-engine, backend, infra, testing, data, human, P1
Parent: #122
Depends on: #121/#124/#125 source and default-off prerequisites are satisfied; accepted #179 commit 02ee455efad09865ecdbd578e940c59e0bf7dc36 and #168 commit 41dca9e3748fe627c195f48a7f97ae203b343f60 define the canonical Task version/history and Card lifecycle contract and are local-only; live evidence waits for #166 A/B/C → #182 final preflight → D → cleanup and one ordinary all-off deploy
Blocks: live-acceptance closure only; #128 owns the integrated rollout and #126 is not a source blocker
Next owner: the #166 orchestrator/credentialed operator through the reviewed storage sequence; then the authorized HUMAN Telegram/provider owner with On-Call through #128; PM closes #126 after the bounded sanitized todo cross-post passes
Resume condition: #166/#182 record accepted sandbox outcomes, canonical writers reopen on the final empty Tasks schema with the accepted #179/#168 commits, temporary phase controls are deleted, ordinary push-triggered OIDC deployment is restored, and one normal main all-off run succeeds; then #128 may run its Text + todo stage
Architecture: _docs/CONVERSATIONAL_AGENT_PLUGIN_ARCHITECTURE.md at 4e5402c
Current review state
c47c5258a08b29c7c5ea6e797149c8a6619bbe71and SAM portability follow-upc8d153cf1ddb07acca6846182b4f65af9338b6ffare accepted ancestors oforigin/main. The static runtime registry/plugin, strict proposal, capability-limited writer, recovery, result delivery, tests, SAM resources, and default-off controls are source-complete.- #128 rollout commit
9595361a0546a52a5cb90ace7df0bbdeb524866ais also integrated. Normal OIDC runs 31545689094 and 31711997388 reached successful all-off deploy/seed/smoke outcomes. This proves the dark path and fail-closed configuration; it is not a live Telegram, model, approval, or Task-write result. - Accepted #179 commit
02ee455efad09865ecdbd578e940c59e0bf7dc36makes every new todo Task canonical atversion: 1withtaskHistory: []. Accepted #168 commit41dca9e3748fe627c195f48a7f97ae203b343f60keeps the todo writer and portable contract inside the strict canonical Task/Card lifecycle. Both are ancestors of current localmainbut absent fromorigin/main; neither is shipped or active in the sandbox. - No live Telegram todo journey, z.ai invocation, approval, Task creation, duplicate-delivery proof, or rollback has run.
#126 closure subset delegated to #128
After #166 repaired A/B/C → #182 final no-replacement Cards preflight → reviewed D → cleanup and one following ordinary all-off deployment:
- Enable only #128 Text + todo through one normal OIDC stack update: Telegram ingress on, execution on, plugins exactly
todo, Typefully external execution off, voice off, photo off. - One linked active operator in a private Telegram chat sends an ordinary todo request, answers one missing/ambiguous-field clarification, requests one revision, and approves the exact current Berlin date-only preview. No Task may exist before that approval.
- Prove exactly one actor-owned Task and one private result. The Task must match the approved description/date and fixed
status=todo,source=conversational-agent, actor ownership, safe assistant provenance,version=1, andtaskHistory=[]; it must read cleanly under the accepted #179/#168 canonical contract. - Repeat the approval/delivery path and prove convergence on the same Task/result with no second Task. Send
/todoand prove static guidance with no proposal, provider call, writer call, or Task. - Cross-post only sanitized IDs/hashes, counts, states, and timestamps to #126. Do not post chat text, Telegram identity, secret/provider configuration, prompt/completion, or task contents beyond a synthetic/sanitized field-match statement.
This bounded text/todo proof is the only remaining #126 criterion. Live voice/photo, Typefully preview/effect, unrelated alarm canaries, and completion of the full #128 rollback matrix are not #126 closure requirements. A failed stage stops progression under #128; it does not authorize replay of an unknown effect, queue purge, Task deletion, manual Lambda patch, migration, import, export, or restore.
Product decisions
This is the first complete production mutation through the conversational framework. It creates exactly one ordinary todo task in the authenticated actor's own scope after an exact preview and core approval.
Keep the first todo schema deliberately small:
description: required normalized plain text, 1–500 characters
date: required valid YYYY-MM-DD calendar date
The resulting task always has status=todo, assigneeId=<authenticated DataOps user>, createdBy=<same user>, and source=conversational-agent. The plugin cannot choose another assignee, status, owner, bundle, recurring rule, time, reminder, waiting state, tags, links, documents, proof fields, or arbitrary task properties.
DataOps operational dates use Europe/Berlin for this MVP. The runtime gives the model the current Berlin calendar date. Relative dates may be resolved to an exact date only when unambiguous; the exact date and timezone are shown in the preview. If the date is missing or ambiguous, ask. If a request contains a specific time, explain that this MVP stores a date only and ask the user to confirm the date-only todo instead of silently dropping the time.
There is no /todo mutation command. /todo with or without arguments returns a short static instruction to describe the task in an ordinary private message and invokes neither the model nor task writer.
Do not route execution through the generic POST /api/tasks handler or the current unrestricted createTask(client, Record<string, unknown>) function. Add a narrow actor-owned todo writer used only by the durable execution worker. Existing task API behavior remains compatible.
An internal DynamoDB task write is recoverable by deterministic identity and conditional creation. If the task commit succeeds but the worker loses its response, do not delete or compensate the task; recovery returns the same task and completes the original attempt. This is safer and simpler than an automatic rollback that could remove a visible task.
Operator journey
-
A linked, active user sends an ordinary message in a Telegram private chat, for example: “Remind me to follow up with Jane next Tuesday.”
-
The #121 adapter records the event and passes text—or already confirmed voice/photo-derived text—to the #125 runtime.
-
The runtime loads the registered
todoplugin in turn one and invokes its strictpropose_createaction in the separate turn two. -
The plugin asks only for missing or ambiguous description/date information. It never creates a task during clarification.
-
The bot presents one normalized todo:
Todo Task: Follow up with Jane Date: 2026-08-04 (Europe/Berlin, date only) Assignee: You Status: Todo -
Core controls are Approve todo, Request changes, Cancel proposal, and Discard draft.
-
Request changes keeps the draft, gathers the requested correction, supersedes the old proposal/presentation, and shows a complete new preview. An old approval button cannot approve the revision.
-
Approve todo uses #124's atomic claim and returns
execution_pending. No task is written in the Telegram webhook/model request. -
The #124 worker invokes only the todo writer. On success, the bot reports one created todo with its task ID, normalized description, and exact date. Duplicate taps/deliveries return the stored result.
-
A proved pre-write failure says no todo was created and allows a fresh proposal. An uncertain internal write is reconciled by deterministic task identity/strong lookup and never creates a second task.
Ordinary conversation remains the interface. A detached “yes”, a new message after proposal expiry, a group message, or an unconfirmed voice/photo derivation is not approval.
Todo plugin
Register one trusted static plugin in #125's registry:
id: todo
action: propose_create
effect: proposal
core permission: todo:create:self
delivery/recovery: deterministic idempotent create
allowed roles: admin, operator
allowed channel: telegram_private
The production action schema uses additionalProperties: false and accepts exactly description and date. Timezone, actor/assignee, status, source, proposal identity, and execution identity come from trusted core state rather than model input.
The validator must:
- normalize description to Unicode NFKC, trim it, collapse ordinary whitespace, and reject control/zero-width characters, empty content, or more than 500 characters;
- accept only a real Gregorian
YYYY-MM-DDdate, not a timestamp or free-form date; - reject any extra field, multiple todo, list/array, alternate assignee/owner, status, time/reminder, recurrence, or embedded mutation instruction;
- return typed missing/ambiguous-field results rather than guessing;
- treat model/plugin text as untrusted and never interpret description text as instructions;
- produce a pure candidate only; it cannot call task storage or #124 approval/execution methods.
The immutable ProposalSpec binds:
plugin=todo, exact build/schema/policy digests, andaction=propose_create;operation=create,effect=task.create, and destinationdataops.tasks;- normalized description and exact date;
- fixed
status=todo,source=conversational-agent,timeZone=Europe/Berlin; - authenticated actor/owner/assignee DataOps user ID;
- conversation/draft references, permission
todo:create:self, and expiry; - no existing target/base revision, because this is create-only.
The deterministic renderer displays every operator-relevant field listed in the journey. The executor receives only the stored approved spec; it cannot add fields or reinterpret the description.
Clarification, revision, and cancellation
- Missing description: ask what task should be created.
- Missing date: ask for the date.
- Relative date that has one answer from the supplied Berlin current date: convert it and show the exact result in preview.
- Ambiguous date, locale, or reference such as “then”, “next time”, or an unresolved “that”: ask one concise question; do not search broad history or invent context.
- Specific time/reminder request: state that this version stores a date only and ask whether to continue for the exact displayed date.
- Multiple requested tasks: explain that one proposal creates one todo and ask which single todo to prepare first. Do not silently choose or create a batch.
- Request changes: update the
PluginDraftat its expected revision, create a new immutable proposal version, and revoke/supersede every old presentation. - Cancel proposal: revoke approval controls but keep the draft/conversation according to core semantics.
- Discard draft: abandon it according to core semantics.
- Expired proposal: explain that approval expired and regenerate a current preview before presenting a fresh control.
No clarification, model retry, revision, cancel, discard, expiry, or session action calls the writer.
Capability-limited actor-owned task writer
Add a typed ActorTodoWriter separate from generic task CRUD. It accepts only a trusted execution envelope from #124 plus the stored todo spec:
attempt ID and current lease generation
proposal ID/version and canonical payload hash
actor/owner DataOps user ID
description
date
It derives a stable task ID and idempotency identity from the execution attempt/proposal, not from random UUIDs or Telegram deliveries. It writes exactly:
id
description
date
status=todo
source=conversational-agent
assigneeId=actor user ID
createdBy=actor user ID
createdAt / updatedAt
assistantExecutionRef:
executionAttemptId
proposalId / proposalVersion
canonicalPayloadHash
The provenance is safe metadata: no conversation text, Telegram IDs, action tokens, model output, prompt, or secret.
Use one DynamoDB transaction for the internal effect:
- condition-check that the referenced #124 attempt is still
executing, belongs to this exact proposal/actor, has this payload hash, and is owned by the supplied unexpired lease generation; - put the deterministic task only if its key does not exist.
On a conditional collision, strongly read the deterministic task:
- if actor, proposal, attempt, payload hash, description, and date all match, return the same successful safe receipt;
- if any value differs, return a redacted
failed_safeidempotency conflict and never overwrite the task.
The writer exposes no update/delete/list/arbitrary-put capability. The execution worker role receives only the additional task-table GetItem, conditional PutItem, and transaction permissions required by this contract. It receives no task update/delete/scan/query permission.
The safe result receipt contains task ID, actor ID, proposal/attempt references, canonical payload hash, and timestamps. It contains no callback token, prompt/model body, Telegram identifier, or private conversation body.
Extend the Task contract and portable task export/validation/restore only as needed for createdBy and the bounded assistantExecutionRef. Existing task rows without these optional fields remain valid. Export validation rejects malformed or secret-bearing provenance.
Authorization and conflict behavior
The plugin is visible/invocable only for an active linked admin or operator in a private Telegram channel with todo:create:self.
Authorization is rechecked by #125 before invocation, by #124 at approval, and by #124 immediately before the todo writer. The writer additionally binds the attempt/proposal actor to the task owner/assignee in its transaction.
These cases execute nothing:
- unlinked, revoked, disabled, wrong-role, wrong-channel, or group actor;
- another actor's presentation or attempt;
- stale conversation/plugin/draft revision;
- expired/revoked/consumed/superseded presentation;
- changed plugin/build/schema/policy/payload/rendered-view digest;
- detached text such as “yes” without the exact core approval action;
- missing/ambiguous/invalid description or date;
- more than one todo or any unsupported field;
- stale/expired worker lease or mismatched execution envelope.
Create-only todo has no mutable canonical target/base revision. Its conflicts are proposal revision/build/policy/hash drift and deterministic task-ID collisions. A conflict never overwrites an existing task; the user must generate and approve a current proposal.
Failure, recovery, and rollback semantics
- Before approval: there is no task to roll back.
- Approval transaction failure: no attempt and no task.
- Failure before the task transaction: record/prove
failed_safe; no task. - Failure of the task transaction: no partial task because DynamoDB transactions are atomic.
- Crash or lost response after the task transaction commits: keep the task; #124 recovery uses the same attempt/key, reads the deterministic task, and records success without another effect.
- Crash before #124 records the successful receipt: the same recovery behavior applies.
- Stale worker result: cannot overwrite the attempt or task because both the task transaction and attempt finalization check the lease generation.
- Cancel/request changes after approval cannot promise rollback once execution is pending/executing. The bot explains that submitted execution cannot be canceled. This issue adds no task deletion/undo.
No automatic rollback deletes a created task. If a later operator wants deletion or update, they use existing authorized task workflows outside this plugin; adding conversational update/delete is a separate issue.
Acceptance criteria
- The production static registry contains one
todoplugin with onlypropose_create,todo:create:self, private Telegram role/channel restrictions, and a strict two-field schema. - An ordinary private-chat request and confirmed media-derived text use the two-turn #125 path; no task is created during selection, clarification, draft, preview, revision, or approval request handling.
- Missing/ambiguous description/date, specific time, and multiple-task requests receive concise clarification without guessing or batching.
- Relative dates use the supplied current
Europe/Berlindate and the preview always displays an exactYYYY-MM-DD, timezone, and “date only” semantics. - Preview displays the complete normalized description, date, assignee, and status bound to the immutable proposal hashes.
- Request changes supersedes the old proposal/presentations and shows a full revised preview; stale controls execute nothing.
- Core approve/cancel/discard/expiry semantics are reused unchanged; plugins cannot approve themselves or define transitions.
- Approval returns
execution_pending, and only the durable #124 worker can invoke the todo writer. - The writer accepts only the capability-limited schema, rechecks the current attempt lease/proposal/actor transactionally, assigns the task to the actor, and cannot update/delete/query arbitrary tasks.
- One approved proposal creates at most one task with exact approved description/date and fixed status/source/ownership/provenance fields.
- Duplicate callback, update, Stream delivery, worker lease, timeout, and recovery return the same task/result and cannot create another task.
- Wrong actor/channel, revoked permission, stale revision/build/hash/lease, unsupported fields, and deterministic ID collision create no task and overwrite nothing.
- Failure before/inside the write leaves no task; a post-commit lost response preserves and reconciles the one task rather than deleting/recreating it.
-
/todonever invokes the model, plugin, approval flow, or task writer and returns only static conversational guidance. - The current Telegram direct-task compatibility route no longer creates a task from an ordinary message before approval.
- Existing generic task API, task records without assistant provenance, exports/restores, and unrelated assistant/Telegram behavior remain compatible.
- Audit/status/result data is bounded and redacted; it contains no token, prompt/completion, Telegram identifier, secret, or raw conversation body.
- SAM grants the execution worker only the minimal additional task-table permissions and does not grant task mutation to the model/runtime or Telegram webhook path.
- [HUMAN, delegated to #128 Text + todo] After the #166/#182 sequence and a fresh ordinary all-off deployment, one linked operator sends an ordinary private Telegram todo request, answers one clarification, requests one revision, approves the exact preview, and confirms exactly one canonical matching Task exists. Repeated approval/delivery produces no second Task, while
/todoinvokes no provider/writer and produces no Task. Sanitized evidence is cross-posted here.
Test scenarios
Ordinary conversation and clarification
Given an active linked private user, fake z.ai responses, and requests with complete, missing, ambiguous, relative-date, exact-time, and multi-todo content
When #121 and #125 process each input
Then complete single input reaches one preview, missing/ambiguous values ask one question, relative dates show an exact Berlin date, time requests require date-only confirmation, multi-todo requests select none, and the task writer call count is zero.
Strict plugin schema
Given descriptions with whitespace/Unicode/control/zero-width/oversize variants, valid/invalid dates, arrays, extra fields, alternate assignees/statuses, and embedded instruction-like text
When propose_create validates them
Then only one normalized description plus real date is accepted, instruction-like description remains inert data, unsupported fields are rejected, and no invalid proposal/writer call exists.
Exact preview and revision
Given proposal v1 and revisions changing description or date
When previews are rendered and old/new controls are used
Then each preview/hash contains all current fields, v1 is superseded/revoked, v1 approval creates nothing, and only exact approval of the current version may queue one attempt.
Approval and actor isolation
Given active, disabled, revoked, unlinked, other-user, group, stale-plugin, expired, canceled, and detached-“yes” cases
When invocation or approval is attempted
Then only the exact active actor/private-channel/current-presentation path queues execution; every other case creates no attempt/task and reveals no other user's state.
Capability and field confinement
Given a malicious model/plugin candidate and direct writer calls containing owner/status/bundle/recurrence/time/link/update/delete/arbitrary fields
When validation and execution run
Then every extra capability is rejected, the writer exposes no generic task client, and a valid result contains only the fixed actor-owned todo fields and safe provenance.
Concurrent approval and execution
Given 25 simultaneous approval callbacks, duplicate Telegram updates/Stream records, and concurrent workers
When the full flow runs against DynamoDB Local
Then one proposal/attempt/task ID wins, one task row exists, all idempotent success paths report that same task, and the task contents equal the approved preview.
Conflict and authorization revocation
Given proposal revision/build/schema/policy/hash changes, deterministic task-ID collision with different data, identity disablement, permission revocation, and a stale worker lease
When approval/recovery runs
Then no mismatched task is created or overwritten, no stale worker dispatch succeeds, and the attempt records a safe conflict/failure requiring a fresh proposal where appropriate.
Crash and rollback boundaries
Given injected failure (a) before approval commit, (b) after approval before lease, (c) before task transaction, (d) during transaction, (e) after task commit before return, and (f) after return before attempt-result write
When #124 recovery runs
Then (a,c,d) leave no task, (b) eventually executes, and (e,f) find the exact existing deterministic task and finalize once without delete/recreate/duplicate.
Command and legacy bypass regression
Given /todo, /todo buy milk tomorrow, and the legacy ordinary direct-task input
When the Telegram route processes them
Then commands return static guidance with zero model/writer calls, ordinary input enters conversation/preview, and no compatibility path creates a task before approval.
Export, restore, and redaction
Given ordinary tasks plus conversational todo tasks and malformed/secret-bearing assistant provenance
When portable export validation, dry-run import, and restore tests run
Then optional safe provenance round-trips, legacy tasks remain valid, unsafe provenance fails without echoing the value, and restored records cannot replay an approval.
Infrastructure permissions
Given the SAM template and deployed-role assertions
When validation runs
Then only the execution worker gains the required task GetItem/conditional PutItem/transaction access, while the model/runtime and Telegram webhook paths cannot write tasks directly through this plugin.
Required verification
Software Engineer and Tester must run:
npm --prefix backend test
npm --prefix backend run typecheck
npm --prefix backend run build
make sam-validate
make sam-build
Also run focused DynamoDB Local tests for plugin validation, actor isolation, 25-way approval concurrency, deterministic task collisions, lease enforcement, all crash boundaries, legacy bypass removal, and portable export/restore provenance. Use injected clocks/current Berlin date, fake z.ai, fake Telegram transport, and no external network or real provider write.
Run the existing Telegram, task API/data-layer, conversational runtime, approval/worker/recovery, portable export, restore, and SAM/IAM test suites in full. Assistant Engineer reviews the plugin/executor boundary before Tester acceptance.
No portal UI changes are in scope, so Playwright/screenshots are not required unless implementation unexpectedly changes a web surface; Tester must explain any omission. The real Telegram/task creation is only the single [HUMAN] sandbox gate above. When all automated work passes first, ship with Refs #126, add human, and leave the issue open until that gate is complete.
Dependencies
- #121 supplies linked private Telegram identity, ordinary text/confirmed media event normalization, conversations, callback/status rendering, and group fail-closed behavior. Preserve this dependency ID even while its grooming is underway.
- #124 supplies exact proposal rendering/presentations, atomic approval, execution attempts, durable worker leasing/recovery, status events, and capability-scoped executor invocation.
- #125 supplies static registry,
todo:create:self, strict two-turn loading/invocation, context/current-date input, z.ai model adapter, and stale-turn validation. - #123 is transitively required through all three dependencies for persistent records and DynamoDB access patterns.
Implementation must not weaken a dependency contract. If #121's final interface names differ, adapt this slice to its normalized private-message/action contracts without adding a second Telegram path.
Out of scope
/todomutation shortcut, slash-command parsing into a proposal, or command-based approval.- More than one todo per proposal, batches, partial success, bulk import, or multi-effect plans.
- Task update, delete, complete, waiting/follow-up, undo, recurrence, reminder times/notifications, another assignee, bundle/template linkage, tags, files, docs, proof, or arbitrary task fields.
- Group conversations/topics, web conversation/approval, cross-channel continuation, or notification delivery beyond #121's Telegram result.
- New voice/photo processing; #121 owns media conversion/confirmation and this issue consumes only confirmed derived text.
- Typefully, SOP, podcast, GitHub, workflow, calendar, email, or other plugins/effects.
- Personal memory, broad history/reference search, source-document retrieval, or private knowledge ingestion.
- Changes to generic task product behavior beyond optional safe provenance compatibility and the dedicated internal todo writer.
- Retrofitting legacy
AssistantJobapprovals. - Modifying
../dtc-operations,../datatasks, or../podcast-assistant.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with _docs/CONVERSATIONAL_AGENT_PLUGIN_ARCHITECTURE.md at 4e5402c, then review #166, #182, and #128 for the reviewed storage sequence and rollout ownership. The remaining work is the bounded private Telegram Text + todo proof after the accepted #179 and #168 commits are shipped; done means one approved, actor-owned canonical Task and one private result, with duplicate convergence and sanitized evidence cross-posted to #126.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- github-actions, typescript
- Domain
- backend, data, infrastructure, testing
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 15/100