All posts
Updated

Skills that create report apps

Turn “make me a report” into a workspace app through conversation, without building a UI by hand.

Source docs/en/site/skill-report-apps.md

Doc version: 1.0 Date: 2026-07-19 Status: Implemented (backend/internal/appfromchat) Related: [docs/core-mechanisms/工作区应用.md](/docs/mech-workspace-apps) §7.2 / §9, [help/product-features/skill-content-rules.md](/docs/help-skill-content-rules)


1. The motivating case

Conversation example (debug-mirror):

RoundUserResult
1/日隆部门职级统计图 部门职级统计图Success: skill produced an HTML report
2“Please create an app… so tapping the app produces this same report”Failed at the time: whole-app LLM context deadline exceeded

User goal: one tap in the app = the same report the skill produced in conversation (same template, same grouping, same numbers), with the stats logic baked into the app code so it no longer depends on the conversation model improvising.


2. What you must keep separate

2.1 Where does the stats logic live?

Take “Rilong department–rank chart” as an example:

WhereWhat it containsCan it compute?
Skill packMarkdown grouping rules, HTML template, placeholder listNo (no build_report / run_script)
Conversationskill_read → two query.run calls → the model groups and fills the table on the spotfile_writeYes (logic lives in the conversation LLM)

Observed tool chain: skill_readskill_script_readdata_source_invoke×2 → file_write, and no run_script.

So:

  • The skill supplies definitions + a delivery template (the authoritative spec);
  • Executable statistics happen in the model step inside the conversation;
  • “Turn it into an app” must turn that same conversation-time computation into Python inside the app pack.

2.2 Product principles (decided)

  1. Skill + conversation = app. The main path is the LLM compiling conversation stats into app code — not a one-size-fits-all report engine in the platform.
  2. All calculation lives in the generated app pack. The platform only orchestrates creation and the data-fetch bridge; it does not re-run business stats at runtime.
  3. Future skills, reports, and fill rules each live in their own app pack. There is no shared “universal backend calculator”.
  4. If the skill already ships scripts/ compute scripts (including build_report), copy them as-is at create time — do not regenerate.

3. Why it failed early (for contrast)

3.1 Create failed

  • Round 2 “please create an app” often did not type / to pick the skill → no reference skill pack → HTML report fast path skipped.
  • Fell through to whole-app GenerateAppScaffold (long handbook) → Minimaxi timed out.

3.2 Even a successful create could drift

The platform generic assemble_html_report.py would:

  • Use a different rank-bucket scheme;
  • Rewrite the HTML skeleton (keep CSS only);
  • Usually run only one query.

That does not match the skill’s “one template + conversation grouping + two queries”.


4. Target architecture

Conversation report (skill spec + LLM stats on the spot)
        │
        ▼ User says “create an app” (no / needed; can inherit this conversation’s skill_read)
        │
        ├─ Skill already has build_report* ──► copy into the app pack
        │
        └─ Skill has no compute script ──► LLM (long timeout)
              Input: skill body + HTML template + this conversation’s report summary
              Output: app scripts/build_report.py
                    + handlers (fetch data → call in-app script → save_upload)
        │
        ▼
One-tap generate on the app desktop (runs only app-pack logic; no conversation-time improvisation)

5. Implementation notes

5.1 Inherit the reference skill from the conversation

  • File: session_skill_inherit.go
  • When the current message has no skill_ids, parse skill_id from recent assistant tool_trace of skill_read / skill_script_read.
  • Fixes: “the report looks great → please create an app” with no / going down the wrong path.

5.2 Conversation summary (so the LLM can match conversation stats)

  • File: session_report_hint.go
  • Extract the latest successful report: query_id list, assistant conclusion summary, corresponding user request.
  • Tell the model clearly: stats were done in conversation; the skill may have no executable script.

