commit 6337fc823ce20251c8de8085948165c81aaa701e Author: DeepSeek Harness Team Date: Sat Oct 31 20:05:39 2026 +0800 feat: add the dsh-superagent research prototype diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2a9a5e --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +lib/ +.artifacts/ +.env +coverage/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..df5ef0e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 DeepSeek Harness Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..39e1c4a --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# dsh-superagent + +> **Research prototype for reference only, not an official DeepSeek product.** This code grew out of our team's day-to-day development work, with much of the engineering done by coding agents. Treat it as research code, not production-ready software. It may still contain bugs, and its coding style and engineering practices may be inconsistent. Review and adapt it before using it in your own environment. +> +> We have adapted it for publication, so it is not identical to our internal version or the implementation described in *Exploring Super-agent Design: Towards a More Autonomous Agent System*. Some designs discussed in the article are directions for future development and are not implemented in this prototype. + +dsh-superagent is a proactive coding agent for one GitHub repository, packaged as a bundle for [DeepSeek Harness](https://www.deepseek.com/en/harness/). It listens to GitHub and, optionally, Feishu. When someone allowed asks the agent for work, it opens a work item, works on it in its own agent session and directory, and answers in the original conversation, for example with a pull request. A web panel shows what needs a person. + +It is also a way of working with an agent: **take as little human attention as possible, and give as much value as possible.** An agent that can act for a whole team can also waste a whole team's attention, so it acts only when asked or when a rule the team wrote allows it, and speaks only when someone must act or can use a result. [docs/000-philosophy.md](docs/000-philosophy.md) explains the principle and what it asks of the operator. + +## Setup + +Setup has many steps, so an agent does it with you. Ask your coding agent: + +> Read SETUP.md in this repository and set up dsh-superagent with me. + +It asks one short question at a time, shows the full plan once, and then installs and configures everything. [SETUP.md](SETUP.md) is what it follows. + +## Documentation + +Read the numbered documents in order; each builds only on the ones before it. + +- [000 Philosophy](docs/000-philosophy.md) +- [001 Domain model](docs/001-domain.md) +- [002 Authorization](docs/002-authorization.md) +- [003 Execution](docs/003-execution.md) +- [004 Prompts](docs/004-prompts.md) +- [005 Panel](docs/005-panel.md) +- [006 Deployment](docs/006-deployment.md): manual setup, configuration, upgrades and troubleshooting +- [007 Architecture](docs/007-architecture.md) +- [008 Cookbook](docs/008-cookbook.md): adding a Source + +## Development + +`pnpm check` runs the typecheck, the tests and the build. + +## License + +[MIT](LICENSE) diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 0000000..87a8366 --- /dev/null +++ b/SETUP.md @@ -0,0 +1,79 @@ +# SETUP + +Instructions for an agent that sets up dsh-superagent with its operator. If you are a person, ask your coding agent to read this file and follow it. + +## How to run the setup + +The setup follows [docs/000-philosophy.md](docs/000-philosophy.md): it takes as little of the operator's attention as possible. + +- **Before the first question,** read only this file and `docs/000-philosophy.md`, and run the probe below once. Read nothing else until every answer is collected. +- **Ask one question per message,** about one thing. Wait for the answer before the next one. Skip a question the probe already answers. +- **Keep each message under 300 words,** unless the operator asks for more. Give each question a recommended answer the operator can accept with one word, and one sentence on why it matters. +- **Do not execute while asking.** Collect every answer, then send one message with the full plan, and execute only after the operator confirms it. +- **Never ask for a secret in the chat.** Steps that need the operator's hands, such as creating an account, signing a CLI in or entering an app secret, go into one message with the exact commands to run in their own terminal. +- **Teach the philosophy through the questions,** one sentence at a time, not as a lecture. + +## 1. Probe + +Run from this checkout, once, and keep the result to yourself: + +```sh +node -v; pnpm -v; git --version; tmux -V; python3 --version; gh --version | head -1; dsh --version +gh api user --jq .login +command -v lark-cli +grep -l -E 'id: dsh-superagent\b' "${DSH_HOME:-$HOME/.dsh}"/profiles/*/cordis.patch.yml 2>/dev/null +``` + +- **Prerequisites:** Node.js 22.19+ or 24+, pnpm, git 2.32+, tmux, Python 3.8+, GitHub CLI 2.48+, and DSH within the `engines.dsh` range of `package.json`. A missing one becomes an item of the plan, not a question. DSH also needs a model provider; if the operator has never run DSH with a model, configuring one is an operator step. +- **Operator login:** the `gh api user` result is the operator's own GitHub login, used as the recommendation in question 5. +- **Existing profile:** a profile with a `dsh-superagent` entry is an earlier setup: ask whether to continue it, keep its configuration, and read Upgrade in `docs/006-deployment.md`. + +## 2. Questions + +Ask in this order, one per message, following the flow of work in [docs/001-domain.md](docs/001-domain.md): first where Triggers come from, then who may ask for work there, then what the Executor does with a Ticket. Recommendations are in brackets. + +**Sources: where requests come from.** + +1. **Sources.** Should the agent take requests from GitHub only, or also from Feishu chats? [GitHub only, unless the team works in Feishu.] +2. **Repository.** Which github.com repository should the agent work on, as `owner/name`? +3. **Agent account.** Which GitHub user account will the agent act as? [A dedicated account, never the operator's own: everything the agent writes appears under this name.] It may not exist yet; creating it is an operator step. +4. With Feishu: **which chats,** by name? The agent looks up their ids after the bot is configured. + +**Policy: who may ask for work.** + +5. **On GitHub.** Which GitHub logins may ask the agent for work? [Only ``: the agent acts only for people on this list, and a short list keeps the team's noise low while the operator learns how the agent behaves.] +6. With Feishu: **who may ask in Feishu?** [Everyone in the first chat, kept in sync with its members.] + +**Execution: what the agent does with a Ticket.** + +7. **Checkout.** Which local clone of the repository should each Ticket check out from? [A new clone under `~/src/`.] +8. **Commit identity.** Who should the agent's commits name as author? [The agent account, so its work is visibly its own.] Another identity, such as the operator's, goes into the Delivery requirements prompt section. +9. **Auto-merge.** Should the agent enable auto-merge on its own pull requests once they are ready and approved? [No: people merge until the agent's pull requests have been right for a while.] The agent never enables it as the agent account, so with yes it needs the GitHub CLI of another account that may merge: ask the operator to sign one in among the operator steps, and name it in Delivery requirements. With no, remove the auto-merge paragraph from Delivery requirements. + +Do not ask about values with a good default; list them in the plan instead: + +- profile `superagent`, a copy of DSH's Web profile kept apart from the profile the operator works in, so it restarts without closing their Sessions; port `3082`; state directory `/.dsh/state/superagent` as an absolute path; +- an hourly check from the operator's own profile, which repairs dsh-superagent when it is not running; +- branch prefix `agent/`; +- accept mode `observe`, which records proactive work without doing it, and the default rules and prompt sections; +- with Feishu: the bot reads only mentions, and direct messages are on. + +## 3. Plan + +Send one message, under 300 words: what will be installed, the accounts and CLIs, the settings to save, the operator steps, and the defaults. Ask for one confirmation. + +## 4. Execute + +1. **Operator steps first.** Read the Accounts section of `docs/006-deployment.md`, write the CLI wrappers yourself, then send one message with what only the operator can do: create the agent account and, with auto-merge, sign a CLI in as the account that enables it, accept the repository invitation as the agent account, run `auth login` for each wrapper, and for Feishu create and publish the bot app and run the `config init` command with its secret. Wait until the operator says it is done, then check each login with ` api user --jq .login`. +2. **Install and start.** Follow the Install, Machine configuration, and Start and stop sections of `docs/006-deployment.md`. Skip the `git clone` of this repository: you are in its checkout. Clone the repository of question 2 if question 7 asked for a new clone. With an existing profile, skip the `dsh superagent --from-default-profile` command, which would replace the profile and delete its `cordis.patch.yml`, and edit that file instead of replacing it. +3. **Settings.** Save them through the Management API of `docs/006-deployment.md`, in this order: the `github` Source, the `feishu` Source if used, the `policy` block with the allowed lists (for Feishu, look up the chat ids with the bot's CLI first), and the `executor` block with a prepare command from the answer to question 7. Then save the prompts with the commit identity of question 8 and the auto-merge answer of question 9 in Delivery requirements. A save that returns `restarting: true` restarts dsh-superagent: wait until `scripts/dsh-superagent status` no longer exits 1 before the next request, and read the revisions again from `GET settings`. +4. **Failures.** A refused save names the setup problem in its response body. Find it in the Troubleshooting table of `docs/006-deployment.md`, fix it, and continue. Ask the operator only for what you cannot fix yourself. + +## 5. Verify and hand over + +`scripts/dsh-superagent status` exits 0 with no setup problem. If you run in the operator's own DSH profile, create the hourly check of the Hourly check section of `docs/006-deployment.md` with your schedule tool; otherwise give the operator its prompt to schedule there. Then send one message, under 300 words: + +- The panel URL, and that the **Superagent** entry in the sidebar is where to look. +- How to try it: from an account on the allowed list, comment `@ ` on an issue in the repository. A Ticket appears on the overview within about a minute. +- Three rules for the operator, from the Responsibility section of `docs/000-philosophy.md`: start narrow, tune for less noise, own what the agent says. +- Where to read more: the numbered documents in `docs/`, in order. diff --git a/cordis.patch.yml b/cordis.patch.yml new file mode 100644 index 0000000..5ada6df --- /dev/null +++ b/cordis.patch.yml @@ -0,0 +1,193 @@ +# dsh-superagent bundle layer: inserts the plugins without configuration, and the agent presets its Sessions run under. The +# profile supplies the `dsh-superagent` entry's stateDir (see docs/006-deployment.md and examples/profile.cordis.patch.yml); the plugin +# refuses to start without stateDir. +- insert: + - id: dsh-superagent + name: dsh-superagent + - id: dsh-superagent-github + name: dsh-superagent/github-watch + # Optional Feishu Source; requires configuring a bot CLI after enabling. + - id: dsh-superagent-feishu + name: dsh-superagent/feishu-watch + disabled: true + # The agent preset of every Executor Session (WORKER_PRESET in src/app/sessions.ts); the main Session has its own, below. + # It is DSH's `standard` preset with only what a worker needs, and a shell whose foreground command moves to a background job + # after 5 seconds, so a long command does not hold back a person's message, which the Session takes at its next step. Left + # out: questions to a person, presenting, plan mode, the plugin manager and market, scheduling, goals, workflows, web fetch, + # and forked subagents, which would run under this preset; spawned subagents run under `standard`, whose shell moves a + # command to a background job only after 60 seconds, as a subagent's jobs are killed when it ends. A preset row has no + # setting that hides it from the preset picker, so its name and description say whom it is for. + - id: dsh-superagent-worker-preset + name: '@deepseek-ai/dsh-agent-preset' + config: + id: dsh-superagent-worker + name: dsh-superagent 执行器 + description: dsh-superagent 的执行器 Session 专用,不供手动选择。前台命令超 5 秒转入后台,只含执行工作所需的工具。 + order: 90 + plugins: + - id: persona + name: '@deepseek-ai/dsh-persona' + config: + prefix: You are a coding agent powered by the {{model}} model. + - id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + - id: time-context + name: '@deepseek-ai/dsh-time-context' + # A foreground command waits at most 5 s, then the shell tool moves it to a background job. + - id: shell + name: cordis:group + group: true + isolate: + shell: true + config: + - id: bash-sandbox + name: '@deepseek-ai/dsh-bash-sandbox' + disabled: !!js process.platform === 'win32' + config: + timeoutMs: 5000 + maxTimeoutMs: 5000 + - id: pwsh-sandbox + name: '@deepseek-ai/dsh-pwsh-sandbox' + disabled: !!js process.platform !== 'win32' + config: + timeoutMs: 5000 + maxTimeoutMs: 5000 + - id: tool-bash + name: '@deepseek-ai/dsh-tool-bash' + disabled: !!js process.platform === 'win32' + - id: tool-pwsh + name: '@deepseek-ai/dsh-tool-pwsh' + disabled: !!js process.platform !== 'win32' + - id: tool-fs + name: '@deepseek-ai/dsh-tool-fs' + - id: tool-fs-search + name: '@deepseek-ai/dsh-tool-fs-search' + config: + sampleOverCapGlobResults: false + # A wait for a job ends after 5 s too; the job's end wakes the worker with its result. + - id: tool-jobs + name: '@deepseek-ai/dsh-tool-jobs' + config: + waitTimeoutMs: 5000 + maxWaitTimeoutMs: 5000 + - id: skill-filesystem + name: '@deepseek-ai/dsh-skill-filesystem' + - id: tool-skill + name: '@deepseek-ai/dsh-tool-skill' + - id: compaction + name: cordis:group + group: true + isolate: + compaction: true + toolResultPruner: true + config: + - id: compaction-basic + name: '@deepseek-ai/dsh-compaction-basic' + - id: command-compact + name: '@deepseek-ai/dsh-command-compact' + - id: tool-result-pruner + name: '@deepseek-ai/dsh-compaction-tool-result-pruner' + config: + thresholdChars: 8192 + headChars: 4096 + tailChars: 1024 + - id: delegation + name: cordis:group + group: true + isolate: + workflowEngine: true + config: + - id: tool-subagent-control + name: '@deepseek-ai/dsh-tool-subagent-control' + - id: tool-subagent + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: spawn + toolName: subagent + modelSelection: true + modelDiscovery: true + agentPreset: standard + - id: tool-todo + name: '@deepseek-ai/dsh-tool-todo' + config: + allowParallelInProgress: true + - id: tool-web + name: '@deepseek-ai/dsh-tool-web' + config: + fetch: false + searchTimeoutMs: 60000 + # The agent preset of the main Session (ROUTER_PRESET in src/app/sessions.ts). The worker's 5-second shell and job waits, so a command or a wait does not hold back the next candidates, and + # only what the main Session uses: no subagents, web, file search or writes, todo list, questions to a person, presenting, + # plan mode, plugin manager, scheduling, goals or workflows. No persona: the main Session's prompt is complete, so it has + # no other section. + - id: dsh-superagent-router-preset + name: '@deepseek-ai/dsh-agent-preset' + config: + id: dsh-superagent-router + name: dsh-superagent 主 Session + description: dsh-superagent 的主 Session 专用,不供手动选择。前台命令和任务等待超 5 秒转入后台,只含路由所需的工具。 + order: 91 + plugins: + - id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + - id: time-context + name: '@deepseek-ai/dsh-time-context' + - id: shell + name: cordis:group + group: true + isolate: + shell: true + config: + - id: bash-sandbox + name: '@deepseek-ai/dsh-bash-sandbox' + disabled: !!js process.platform === 'win32' + config: + timeoutMs: 5000 + maxTimeoutMs: 5000 + - id: pwsh-sandbox + name: '@deepseek-ai/dsh-pwsh-sandbox' + disabled: !!js process.platform !== 'win32' + config: + timeoutMs: 5000 + maxTimeoutMs: 5000 + - id: tool-bash + name: '@deepseek-ai/dsh-tool-bash' + disabled: !!js process.platform === 'win32' + - id: tool-pwsh + name: '@deepseek-ai/dsh-tool-pwsh' + disabled: !!js process.platform !== 'win32' + - id: tool-fs + name: '@deepseek-ai/dsh-tool-fs' + config: + write: false + edit: false + - id: tool-jobs + name: '@deepseek-ai/dsh-tool-jobs' + config: + waitTimeoutMs: 5000 + maxWaitTimeoutMs: 5000 + - id: skill-filesystem + name: '@deepseek-ai/dsh-skill-filesystem' + - id: tool-skill + name: '@deepseek-ai/dsh-tool-skill' + - id: compaction + name: cordis:group + group: true + isolate: + compaction: true + toolResultPruner: true + config: + - id: compaction-basic + name: '@deepseek-ai/dsh-compaction-basic' + - id: command-compact + name: '@deepseek-ai/dsh-command-compact' + - id: tool-result-pruner + name: '@deepseek-ai/dsh-compaction-tool-result-pruner' + config: + thresholdChars: 8192 + headChars: 4096 + tailChars: 1024 diff --git a/docs/000-philosophy.md b/docs/000-philosophy.md new file mode 100644 index 0000000..8422db9 --- /dev/null +++ b/docs/000-philosophy.md @@ -0,0 +1,33 @@ +# 000 Philosophy + +dsh-superagent is a framework, and also a way of working with an agent. Its core principle: + +**Take as little human attention as possible, and give as much value as possible.** + +Every part of the project is judged by the attention it costs. A message, a notification, a question, a setting, a panel element and a paragraph of documentation each cost someone's attention, and each has to give more value than that, or it is removed. + +## What follows for the agent + +- **It acts only when asked, or when a written rule allows it.** A request counts only when it addresses the agent directly and comes from a person the team allowed: one on an allowed list, or anyone in a conversation the team opened to requests. Work nobody asked for needs a rule the team wrote, and starts in a mode that only records what the agent would have done. +- **It speaks only when a person must act or can use a result.** It confirms a request once and names what it will do. It stays quiet while it works. It writes again when a result is ready, when it needs a decision, or when it cannot finish. Messages are short and go to the conversation where the request was made. +- **It asks one concrete question at a time,** in a structured form, so the question reaches the person instead of staying inside a long report. +- **It stops by impact, not by effort.** A local change goes as far as a reviewable result. A change that needs a design decision stops with a clear question. +- **It reports honestly.** A draft is marked unfinished, a failure is reported as a failure, and a limit is never bypassed to avoid asking. + +## What follows for the design + +- **Few concepts.** The design has only its domain concepts, such as a Source, and their concrete implementations, such as GitHub and Feishu. A business need becomes an attribute of one of them, or a prompt the agent follows. The design does not add a concept for it. +- **Few settings.** A default replaces a setting when one value suits almost every team. Behaviour that a team describes in words is prompt text, not a setting. +- **A panel that shows only what needs a person.** It lists first what waits for a person. A healthy state takes no space. +- **Documents read in order.** Each numbered document builds only on the ones before it, so a reader never needs a concept that has not been introduced yet. + +## Responsibility + +The agent comments, opens pull requests and messages people under accounts your team sees every day. Every message it sends costs your colleagues' attention, and you are responsible for it. The more the agent can do, the more care its operator owes the team: + +- **Start narrow.** Begin with a short allowed list and with proactive work only recorded. Widen both once what the agent would have done looks right. +- **Tune for less noise.** When the agent is noisy, make its prompts shorter and stricter, or pause it. Do not add more messages to explain the noise. +- **Do not use it to produce volume.** Mass pull requests, broadcast messages and busywork cost the team more than they give. +- **Own what it says.** Read what the agent posts in your name, and correct it in the prompts when it is wrong. + +The goal is the right help at the right time, and nothing at any other time. diff --git a/docs/001-domain.md b/docs/001-domain.md new file mode 100644 index 0000000..fd53001 --- /dev/null +++ b/docs/001-domain.md @@ -0,0 +1,112 @@ +# 001 Domain model + +This document names the concepts of dsh-superagent and the rules each one keeps. The code, the tools the agents call, the panel and the other documents use these terms and no others. Following [000 Philosophy](000-philosophy.md), the model holds only concepts the work needs, and a business need becomes an attribute of one of them. + +## Flow + +Work moves from Trigger to Eval to execution. A Source produces Triggers. A Trigger fans out as context to the Tickets subscribed to its Threads. A person's message that reached a Ticket this way stays with that Ticket; every other Trigger enters Eval, which decides whether it asks for new work. When Eval accepts, it creates a Ticket with a Mandate. An Assignment hands the Ticket to an Executor, and results go back to the Source through a Channel. + +```mermaid +flowchart LR + S[Source] -->|Trigger| T[Thread] + T -->|batch| E[Eval] + T -->|Subscription fan-out| K[Ticket] + E -->|accept with Mandate / stop| K + E -->|ignore / unavailable| X[recorded] + P[Policy change] -->|notice| K + K -->|Assignment| W[Executor] + K -->|Channel| S +``` + +## Concepts + +Source, Thread, Eval, Mandate and Subscription form the core domain; the rest supports them. + +| Concept | Definition | Rules it keeps | Why it exists | +| --- | --- | --- | --- | +| Source | A stream of events. A Source kind is a reusable implementation; a Source instance is one configuration of a kind: whether it runs and what it listens to | Holds its read position and its own bot identity. Defines what counts as an event, including conditions that only hold over time. Drops the bot's own messages and Triggers outside what it listens to before they enter the domain. Translates each Trigger into the Threads it belongs to and the Channel addresses to reply on | A restart must not replay events | +| Trigger | One immutable event with the Source's own metadata, such as its actor | Belongs to one or more Threads. Accepting a Trigger twice creates one Ticket | The unit of audit and of idempotence | +| Thread | Triggers in time order; the Source defines which Triggers form one Thread | At most one Eval runs on it at a time. It may have several Tickets. It holds its Subscriptions, the running Eval and its Decision history | Gives Eval a consistent view and its context | +| Correlation | The relation between Threads that one Trigger belongs to | Adds context; never routes | Lets Eval follow related evidence | +| Channel | The write side of a Thread; the Source defines its addresses | A Thread may have several Channels; a Ticket holds a set of them | Acknowledgements, replies, pull requests and resolved reviews are all writes | +| Policy | The versioned assessment rules and each Source's part: who may originate work through that Source, in that Source's own identities, how the list is synchronized, and which bots may make candidates | The Policy version covers the rules, the accept mode and the Source parts. A change that bears on a live Ticket, such as a change to a rule it was accepted under, is delivered to its Assignment; it does not revoke Mandates. Identities on different Sources are never mapped to each other | When the rules change, a running Eval must be discarded | +| Eval | The judgement on a batch of Triggers, in two layers: gate and assessment | Input: the Trigger batch, a Thread snapshot, every Ticket on the Thread including ended ones, and the current Policy. Output: a Decision. Holds no state | Decides whether new work starts, goes on or stops | +| Mandate | What an accept grants: the Policy version, the scope, the strength, the matched rules, the sender's identity on that Source and how the Source received the Trigger | Immutable; wider scope means a new Ticket. A Ticket's end does not end it: only a person's stop withdraws it, and a Policy that no longer holds it revokes it. Revalidated against the current Policy before an ended Ticket resumes | States why the work may be done, and how far it may go | +| Ticket | A unit of authorized work | Only Eval creates one. Refers to its Mandate; holds its Channels and Inputs. Goes open → ended, and back to open when a person's message on one of its Threads resumes it, unless a person stopped it. Defines its own state instead of following the Executor's | Owns the execution and its reports | +| Assignment | The binding of a Ticket to one Executor instance | A Ticket has several over time. Goes pending → running → ended independently of the Ticket. Only its final report may end the Ticket on the Executor's side | The Executor is outside the domain, but who executes must be inside it | +| Subscription | A many-to-many relation between Tickets and Threads, held by the Thread | A matching Trigger fans out to every live subscribed Ticket, with no check of who sent it; a person's message that reaches a Ticket this way takes no Eval. A Thread an Executor follows for context reaches its Ticket the same way, but lends it no authority and does not resume it. An ended Ticket keeps its Subscriptions, including that of a pull request that closed or merged: a person's message resumes it under the same Mandate, with no Eval or Decision; messages from bots and CI do not reach it, since its Executor reads its Threads again when it resumes. A Ticket a person stopped receives nothing. The main Session attaches a Thread to an existing Ticket only when its message points at that Ticket explicitly. A person can drop every Subscription on a Thread; an Executor can drop only its own Ticket's Subscription to a Thread it finds unrelated, except the Thread its Ticket started on and a Thread a Source made it follow, such as its pull request's | Later discussion reaches the Executor without anyone mentioning the agent again | + +A person is not a domain concept. A Trigger carries its actor's identifier as the Source gives it. An allowed list is a Source's part of the Policy, in that Source's identities. That one person has accounts on several Sources is prompt text that the agents read when they need it. + +The Executor, with its Sessions and directories, is outside the domain and connects through Assignments. + +Aggregates: a Source holds its read position. A Thread holds its Subscriptions, its running Eval, which runs serially and merges Triggers that arrive meanwhile into the next batch, and its Decision history. A Ticket holds its Assignments, Channels and Inputs, and refers to Decisions by id. + +## Rejected concepts + +| Rejected | Reason | +| --- | --- | +| Principal or Membership as an entity, or mapping identities across Sources | A person is Trigger metadata; an allowed list is a Source's part of the Policy; a cross-Source mapping is prompt text | +| Eval scheduling its own delayed Triggers | A condition that needs waiting is an ordinary Source, so Eval never depends back on Sources | +| Waiting or revoked Eval states | Waiting belongs to Sources; an Eval that runs out of time queues its Triggers again, or ends `unavailable` | +| At most one live Ticket per Thread | One Thread can carry several independent pieces of work; the assessment avoids duplicates from the Ticket list | +| An "append to Ticket" Decision | Appending is Subscription fan-out and needs no Eval | +| Admission checks on Subscriptions | A Subscription covers the whole Thread; the Mandate separates authorization from context | +| Correlation that routes | Subscriptions route; Correlation only adds context | +| Ticket state that follows the DSH Session | A Ticket can be handed off, resumed, or run by several Executors, so it defines its own state | +| An Executor declaring that a person asked it to stop | Only Eval decides the strength of an end | +| Cancelling running Tickets on a Policy change | A cancelled change leaves half-finished work; the change is recorded and delivered instead | +| Locking all of an Executor Session's tools when it fails or blocks | It would refuse a person working directly in the Session, and the report after recovery; pausing automatic input is enough | +| Inferring "waiting on a person" from a report's free text | The classification needs structured facts; the Executor asks through a structured field | + +## What is reusable + +The concepts above and their rules are reusable. The Source implementations, the Policy contents, the Executor and the Channel implementations belong to a deployment. Each Source keeps its own words internally, such as message, chat or notification, and translates them into Triggers. + +| Part | In this repository | +| --- | --- | +| Source | Feishu: live events, history recovery, polling of subscribed topics. GitHub: notifications, subscribed Issues and pull requests, CI changes, pull requests merged or closed, and, where switched on, every new Issue, pull request and comment in the repository and the default branch's CI | +| Policy | Assessment rules, and each Source's allowed list, its sync, its bot list and its admission table | +| Executor | A DSH Executor Session and a directory per Ticket, in an execution environment whose variables the Executor and the Sources register | +| Channel | Feishu topic replies, GitHub comments and review thread replies | + +## Glossary + +A reference list of the terms this and the later documents use, with their Chinese names; the later documents explain the terms not introduced above. The panel shows one language at a time; its (?) button opens this glossary with both languages. The messages agents send to people on GitHub and Feishu use plain language, not these terms. + +| Term | 中文 | Meaning | +| --- | --- | --- | +| Agent | 智能体 | The automated team member dsh-superagent runs, acting on Sources through identities such as a GitHub account and a Feishu bot | +| Main Session | 主 Session | The DSH Session that evaluates Triggers, accepts and dispatches Tickets, and replies; `orchestrator` in code and prompts | +| Executor Session | 执行器 Session | The DSH Session an Executor runs for one Ticket; `worker` in code and prompts | +| Source | 来源 | A stream of events: a kind that a plugin provides, plus one instance configuration | +| Trigger | 触发 | One immutable event from a Source | +| Thread | 线程 | Triggers in time order; the unit within which Evals run one at a time | +| Correlation | 关联 | The relation between Threads that share a Trigger; it adds context only | +| Channel | 渠道 | The write side of a Thread | +| Policy | 策略 | The versioned rules for accepting work, and each Source's part of it, such as who may ask for work there | +| Policy version | 策略版本 | The identifier of one combination of rules, Source parts and accept mode; every Mandate records it | +| Eval | 评估 | The process that turns a batch of Triggers into a Decision | +| Gate | 准入 | The deterministic first layer of Eval: who may originate work, and with what strength | +| Assessment | 研判 | The model's judgement during Eval | +| Decision | 决定 | The result of Eval | +| Mandate | 授权 | What an accept grants: strength, scope and the Policy version it relied on | +| Ticket | 工单 | A unit of authorized work | +| Assignment | 派工 | The binding of a Ticket to one Executor instance | +| Subscription | 订阅 | The relation between a Ticket and a Thread | +| Fan-out | 扇出 | Delivering Triggers of a subscribed Thread to a Ticket as context | +| Executor | 执行器 | The port that runs Assignments: one Executor Session and one directory per Ticket | +| Execution environment | 执行环境 | Where Executor Sessions and the prepare command run: variables that the Executor and the Sources register, each with a key, a value and a description | +| Report | 报告 | The state an Assignment reports to its Ticket | +| Attention | 待人工决定 | A question on a Ticket that waits for a person's answer | +| Disposition | 去向 | The record of a Trigger being filtered, evaluated or fanned out | +| Input | 输入 | Context delivered to a Ticket: fanned-out Triggers, Policy changes, resumes and text | +| Scope | 范围 | The work a Mandate allows | +| Strength | 强度 | How strong the basis of a Mandate or an Eval is | + +| Group | Values | +| --- | --- | +| Decision | accept 接单 · stop 停止 · ignore 忽略 · unavailable 未完成 | +| Strength | explicit 明确 · inferred 推断 | +| Assignment | pending 待启动 · running 执行中 · ended 已结束 | +| Report | progress 进展 · waiting 等待 · ready 就绪 · blocked 受阻 · failed 失败 · dropped 无需处理 | diff --git a/docs/002-authorization.md b/docs/002-authorization.md new file mode 100644 index 0000000..287e3fe --- /dev/null +++ b/docs/002-authorization.md @@ -0,0 +1,155 @@ +# 002 Authorization + +Authorization determines who may start work and what that work may include. This document builds on [001 Domain model](001-domain.md). + +The host enforces allowed lists, rules and Mandates. Messages, comments and quoted documents are inputs to a decision; they cannot change those permissions. + +## Two paths for a Trigger + +### Eval: deciding whether to start work + +The Source records whether a message addresses the agent: `mention` for a mention of the bot or agent account, and `direct` for a direct message. The gate checks the sender and the Source's Policy: + +- An allowed person who addresses the agent makes an explicit request. +- A person's follow-up on a Thread with Tickets follows the rules below. +- Other human messages use the admission table for their receiving scope. +- A bot on the Source's bot list can produce an inferred candidate, never an explicit request. Other bots are not admitted. + +### Subscriptions: continuing existing work + +Every message on a subscribed Thread reaches its subscribed Tickets as context, including messages from bots and CI. This delivery bypasses the gate and grants no new authority. Each Ticket remains limited by its Mandate. + +A person's message on a Ticket's own Thread belongs to that Ticket's Executor to decide. The same applies when the main Session attaches a message to a Ticket. While an Executor owns the message, it takes no Eval and gets no Decision. Each Executor either handles the request within its Ticket's scope or hands it back for routing elsewhere. + +Ownership continues while the Ticket is live, even if a person removes its Subscription. It also covers an ended Ticket that was not stopped by a person and has the message waiting to resume it. + +The main Session receives a follow-up notice. While an Executor owns the message, the main Session may only stop work on the person's request; accept, attach, ignore and requeue are refused. A stop still passes through the gate with the follow-up strength described below. + +### Handing back and releasing follow-ups + +An Executor hands a message back once when it asks for work beyond the Ticket's scope or belongs to another Ticket. The Ticket keeps its Subscription. If an attached Thread is unrelated, the Executor hands back the messages it owns there. + +A message goes to a fresh Eval only after no Ticket still owns it. Its candidate includes the Executors' reasons. The gate judges it as an ordinary message on that Thread, and the main Session can accept it, attach it elsewhere or ignore it. Handing back a message the Ticket did not own, such as a bot message or one a person already sent to Eval, queues nothing. + +When a Ticket ends, it releases follow-ups its Executor did not read or take up. These include messages never delivered to the Executor and messages delivered after its last Report or turn end, provided it neither acknowledged, answered nor handed them back. Each released message goes to Eval when no other Ticket owns it. A stop ends only the stopped Ticket's work. + +A stopped Ticket does not send its stop message to Eval again unless another Ticket handed it back. The main Session that stopped it may accept the rest of the message as new work. + +A person can send an owned follow-up to Eval from the panel. If the main Session then attaches it back to a live Ticket, the Executor receives it again. The panel also offers re-evaluation when no effective judgement covers the message. + +### Context-only subscriptions + +An Executor can follow a related Thread, such as another Issue or pull request, for context. Its messages reach the Ticket but grant no authority or follow-up strength. Human messages there do not resume an ended Ticket unless the main Session attaches a message from that Thread. Notes from the main Session about other work are also context only. + +Human messages that reach no Ticket, including Threads whose Tickets were stopped by a person, take the Eval path. Bot messages take that path independently of their delivery to subscribed Tickets. + +## Strength + +| Strength | Cases | +| --- | --- | +| explicit | A person on the list addresses the agent, or follows up on a Thread that already has a Ticket; a cell of the admission table set to explicit request | +| inferred | A person not on the list follows up on a Thread with a Ticket, unless their cell of the admission table is explicit; a cell of the admission table set to inferred candidate; a message of a bot on the Source's bot list that addresses the agent or arrives where the Source receives every message | +| not admitted | Any other bot's message, everything the admission table ignores, and every inferred case while the accept mode is `off` | + +For an unlisted person, an explicit admission cell takes precedence even on a Thread with Tickets and works in every accept mode. Otherwise their follow-up is inferred and is not admitted in `off` mode. Messages on Threads followed only for context use the admission table instead of follow-up strength. + +## Admission + +Each Source's Policy has an admission table. Its rows are receiving scopes: + +- **Feishu:** each configured chat, other chats the bot receives mentions from, and direct messages. Other chats are received only when **其他群** is set to **仅 @** on the Feishu card. +- **GitHub:** new Issues, new pull requests, comments and the default branch's CI. + +A row has up to three cells. Each cell decides whether a message becomes an explicit request, an inferred candidate or nothing: + +| Cell | Choices | Default | +| --- | --- | --- | +| On the list, not addressing the agent | inferred, ignore | inferred | +| Not on the list, addressing the agent | explicit, inferred, ignore | ignore | +| Not on the list, not addressing the agent | inferred, ignore | ignore | + +Cells for unaddressed messages are available only where the Source receives every message, such as a Feishu chat set to **全部消息** or a GitHub scope with its receive-all switch enabled. A CI result has no sender, so its row offers only the last cell. Bot messages use the bot list instead of these cells. + +Setting the unlisted-addressed cell to explicit lets anyone in that scope request work by mentioning the agent. This can be useful for a dedicated support chat. Inferred candidates still depend on the accept mode. Follow-ups normally bypass this table, except that an explicit cell still grants explicit strength. + +The admission table is part of the Policy version. Weakening a cell does not immediately stop open Tickets accepted under it. It can revoke their Mandates when they are revalidated before resuming. + +### Bots and automated reviewers + +The host recognizes GitHub Apps, logins ending in `[bot]`, and Feishu bots as bots. A Source can list bots such as `dependabot[bot]` whose messages may become inferred candidates. They must either address the agent or post in a scope that receives every message. They cannot make explicit requests or stop Tickets. + +An automated reviewer using an ordinary account counts as a person to the host. Its comments reach subscribed Tickets and generate follow-up notices. Team prompt text must identify such accounts as automated reviewers. + +## Routing candidates + +The main Session routes explicit candidates with `orchestrator_route`. One call can contain several accept, attach, stop or ignore decisions. Each item is processed independently: a refused item or a missing field does not prevent the others from running. + +An inferred candidate normally needs a full assessment. There is one shortcut: if its card clearly touches no enabled rule, such as plain chat or unrelated discussion, the main Session may ignore it directly. The Decision records `ignoredFromCard` to distinguish this from a rule assessment. The main Session reads the enabled rules in its Policy prompt section. + +Accepting an inferred candidate always requires an assessment. Direct accept refuses a Trigger that the gate did not admit as explicit. + +## Accept mode and rules + +The accept mode is part of the Policy: + +| Mode | Effect | +| --- | --- | +| `off` | Nothing inferred becomes a candidate, such as an unaddressed message, a bot's message or the follow-up of a person not on the list | +| `observe` (default) | Inferred candidates are assessed; a positive assessment is recorded as ignore marked "would accept", and nothing is accepted | +| `auto` | A positive assessment of an inferred candidate accepts it | + +Explicit requests work in every mode. Start in `observe` and switch to `auto` after reviewing the recorded Decisions. Each assessment judges every enabled rule exactly once, whether it matches or not. A proactive accept requires at least one matching rule. + +Each rule describes a kind of proactive work. The Ticket's Mandate records the matched rules, and its Executor receives their names and instructions. + +For that Ticket, a rule's process and communication requirements override the defaults. A silence rule can suppress an acknowledgement; a review rule can name the required reviewer and approval before merge. Requirements that apply to every Ticket belong in team prompt sections instead. + +## Policy version + +Rules, accept mode and each Source's Policy form the Policy version. Every Mandate records the version under which it was accepted. + +A version change discards running Evals and queues their Triggers again. It also sends a policy-changed Input to each affected live Ticket. The Input explains whether: + +- a matched rule changed, including its new text, or no longer applies; +- the requester may no longer start work; or +- proactive work is no longer accepted, for example because the mode changed from `auto`. + +Unaffected Tickets receive no notice. The change does not immediately revoke live Tickets' Mandates. + +Source configuration is separate from Policy. It controls what enters the system: which chats are read and whether a scope receives all messages or only mentions. A Mandate records its Source and receiving scope. Resuming needs the Source to still listen to the Thread, but does not require it to still receive every message there. + +Replies wait while their Source is off. They are dropped if the Source no longer listens to their destination. + +A condition on what the Executor may do with its own result, such as when it may merge, does not decide who may authorize work. It is part of the agent's instructions, not of the Policy. + +## Resume and stop + +An ended Ticket keeps its Mandate. A person's message on one of its own Threads can resume the same work without a new Eval or Decision, regardless of who sent it. Bots and CI do not wake ended Tickets. The Executor decides whether the request fits the scope and ends the round if it does not. + +Only three operations change a Mandate: accept creates it, a person's stop withdraws it, and failed revalidation revokes it. A Ticket stopped by a person stays ended and receives nothing more. Work requested there needs a new accept. + +### Revalidation before resume + +The Source must still listen to the original Thread. The remaining checks depend on the Mandate's basis: + +- An explicit follow-up Mandate passes. +- An inferred Mandate needs `auto` mode and its matched rules still enabled. +- A Mandate admitted by a table cell must still receive at least its original strength under today's table and sender membership. Removing a sender from the list can therefore revoke a Mandate accepted through the listed-sender cell. +- A bot's Mandate requires that bot to remain on the Source's bot list. Inferred table and bot Mandates also need `auto` mode and enabled matched rules. +- Other Mandates require the sender to remain on the Source's allowed list. + +If revalidation fails, the Ticket stays ended and takes none of its waiting messages. Each message is treated as having reached no Ticket; if no other Ticket owns it, it goes to Eval. The main Session is not told it reached a worker. This also applies after a processing pause. Attaching to the Ticket is refused, and further work requires a new accept. + +- **A person saying "stop" in a Thread is an ordinary Trigger,** and Eval decides stop. An Executor cannot declare that a person asked it to stop. +- **Stop in the panel is a command.** It acts on the Ticket directly and is recorded as a manual stop. +- **Waiting conditions belong to Sources.** A condition such as "no assignee for N hours" would be a Source that waits and checks again itself; Eval never waits. + +## When Eval does not finish + +The time limit starts when the candidate reaches the main Session. Time spent waiting for delivery does not count, and an undelivered candidate never expires. + +An expired Eval queues its Triggers for another attempt. After two consecutive retries, the third expiry records `unavailable`. An assessment failure also records `unavailable`; a later Trigger can start a new Eval. A Policy change or a decision made against an outdated Policy records `unavailable` and immediately queues the batch under the current version. + +The `evals` health entry reports unresolved unavailable Decisions from the last hour. `scripts/dsh-superagent status` includes that error. + +The panel asks for human attention only when the unavailable Decision is less than 24 hours old, is still the latest Decision on its Thread, has not been dismissed, and the Thread is neither queued nor evaluating. A person can evaluate the Trigger again or dismiss the problem. diff --git a/docs/003-execution.md b/docs/003-execution.md new file mode 100644 index 0000000..6cb33a8 --- /dev/null +++ b/docs/003-execution.md @@ -0,0 +1,203 @@ +# 003 Execution + +This document follows an accepted Ticket through execution, delivery and recovery. It builds on [001 Domain model](001-domain.md) and [002 Authorization](002-authorization.md). + +## Ticket stages + +Each open Ticket has one stage, determined by what it is waiting for. Read the table from top to bottom; the first matching row wins. + +| Stage | Condition | Panel label | +| --- | --- | --- | +| person / delivery | Delivering an Input to the Executor Session failed; it is retried after 2 seconds, then after a delay that doubles up to a minute, and a success, a new Assignment or the Ticket's end clears it | Input undelivered | +| person / answer | It has an Attention | Awaiting answer | +| person / review | The latest Report is `ready` | Awaiting review | +| person / blocked, failed | The latest Report is `blocked` or `failed`, and no person's message reached the Ticket since | Blocked · Failed | +| working / queued | No Assignment, or a pending one | Queued | +| idle | The latest Report is `waiting`, or the Assignment ended | Waiting for a Trigger | +| working | Everything else | Working | + +A `person` stage means the Ticket cannot continue without human input. A new human message releases a `blocked` or `failed` Ticket to its Executor, so it moves to the next matching stage, usually `working`. + +An `idle` Ticket has finished its turn without an outstanding question. A Trigger on a subscribed Thread can continue it. + +## The Executor + +Each Ticket gets an Executor Session named `worker-` and a directory named after the Ticket's short name. Directories are created under the configured execution root, which defaults to `/work`. + +Executor Sessions run commands without approval as the operator's OS user. Their `dsh-superagent-worker` preset contains the tools needed for work and moves foreground shell commands to background jobs after 5 seconds. This lets a worker receive new messages at the next step instead of waiting for a long command to finish. + +The main Session uses `dsh-superagent-router`, with the same 5-second shell and job-wait limits and a smaller tool set for routing. Sessions retain the preset selected at creation. Worker subagents use DeepSeek Harness's `standard` preset. + +### Background work and worker slots + +When a turn ends with shell jobs or subagents still running, the Ticket keeps its worker slot and stays working. It is not reported as waiting, and the main Session receives no waiting report. A completed job or subagent wakes the Executor with its result in the same slot. Once a turn ends with no background work, the Executor reports waiting normally. + +After each turn, background work has at most 30 minutes to finish. At that limit, shell jobs are killed and the Ticket is reported waiting. The report includes the Executor's last words and which jobs were killed or subagents remain active. Subagents are allowed to continue. The Executor reads killed-job results on its next turn. + +A handoff to a person frees the slot immediately and leaves the Report in effect. This applies after a `ready` Report or a question, unless another Input arrived since, and after any `blocked` or `failed` Report. + +At handoff, subagents stop. Shell jobs, such as a server offered for review, may continue until the Assignment ends. They hold no slot and are exempt from the 30-minute limit, including after later turns. Their completion does not wake the Executor; it reads the results on its next turn. + +A person can also start a turn by writing directly in the Executor Session. Its background work follows the same rules. + +When an Assignment ends, its shell jobs are killed without waking the Session. Its subagents are released, and any completion turns they wake are dropped. The replacement Assignment and its waiting Inputs start after that shutdown finishes. + +Stopping or restarting the plugin inside the DeepSeek Harness process uses the same shutdown path. Inputs not yet taken into Session history are retained. Shutdown waits up to 10 seconds for each worker's background work; a process restart ends that work outright. An Assignment interrupted while busy restarts with a recovery note. + +Worker subagents, including nested ones, use DeepSeek Harness's `standard` preset with the worker's permissions. They retain only the worker's allowed work tools, and lose those tools when the Ticket ends. Under `standard`, a shell command moves to the background after 60 seconds. A subagent's jobs are killed when it ends. + +### Preparing a Ticket directory + +Before the first Assignment, if the Ticket directory does not exist, the prepare command runs once with `sh` in the execution root. It has ten minutes to finish. Without a prepare command, the worker starts in an empty directory. + +A failed command fails the Ticket and records its output. A retry or a person's message on one of the Ticket's Threads tries preparation again. A typical command uses `git worktree` to create a checkout. A pull request belongs to the Ticket when its branch's last path segment matches the Ticket's short name. + +### Concurrency + +A Ticket holds a worker slot from the start of a launch, including preparation, or from the delivery that wakes its idle Executor. It also holds the slot throughout an active turn. + +The configured concurrency limit applies to these slots, with one extra slot for a person's message or a status request. Such a wake can use the extra slot when normal slots are full, and takes the next free slot before a new launch. New work therefore cannot prevent a person from continuing an existing Ticket. + +Other Inputs, such as CI results, wait for a normal slot. A launch waits in `working / queued` until a slot is free. Launches run concurrently, so a slow prepare command delays only its own Ticket. + +### Execution variables + +The prepare command and Executor instructions receive the same execution variables. The command reads environment variables; the Executor reads their values and descriptions in its prompt. None contains credentials. + +| Variable | Registered by | Value | +| --- | --- | --- | +| `DSH_SUPERAGENT_DIR`, `DSH_SUPERAGENT_NAME`, `DSH_SUPERAGENT_TICKET` | Executor | The Ticket's directory, short name and id | +| `DSH_SUPERAGENT_BLOCKED_COMMANDS` | Executor | Commands the Executor may not run | +| `DSH_SUPERAGENT_GITHUB_REPOSITORY` | GitHub | The repository the Source watches, `owner/name` | +| `DSH_SUPERAGENT_GITHUB_ACCOUNT`, `DSH_SUPERAGENT_GITHUB_COMMIT_EMAIL` | GitHub | The agent account and its commit email | +| `DSH_SUPERAGENT_GITHUB_NUMBER`, `DSH_SUPERAGENT_GITHUB_PR_HEAD` | GitHub | The Issue or pull request that started the Ticket, and its head ref when it is a pull request | +| `DSH_SUPERAGENT_FEISHU_BOT`, `DSH_SUPERAGENT_FEISHU_CHAT` | Feishu | The bot, and the chat that started the Ticket | + +Ticket-specific values come from its directory and originating Trigger. Other values come from the current deployment settings. Only the panel can change execution environment settings; text sent to the main Session cannot change the configured shell command. + +Workers reach GitHub through the agent account's `gh` and Feishu through the bot's `lark-cli`. Blocked commands fail when invoked by name. + +These wrappers and directories establish defaults, not a security boundary. A worker can still execute a program by absolute path. + +## Merging + +Auto-merge is governed by the **Delivery requirements** prompt. The host has no merge-specific decision logic. + +By default, the Executor never enables auto-merge as the agent account: GitHub attributes the merge to whoever enabled it. Delivery requirements must name another account's CLI for merging. Without one, people merge the pull request. + +The default instructions allow auto-merge only for a pull request opened for the Ticket, when it is ready, the required approvals cover the head commit, and additions plus deletions total fewer than 200 lines. Before pushing another revision, the Executor withdraws auto-merge. + +Reviews without a body, such as approvals, also arrive as Triggers and can wake an Executor waiting for review. These instructions govern the agent, not GitHub's merge rules. To enforce approvals, require them in branch protection and dismiss stale approvals. Omit the merge CLI or remove the auto-merge paragraph to require manual merging. + +## How a Ticket ends + +- **Ready without a pull request:** a `ready` Report ends the Ticket as completed. +- **Nothing to do:** an Executor that finds no work for a person to act on, such as a request that does not reproduce or is superseded, reports `dropped`. The Ticket ends without a message, unless a matched rule asks for one, and a person's later message on its Threads resumes it. +### Pull requests + +A Ticket follows the pull request it started from once its Executor reports it. It also follows each pull request opened from its branch once reported by the Executor or found by the GitHub Source. Reopened pull requests are followed again. + +The Ticket is done when none of the pull requests it opened remain open. If it opened none, all pull requests it follows must be closed. It then ends with the outcome of the last relevant close and keeps its Subscriptions, allowing a later human comment to resume it. + +A close does not end the Ticket while resumed, retried or recovered work is unfinished. It also waits if the Executor acknowledged or answered a follow-up it owns but has not since made a non-progress Report or ended a turn after reading that message. A later `ready` Report ends the Ticket once the same pull-request completion rule is satisfied. + +Chat Channels receive one final notice about the pull-request outcome. GitHub already displays the outcome on the pull request itself. Removing a Ticket's Subscription to a pull request also stops it following that pull request. + +"Awaiting review" therefore means a `ready` Report with a pull request. + +### Follow-ups and context Threads + +The Executor decides human follow-ups on its Ticket's own Threads. It handles in-scope requests and hands back requests beyond its scope or for another Ticket. Once no Executor owns the message, it becomes a candidate for the main Session. The main Session may otherwise act on the follow-up only to stop work at the person's request. + +An Input counts as read after the Session takes it into a step and subsequently reports or ends a turn. When a Ticket ends, unread messages that were not acknowledged, answered or handed back return to Eval, as described in [002 Authorization](002-authorization.md). Read messages left without action, such as thanks, do not. + +Threads followed only for context still deliver messages, but do not give the Ticket ownership of human follow-ups or resume ended Tickets. Those messages can be assessed independently. + +Sources poll subscribed Threads while a live Ticket subscribes, and for two weeks after the later of the last Trigger and the subscribing Tickets' end. Afterwards, messages arrive only through ordinary intake: GitHub notifications or Feishu live events. + +## Failure and recovery + +### Executor blocked or failed + +When an Executor Session reports `blocked` or `failed`, or fails to start or errors, which records `failed`, the Ticket takes no automatic Input. + +A passing failure of the model provider or the network, an HTTP 408, 429 or 5xx or a refused, reset or timed-out connection, is tried again first, three times with a growing backoff: a launch runs again, and a worker whose turn failed is told to go on; a failed prepare command is not tried again. + +Once failed, CI and bot messages are recorded on it, but they are not delivered and do not wake the Session. + +Its tools stay available, so a person can continue in the Session directly. + +A launch that failed is not tried again on its own. + +A person's message, a later Report that is neither `blocked` nor `failed`, or a retry ends the hold. + +It is worked out from the stored Reports and Inputs, so a restart keeps it. +### Retry + +Available while an open Ticket is in `person / delivery`, `person / blocked` or `person / failed`: a new Assignment, which starts its worker again and takes the Inputs that wait, is the remedy for each. + +Once a person's message reached a blocked or failed Ticket, the Executor has the message, so the Ticket shows as working, or as queued when a failed launch starts again, and offers no retry, unless delivering that message failed. + +A retry ends the current Assignment and starts a new one in the same Executor Session, directory, branch and pull request. + +The Executor model applies only when a Session is created; an existing Session keeps its model. +### Restart + +Running Assignments end with "host restarted" and new ones start with a recovery note that tells the Executor to check its directory, git history and GitHub before repeating any outside action, oldest Ticket first. + +Assignments that are waiting, ready, blocked or failed stay as they are, unless an Input reached them after that Report, in which case they start again too. + +Sessions, directories, Subscriptions, pending messages and the stored work below survive a restart, and so does the record of which messages each Executor Session took into its history, which a step does once it claimed them and before its model call: a message taken by a turn the restart cut off counts as read once a Report or the end of a turn follows, while one a step claimed but a cancel or error cut off before the history counts as unread. + +A retry or a restart gives the new Assignment again the messages delivered to the Assignment it replaces that its Executor Session did not take, which a cancel or a cut-off step may have dropped; a copy still waiting in the Session, kept by a restart or persisted across a crash, is taken out before the new Assignment's first message, so the new delivery is the only copy. +### Stored work + +What dsh-superagent does after a decision, such as relaying a candidate, a follow-up or a report to the main Session, launching an Assignment or delivering an Input, is stored first and done afterwards. + +A relay or a delivery that fails, such as when its Session cannot be reached, is retried after 2 seconds, then after a delay that doubles up to a minute; a launch that fails fails its Ticket, unless the failure was a passing one, as above. + +Each kind of work reports an error among the overview's system problems only after one item failed three times in a row; a failed Input delivery shows on its Ticket instead. + +Relays to the main Session keep their order, and so do notices; a failed one holds back the later ones of its kind. +### Source failure + +The GitHub Source reads each Issue, pull request and Thread on its own: a failed read keeps its error on its Thread, is read again on the next poll, and counts towards an error of the Source, which it reports after three failed polls in a row by default; any successful poll resets the count. + +GitHub answers 404 also when the agent lost access to a private repository, so a 404 is retried. + +A notified Issue or pull request whose reads keep failing is dropped a day after its first failed read, and at once when GitHub reports it as deleted; it then no longer fails the poll, its Thread keeps the error, and the Source's state names it. + +Its read position stays where it was, so a later notification or a poll of its Thread reads what the failed reads missed. + +The Feishu event consumer reports every failure, and polling of subscribed topics keeps each failure on its Thread. + +A malformed item in a GitHub list or a Feishu page is skipped, and the Source's state counts it. +### Channel failure + +Outgoing messages are stored and sent as stored work, retried with backoff from 5 seconds, doubling up to 300. + +A Ticket's messages to one conversation go out in the order they were queued, so its acknowledgement comes before its later answer or result: a message waits while an earlier one of the same Ticket to the same conversation has not gone out, until that one has failed three times in a row and shows as a problem. + +Messages of other Tickets, and messages of no Ticket, do not wait for it. + +A message with failed attempts is a system problem until it goes out or is withdrawn. + +A message is dropped only when its Source no longer listens to the conversation, or when the Source answers that the conversation can never receive it; while its Source is off, it waits. + +The Sources answer that for a deleted Issue or a locked conversation on GitHub, and for a recalled message or one Feishu no longer finds on Feishu. + +A reply to a review comment that no longer exists goes to the pull request's conversation instead. + +A dropped message keeps its record, with when and why it was dropped and the Ticket it was for, so it is never queued again; a dropped acknowledgement reads as dropped, not sent. +### Pause processing + +Stops dispatch, new Evals, Eval timeouts, Input delivery and replies; a launch already under way waits until processing resumes. + +Sources keep recording Triggers, and a running Executor turn is not interrupted. + +A person's message to an ended Ticket waits: the first pass after the pause resumes the Ticket, if its Mandate holds under the Policy then. + +An Eval in progress does not expire during a pause; its deadline moves later by the pause's length. +### Invalid configuration + +A stored Source configuration that its kind rejects disables that Source instance. diff --git a/docs/004-prompts.md b/docs/004-prompts.md new file mode 100644 index 0000000..d53639a --- /dev/null +++ b/docs/004-prompts.md @@ -0,0 +1,96 @@ +# 004 Prompts + +This document explains what the agents read, what a team can edit, and when changes take effect. It builds on [000 Philosophy](000-philosophy.md) through [003 Execution](003-execution.md). Prompt text is written in English. + +## What a team writes + +A team can edit three team sections, add skills, and override every built-in system-prompt part. Most defaults live under `prompts/` and `skills/`. Tool descriptions, notices and cards remain in code and cannot be edited through prompt settings. + +| Section | Holds | +| --- | --- | +| Communication | When to message people, how messages read, the line that begins every GitHub message (`This is by an automated agent, not a human` by default), how to handle reviews, and which ordinary accounts are review bots | +| Ticket scope | When a Ticket goes on and when it stops with a draft | +| Delivery requirements | Evidence in pull requests, the auto-merge condition with its line threshold, and the commit author; without one, the Executor commits as the agent account's no-reply address | +| Team skills | Knowledge the agents load when they need it, such as an `identities` skill that says which Feishu user is which GitHub account | + +The built-in text covers the roles, tool usage, host mechanics, authorization rules, and the GitHub and Feishu skills. It refers to the team sections by their headings instead of repeating them. + +### Built-in parts and templates + +Overrides use these keys: + +| Parts | Keys | +| --- | --- | +| Shared instructions and roles | `runtime/shared`, `runtime/orchestrator`, `runtime/worker` | +| Working instructions | `runtime/subscriptions`, `runtime/replies`, `runtime/dispatch`, `runtime/work`, `runtime/admission` | +| Handoff instructions | `runtime/handoff/orchestrator`, `runtime/handoff/worker` | +| Source skills | `skill/feishu`, `skill/github` | +| Generated sections | `runtime/environment`, `runtime/policy`, `runtime/policy/none` | + +The generated sections are editable templates. They must retain these placeholders: + +- `runtime/environment`: `{variables}`, filled with execution environment lines. +- `runtime/policy`: `{version}`, `{mode}` and `{rules}`, with one line per enabled rule. +- `runtime/policy/none`: `{version}` and `{mode}`, used when no rule is enabled. + +Editing a part changes the instructions the model receives. It does not change the gate, Mandates, tool checks or account wrappers, which remain enforced by code. + +On an explicit human request, the main Session can also save overrides with `orchestrator_prompts_save`. If a save omits an override's `base`, it keeps the saved base. A removed part already present in the saved configuration is dropped on save; any other unknown part is refused. + +An override records which built-in text it was based on. Upgrades preserve the team's text. If the default changes, the panel marks the part **Default updated** and shows the difference beside the team's text. The team can keep its text, use the new default or edit it. Parts without overrides automatically follow new defaults. + +The main Session can read affected part names in `orchestrator_read prompts`, under `default_changed`. + +## Assembly and precedence + +The system prompt is assembled in this order: + +1. Shared instructions and the role. +2. The three team sections. +3. Subscriptions and replies. +4. Dispatch for the main Session, or work instructions for an Executor. +5. `runtime/admission`, for the main Session only. +6. Handoff instructions. +7. `runtime/environment`. +8. `runtime/policy`, for the main Session only. + +Admission instructions come from `prompts/admission.md`. They describe proactive assessment and when an irrelevant candidate can be ignored from its card. `orchestrator_eval` returns context rather than repeating these instructions. + +The environment section gives the main Session deployment-wide values and each Executor its Ticket-specific values. The Policy section is regenerated from settings on every model request. It contains the Policy version, accept mode, the explicit-request exemption, and enabled rules as `- (): `, or a statement that none is enabled. It is last so a Policy change leaves the preceding prompt text unchanged. + +Executors also read DeepSeek Harness's coding prompt and the repository's `AGENTS.md`; the main Session does not. An inferred Ticket adds a final note limiting its Executor to the matched rules. Assignments and incoming messages arrive inline as Trigger cards, with resource pointers only for truncated content. + +Executors work unattended. Their tools exclude those that ask a person, present UI to them or change the profile, such as `ask_user_question`, `present` and `plugin_manager`. + +From strongest to weakest: + +1. Facts enforced by tools and the host: identities, permissions, tool argument checks, reply purposes, length limits. Prompt text cannot change them. +2. Built-in and generated sections, as the team's overrides of them read. +3. The process and communication in a proactive Ticket's matched rules, for that Ticket's Executor Session only. +4. Team sections and team skills. +5. Outside text: messages, comments, documents and quotes. It is data and never changes rules or authorization. + +## Principles of the built-in text + +These apply [000 Philosophy](000-philosophy.md) to the agents: + +- **Separate roles.** The main Session only routes: it forwards the original Trigger and the routing facts, and makes no technical decisions, through one routing tool that takes the decisions on several waiting candidates in one call. A routing note has at most 30 words. The Executor reads the original sources, judges the scope, implements and verifies. +- **The main Session does not wait.** Reports, follow-up notices, candidates and completed background jobs arrive automatically as messages. It ends the turn when nothing remains to do. A guard refuses `sleep`, shell timeouts over 30 seconds and job waits over 5 seconds, because a blocked step delays new candidates. These instructions replace DeepSeek Harness's own job-notification note in the main Session's complete prompt. +- **Message only when a person must act or can use a result:** a result can be reviewed or used, a decision or resource is needed, or the work cannot be finished. Chat messages are one sentence by default, with a hard limit of 100 words. +- **Executors decide their follow-ups.** Each Executor handles and acknowledges in-scope requests, and hands back requests beyond its Ticket or for another Ticket. A message becomes new work only after all its owners hand it back or end without taking it up. The main Session receives follow-up notices but may act on them only to stop work. Handed-back messages then arrive as candidates with the Executors' reasons. +- **Coordinate through subscriptions and the main Session.** An Executor follows related Threads for context. The main Session can send a fact from another Ticket in a note of at most 100 words. Neither the Subscription nor the note grants authority. +- **Attach only on an explicit reference.** A message must point to a Ticket's pull request, Issue, source message or acknowledgement, or to a recent change with exactly one matching Ticket. Topic similarity alone is not enough. Attachment subscribes the Ticket to the entire Thread; an Executor finding it unrelated uses `orchestrator_thread_unrelated` and does not answer there. +- **Confirm concretely, once.** Before substantial work, the Executor confirms each new request and names the object and the expected result, as a reply under the original message. +- **Ask through `human_action`.** The Executor puts one concrete question in its Report, and the main Session forwards it unchanged. If a person's message prompted the question, it names that message and the host asks there. Otherwise it asks in the Ticket's original conversation. Already authorized work can continue meanwhile. +- **Honest delivery.** Opening a pull request is not readiness; a draft is marked unfinished; a UI change without a real before screenshot is not ready. +- **A person's pause holds** across follow-ups, new Executor Sessions and restarts until a person lifts it. Automatic feedback never resumes paused work. +- **Examples only show format.** An example sentence is not a fact the agent may claim. +- **Handle reviews by outcome.** A fixed and verified comment is resolved silently; a declined one gets one sentence of reason. An automated reviewer's thread is resolved once it has an answer, and each automated reviewer gets at most three rounds per pull request. +- **Yield to duplicate work.** When a person's branch or pull request already contains the Ticket's change, the Executor closes its own pull request with one sentence pointing to theirs. + +## Versions + +- Each save creates an immutable version containing the full configuration, revision number, predecessor, timestamp and origin (panel or tool). Restoring a version or defaults creates another version. +- Saves name their base revision. A conflicting save is refused without discarding the draft. +- Sessions read the latest version on every model request, including later steps in the same turn. Saving does not restart, cancel or wake a Session. The host records the revisions each Session used; an Executor can read any revision it worked under. +- The deployment sets a total size limit, including overrides. Validation refuses unknown keys, reserved built-in skill names, unknown override parts and missing template placeholders. It never silently truncates text. An override identical to the default is removed so the part follows future defaults. diff --git a/docs/005-panel.md b/docs/005-panel.md new file mode 100644 index 0000000..cffcac8 --- /dev/null +++ b/docs/005-panel.md @@ -0,0 +1,58 @@ +# 005 Panel + +Open **Superagent** in the DeepSeek Harness Web sidebar to supervise the agent. The panel prioritizes work that needs a person, then system health and history. This document builds on [000 Philosophy](000-philosophy.md) through [004 Prompts](004-prompts.md). + +## Three questions + +Opening the panel answers three questions, in this order: + +1. Does anything need me now? +2. Is the system healthy and doing the right things? +3. What is the history of one piece of work, from Trigger through Eval to Ticket? + +## Tabs + +The tabs follow the workflow: **Overview**, **Source**, **Trigger**, **Eval**, **Execution**, and **Settings**. Until repository and account setup succeeds, Overview shows what is missing and links to the relevant setting. + +| Tab | Shows | +| --- | --- | +| Overview | Tickets needing action, system problems, activity counts for the last 24 hours, and Executor load. Each block links to its detailed page | +| Source | Source state, setup problems and non-zero counts. Filtered Triggers and dropped messages cover the same 24-hour window as Overview | +| Trigger | Threads filtered by Subscribed, Evaluating, New Triggers in 24 h, or All Threads. Each Thread shows its Triggers newest first, Decisions and Subscriptions | +| Eval | Decisions with their reason and matched rules | +| Execution | Executor state, Pause processing, and Tickets filtered by Open, Pending, Working, Waiting for a Trigger or Ended. Ticket details show actions, authorization, waiting Inputs, Subscriptions and Assignments | +| Settings | Sources, Eval, Execution, Prompts and Deployment. Each group includes its save history | + +### Reading individual records + +A dropped message links to its Ticket, or to its Thread when it has no Ticket. The Source page explains whether its conversation disappeared or the Source stopped listening to it. + +A human follow-up still owned by an Executor is marked **Delivered to Ticket**, without an Eval. The Thread also shows hand-back reasons and context-only Subscriptions. **Evaluate again** can send that follow-up to Eval, or retry a Trigger with no effective judgement: one filtered by its Source, an unavailable Eval, or a delivered message with neither a non-stop Decision nor an Executor acknowledgement. + +Ticket details show the originating Trigger, Decision and reason, scope and Policy version. Assignments and secondary details are folded below the immediate actions. **Retry** is available for failed Input delivery or a Ticket waiting for a person after a blocked or failed Report. Ended Tickets show outcomes such as **Pull request merged** or **Stopped by a person**. + +## Settings groups + +- **Sources:** one card per Source with its switch, fields and verified account facts. GitHub also offers receive-all switches for new Issues, pull requests, comments and default-branch CI. Polling and read limits are under Advanced. +- **Eval:** the Policy version and rules, followed by **On each Source** and the Eval settings. Each Source tab contains its allowed list, membership sync, admission table and bot list. Sync runs at startup and on **Sync now**. The admission table distinguishes receiving scope, sender membership and whether the message addresses the agent. Eval settings select the accept mode, timeout and assessment model. +- **Execution:** worker model, concurrency, directory root, prepare command and blocked commands. Clicking an available variable inserts it into the prepare command. +- **Prompts:** **Edit** changes team sections, built-in parts and skills. **Full prompt** shows the assembled text and compares it with saved text or defaults. **History** lists versions. Edited built-in parts are marked changed and can be reset; templates list their required placeholders. + + An upgrade that changes an overridden default marks it **Default updated** (默认内容已更新). The panel compares the old and new defaults above the team's text. If the old default was not recorded, it compares the team's text with the new default instead. Choose **Keep my text** or **Use the new default**. Overrides for removed parts are listed below the tab bar and dropped on the next save. +- **Deployment:** startup problems with **Check again**, the state directory and machine limits. Saved limits apply without restart. The DeepSeek Harness Plugin Manager exposes the same page. + +## Search + +The header search finds Triggers by text, id, Feishu message id or a stored link, such as a GitHub comment URL. A Feishu link missing from the original message becomes searchable only after the host reads it during the current process lifetime. + +Search also finds Threads by references such as `#12` or a GitHub URL, and Tickets by title or a matching Thread. + +## Principles + +1. **Focus on what matters.** Every element answers one of the three questions, or it is removed. A number appears only when it changes what the supervisor does. +2. **Precise definitions.** Categories have checkable definitions, are mutually exclusive and cover every case, as the Ticket stages do. A label says whom a row waits for and what it needs, not an internal status code. +3. **One place per concept.** Source state belongs in Source; Threads and Triggers in Trigger; Decisions in Eval; Tickets in Execution. Settings are grouped by the concept they configure. Overview summarizes these pages and links to them instead of becoming another detailed view. +4. **Domain structure.** The panel uses the domain terms. Internal identifiers appear only in folded details, and names replace identifiers where they can be resolved. +5. **Honest state.** A count equals the length of the list it links to, and no state contradicts another fact. +6. **Quiet by default.** Healthy states take no extra space. Problems must be actionable; a one-off network error is not enough. Rows needing a person offer their action directly. Keep page explanations to one sentence and put further detail in the (?) tooltip. Destructive actions require confirmation. +7. **Consistent controls.** Use DeepSeek Harness design tokens and control behavior. The same information should look the same throughout the panel. Each settings block has one save bar, visible only while changes exist, a save runs or a save fails. diff --git a/docs/006-deployment.md b/docs/006-deployment.md new file mode 100644 index 0000000..d4e810f --- /dev/null +++ b/docs/006-deployment.md @@ -0,0 +1,274 @@ +# 006 Deployment + +Use this guide to install, configure and operate dsh-superagent. It covers accounts, startup checks, upgrades and troubleshooting, building on [001 Domain model](001-domain.md) through [005 Panel](005-panel.md). + +## What runs + +- **A dedicated DeepSeek Harness profile**, called `superagent` in this guide. Create it from the Web profile template. It loads the bundle and serves its panel on `127.0.0.1`. Keep it separate from the profile you work in: the plugin changes process-wide `PATH` and git settings, and its process needs independent restarts and upgrades. +- **An agent GitHub account.** The agent comments, pushes branches and opens pull requests as this account, through a GitHub CLI signed in only as that account. +- **Optional:** a Feishu bot for chat requests. + +Plugin configuration holds the state directory and machine limits. Use the panel for everything else: Sources for the repository and accounts, Policy for allowed lists, and Execution for worker directories and commands. + +### Permissions and isolation + +The bundle defines `dsh-superagent-worker` for Executors and `dsh-superagent-router` for the main Session. They appear in the DeepSeek Harness preset list but are intended only for dsh-superagent. Do not select them for personal Sessions or disable their configuration rows. The application needs them to create and resume its Sessions. + +Opening a blank managed Session in the Web UI can select the profile's default preset. dsh-superagent restores its role-specific preset before the first turn. + +Both presets run with `danger-full-access` as the operator's OS user and read messages from subscribed Threads. Account wrappers choose the agent's credentials by default; they do not isolate the process. A worker can execute programs by path and read the operator's home directory. For isolation, use a dedicated OS account containing only the agent's credentials. + +## Prerequisites + +- Node.js 22.19+ or 24+, pnpm, git 2.32+, Python 3.8+ and tmux +- GitHub CLI (`gh`) 2.48 or later. Source CLI fields must point to `gh` or `lark-cli` wrappers; the host relies on their subcommands and output formats. +- A repository on github.com; GitHub Enterprise Server is not supported. To create a checkout per Ticket, keep a local clone from which the prepare command can add worktrees. Install `git-lfs` if that repository uses Git LFS. +- A model provider configured for DeepSeek Harness. dsh-superagent uses the profile's default model unless the Eval or Execution settings name another. +- For Feishu: `lark-cli` (tested with 1.0.90) and a Feishu or Lark app with a bot + +## Accounts + +### Agent GitHub account + +1. Create a GitHub user account for the agent and give it write access to the repository. A GitHub App cannot be the agent, because dsh-superagent reads the account's notifications. +2. Give it a GitHub CLI that uses only its own credentials: + + ```sh + mkdir -p ~/.local/bin + cat > ~/.local/bin/gh-agent <<'EOF' + #!/bin/sh + GH_CONFIG_DIR="$HOME/.config/gh-agent" exec gh "$@" + EOF + chmod +x ~/.local/bin/gh-agent + gh-agent auth login + gh-agent api user --jq .login + ``` + + Sign in as the agent account; the last command prints its login. dsh-superagent reads the login the same way. + +### Feishu bot (optional) + +1. Create a self-built app with the bot capability. Grant it the permissions to receive message events, read and send chat messages, and read chat members, then publish the app version. +2. Give the bot its own `lark-cli` configuration, restricted to the bot identity, and a wrapper: + + ```sh + LARKSUITE_CLI_CONFIG_DIR="$HOME/.lark-cli-dsh-superagent" lark-cli config init --app-id --app-secret-stdin + LARKSUITE_CLI_CONFIG_DIR="$HOME/.lark-cli-dsh-superagent" lark-cli config strict-mode bot --global + cat > ~/.local/bin/lark-bot <<'EOF' + #!/bin/sh + export LARKSUITE_CLI_CONFIG_DIR="$HOME/.lark-cli-dsh-superagent" + exec lark-cli "$@" + EOF + chmod +x ~/.local/bin/lark-bot + ``` + + The first command reads the app secret from standard input. Use a separate configuration directory: `lark-cli` allows one profile per app id, and your personal configuration may already contain a user login for that app. + + The host refuses a CLI that can act as a user. It reads the app id, Feishu/Lark brand and bot `open_id` from the bot configuration. + + Both configurations share the keychain entry for the app secret. To remove only the bot configuration, delete its directory; do not run `lark-cli config remove`, which would affect the shared secret. + +## Install + +Run this block once, on a machine that has no `superagent` profile yet: + +```sh +npm install -g @deepseek-ai/dsh@0.2.1-rc.3 +git clone dsh-superagent && cd dsh-superagent +pnpm install && pnpm build +dsh superagent --from-default-profile web --dump-config > /dev/null +dsh plugin --profile superagent add "link:$PWD" +``` + +The pinned version above is public; other versions must satisfy `engines.dsh` in `package.json`. + +The fourth command creates `~/.dsh/profiles/superagent` from the DeepSeek Harness Web template. The fifth installs this bundle. Set `DSH_HOME` to relocate the default `~/.dsh` directory. + +Run the profile-creation command only for a new installation. If the profile already exists, that command replaces it and deletes its `cordis.patch.yml`, including machine configuration. Do not use it for upgrades. + +## Machine configuration + +Copy [examples/profile.cordis.patch.yml](../examples/profile.cordis.patch.yml) to `~/.dsh/profiles/superagent/cordis.patch.yml`, replacing that file, and replace every ``. For Feishu, also enable the `dsh-superagent-feishu` entry there. The `webserver` entry sets the panel's port. + +| Key | Required | Meaning | +| --- | --- | --- | +| `stateDir` | yes | Absolute path: the database, the lock, the command shims in `worker-bin`, the agent's git configuration and, by default, the Ticket directories in `work`. Back it up before upgrades | +| `promptMaxChars`, `admissionContextMaxPages`, `admissionContextTimeoutMs` | no | The largest saved prompt configuration, and how many Thread pages one assessment reads within what time | + +After the first start, edit machine limits in **Settings → Deployment** or the DeepSeek Harness Plugin Manager. Changes apply without restart. `stateDir` cannot be changed there. Invalid values or unknown configuration keys prevent startup; the log identifies the key. + +## Start and stop + +```sh +scripts/dsh-superagent start +scripts/dsh-superagent status +``` + +`start` runs `dsh --profile superagent` in tmux session `dsh-superagent`, or `dsh-` for another profile. It waits up to one minute for the runtime to become ready or report a setup requirement. On a first run it prints the missing setup information. It refuses duplicate instances, including ones started outside this script. + +`stop` and `restart` request graceful shutdown and wait up to five minutes for Executors. They never force-kill the process or touch other tmux sessions. + +`status` prints process ids, health and setup problems. For a different profile or port, use `--profile` and `--url`, or set `DSH_SUPERAGENT_PROFILE` and `DSH_SUPERAGENT_URL`. + +`status`, `start` and `restart` exit with one of four codes: + +| Code | Meaning | +| --- | --- | +| 0 | The service runs normally | +| 1 | It does not run, runs twice, does not answer, or its runtime reports an error; a restart may fix it | +| 2 | It runs, but a part other than the runtime, such as a Source or a Policy sync (`sync:`), reports an error; a restart does not fix it | +| 3 | It runs and waits for setup; a restart does not fix it | + +A Source that is still starting counts as normal operation. So does a Feishu bot that setup could not check. + +`status` returns failed parts and their details under `errors`, and setup requirements under `setup`. `start` and `restart` print those problems as lines; they exit 1 if the service never comes up. Only an intentional `stop` pauses automatic recovery. + +Each profile stores logs and control files under `.artifacts/`: + +- Startup logs: `dsh-superagent---