5.3 LLM-generated in-app compute script (main path)

  • File: skill_report_build_llm.go
  • Entry: CompileAppBuildReportViaLLM
  • Require a complete Python file that includes def build_report(app_root, template_rel, period, emp_rows, dict_rows=None);
  • Keep the skill HTML skeleton; update the data region only; no matplotlib / CDN / PNG;
  • Write to the app scripts/build_report.py after agentscript.ScanScriptContent security scan.
  • Timeout: when creating a report app, use PlatformAppImproveLLMTimeout (about 10–15 minutes) so we do not hit the short whole-app timeout again.

5.4 If the skill already has a script, copy it

  • File: app_local_build_report.goFindSkillReportComputeScript
  • Prefer def build_report; otherwise a non-matplotlib script whose filename contains fill / build / assemble / report.

5.5 Fallback

  • If the LLM fails and the skill body’s grouping table can be parsed: GenerateAppLocalBuildReportPy (deterministic script with rules inlined).
  • If that also fails: tell the user to type / for the original skill or ask the app-dev assistant for a whole-pack alignment, not a vague “simplify the request”.

5.6 What handlers do

  • File: skill_faithful_report.goskillFaithfulHandlersPy
  • Only: query_run (emp + dict) → load in-app BUILD_SCRIPTbuild_reportsave_upload + history table.
  • Do not aggregate business stats in the platform backend.

5.7 Orchestration entry

  • execute.go: ResolveReferenceSkillIDsTryScaffoldHTMLReportFromSkills (with conversation ID + long-timeout LLM).
  • html_report_scaffold.go: faithful path first; otherwise the old generic assemble (not recommended).

6. Key files

PathRole
backend/internal/appfromchat/session_skill_inherit.goInherit skill_id from the conversation
backend/internal/appfromchat/session_report_hint.goSummary of the conversation report
backend/internal/appfromchat/skill_report_build_llm.goLLM generates build_report.py
backend/internal/appfromchat/skill_faithful_report.goAssemble the faithful report-app draft
backend/internal/appfromchat/app_local_build_report.goDiscover skill scripts / deterministic fallback
backend/internal/appfromchat/skill_report_rules.goParse grouping tables from the body (fallback / check)
backend/internal/appfromchat/html_report_scaffold.goHTML report scaffold fork
backend/internal/appfromchat/execute.goOverall orchestration for creating an app from chat
backend/internal/appfromchat/persist.goPersist to disk; never overwrite in-app compute scripts with platform constants

7. Recommended flow (user side)

  1. In a work-agent conversation, type / to pick a report skill and produce a good report first.
  2. In the same conversation, send: “Please create an app so tapping the app produces this same report” (you may type / to pick the skill again).
  3. Open the app desktop → one-tap generate → preview the HTML and check numbers against the conversation result.
  4. If the skill should stand on its own later: copy the stable build_report.py back into the skill scripts/ so later app creates can sync it directly.

8. How to write the skill (less drift)

See [skill-content-rules.md](/docs/help-skill-content-rules) “fetch data + fixed HTML report”:

  • Spell out the grouping table, placeholders, and the single template path in the body;
  • Prefer shipping build_report(...) in the skill (no charts);
  • If stats still happen only in conversation: creating the app depends on LLM compile — the clearer the grouping table, the better the generated code.

9. Acceptance

  • [ ] “Create an app” with no / inherits the report skill just used in this conversation, and does not die on a whole-app timeout.
  • [ ] The app pack contains a compute script (copied from the skill or LLM-generated scripts/build_report.py).
  • [ ] One-tap generate: two queries fetch data + in-app script fills the template; numbers match the successful conversation report (or differences are explainable).
  • [ ] Editing one app’s script does not affect other report apps; there is no coupling to a platform “universal report service”.

10. Explicitly out of this round

  • Invoking an app directly from conversation (mechanism doc P2).
  • Stretching whole-app GenerateAppScaffold timeout as the main report fix (treats the symptom).
  • Hard-coding one business grouping scheme into resident Go backend logic.

11. One-line reminder

The skill gives the spec and template; the conversation does the stats. When creating the app, the LLM compiles that conversation stats into in-app Python; at runtime only the app pack runs.