zhangcun1995521/deepwork

deepwork

Enterprise ontology and governance layer for DeepSeek Harness (DSH)

待分类

DeepWork

An enterprise agent has to know what a project is, who owns it, which customer it belongs to, and what it is allowed to touch. DeepWork is where an enterprise writes that down — and where an agent's tools and permissions are derived from it, and enforced.

It is deliberately not part of an agent. The world it describes belongs to the enterprise, so the second agent, the third, and the one after that read the same one.

DeepWork is a platform built on DeepSeek Harness (DSH). DSH ships a complete agent harness; DeepWork adds the one thing it does not ship — the enterprise's own world, and the rules for acting in it. A plugin, not a fork, and a new domain costs YAML rather than code.

What the governance is, and what it is not

The rules are enforced against declarations, not against behaviour. A capability says what its tool touches — which argument is a path it writes, which program it starts — and the guard judges every call against that plus the domain's grants, before it runs and in the audit trail afterwards. Nothing verifies the declaration against what the tool actually does. A capability that described itself dishonestly would be trusted, and a tool that reaches the world through its own code rather than through the shell it declared is outside the door entirely.

So the guarantee here is advisory: a capability that declares honestly is governed, refused when it must be, and recorded every time — and reviewing that capability is what makes its declaration true. What this repository does not claim is that nothing can exceed its authority. Claiming that needs a boundary the current composition cannot express: one where the only way to touch the world is a channel the guard can see, so that "undeclared" and "impossible" become the same thing. That is the direction this project is built to move in, and it is not where it is today. The architecture document says which branches pass without judging anything, so a reader can check the claim against the code.

The whole project, as one line

Every part of this repository is one step of the same journey: a world is declared, an agent is equipped from it, and what that agent does is governed on the way out.

   the enterprise's world                       what the agent gets
   ──────────────────────                       ────────────────────
①  declared    objects, relations, states,  →   a domain is data: `schema.yaml` and the
               who may write what                instances beside it

②  derived     read tools, the write tool,  →   a capability is declared, not coded —
               declared actions and queries      nothing in TypeScript to add or remove

③  authorised  `written_by` per type,       →   the host's own file and shell tools are
               roles per deployment              covered too; no roles means read-only

④  gated       states, the moves between    →   a move needing a second pair of eyes is
               them, and what each needs         refused without a verdict from somebody
                                                 other than the person making the move

⑤  recorded    the audit trail and write    →   every attempt, refusals included; field
               provenance                        names and never field values

⑥  checked     `npm run validate`           →   a declaration that did not take effect is
                                                 a failure, not a silence

Read it top to bottom and this repository is one thing. Read the sections below out of order and it is a list of mechanisms.

The same line as a picture:

flowchart TB
  subgraph assembly["assembly"]
    prof["a profile: which plugins, which roles, which domain, which mode — changing the deployment changes only this layer"]
  end

  subgraph data["a domain — data, not code"]
    schema["schema.yaml — objects · relations · functions · states and transitions · who may write · actions · queries · situation · skill"]
    inst["the instances beside it"]
  end

  subgraph dw["DeepWork — a plugin, not a fork"]
    eng["the engine — reads the domain, builds the graph, derives the edges"]
    der["derivation — the read tools and query modes, the write tool, declared actions, the lifecycle's requirements"]
    gov["governance — the guard mounted globally in front of every call, permissions derived from written_by, review checked against provenance, an audit of field names and never field values"]
    inj["injection — the rules the situation marks, in the prompt and in the catalogue"]
    cap["a capability — a second plugin, declaring what its tools touch"]
  end

  subgraph hosted["the host — DeepSeek Harness"]
    loop["the agent loop · the tool registry · the skill and prompt registries · the session"]
    own["its own file and shell tools"]
  end

  prof --> dw
  schema --> eng
  inst --> eng
  eng --> der
  eng --> gov
  eng --> inj
  der -->|"the tool surface an agent is given"| loop
  inj -->|"the rules block, and the catalogue"| loop
  gov -->|"judges each call before it runs"| loop
  cap -->|"declares, so it is judged too"| gov
  gov -->|"or the host's own tools are the way around it"| own
  loop -->|"a write that was allowed"| wrote["the instance file, and one structured audit entry"]
  gov --> refused["a refusal, and one structured audit entry naming the rule that refused"]

Two places in that picture answer questions people expect one place to answer, so they are worth naming. The guard is the door: before any tool runs, may this call touch this path at all. The write path is the desk: may this content be written, by this caller, in this state. A capability's own tools stop at the door; this plugin's write tool passes the door unjudged and is settled at the desk, because nothing declares ontology_write and it is not in the built-in table — so its authority lives inside the tool rather than in the mounted guard.

docs/architecture.md is the same route written out — the layers, one write end to end, and the limits named; and the same document in Chinese is there for a reader who works in Chinese.

Who this is for

Anyone putting an agent into a real business system. A forward-deployed engineer standing up the first use case; a platform team that has to answer "what is this agent allowed to touch, and who approved it"; the team that inherits it six months later.

If you only look at one thing

packages/ontology/examples/engagement is the whole line at small scale: four stages, three of them gated on a signature scoped to that stage, one declared action, one relation.

corepack pnpm install            # once, right after cloning — nothing else is needed
npm run validate                  # is it sound? exit 0 or a list of what did not take effect
node packages/ontology/scripts/show-derivation.ts packages/ontology/examples/engagement

The second command prints what came out of the declaration — and note what it prints with no roles configured: three read tools, no write tool, and no actions derived, because an unconfigured deployment is a read-only one.

Then packages/ontology/src/examples.test.ts runs it for real — a stage cannot be entered without its signature, a signature for one stage does not open the next, and the role that makes a move cannot sign for it. Those three sentences are the project, and they are a test rather than a paragraph.

The gap this fills

DSH ships a complete agent harness — loop, tools, skills, workflows, plan mode, approval, subagents, sandboxing. What it does not ship is any notion of the enterprise world the agent is working in. A general agent sees document_001, project_782, user_23891. An enterprise agent needs to know what a project is, who owns it, which customer it belongs to, and what it is allowed to touch.

That gap is semantic, not mechanical. This project fills it, and nothing else.

There is a third layer between the model and the harness, and this project is it. A model reasons; a harness executes; neither knows what the enterprise is. That layer has to be declared, and that word is doing the work. A context assembled per request out of whatever a search surfaced is a guess about relevance — a good guess, and still a guess, and different on Tuesday. A context declared as objects, relations and rules is a fact the enterprise can be asked about, diffed, and held to, and the same question asked twice answers the same way.

It also has to distinguish what somebody vouched for from what merely got recorded. A layer that cannot tell a rule a person signed off on from a note an agent wrote yesterday has made its own trustworthiness a second thing nobody can check — so that distinction is a lifecycle here, and only the vouched-for half ever reaches an agent.

DSH provides                    DeepWork adds
──────────────                  ──────────────
agent loop                      ontology    entities, relations, declared queries
tools                           governance  written_by, file_location, required_fields
skills                          injection   the vouched-for rules, marked for the situation in hand
                                audit       every write attempt, refusals included
workflow
plan mode
human review
sandbox

The claim

A domain author writes YAML. The agent gets tools. Nobody writes TypeScript.

functions:
  open_issues_of:
    description: Issues still open against a given project.
    use: traverse
    relation: reported_on
    direction: incoming
    by: project_id
    where: { status: open }

Those eight lines produce a callable mode, with its argument typed from the field declaration it names. Adding a type makes it queryable. Deleting a function removes the mode. Neither touches the plugin.

Governance works the same way. written_by on a type is the policy; there is no per-tool check anyone has to remember to add. With no roles configured the write tool is not registered at all, so an unconfigured deployment is a read-only one rather than a permissive one.

Ten seconds, no runtime

The engine depends on nothing from the host, so a domain can be watched deriving its tools without installing one — no runtime, no API key, no agent. The same domain, declared twice: with nobody authorised to write, then with one role declared.

The same domain, declared twice

The picture is generated from the command below, not drawn, so it cannot drift from the output (node packages/ontology/scripts/render-demo-svg.ts).

corepack pnpm install            # once, right after cloning
node packages/ontology/scripts/show-derivation.ts packages/ontology/examples/project-delivery
domain   packages/ontology/examples/project-delivery
         5 object types, 16 instances, 6 relations (22 edges)

tools    4 read, 0 write
         ontology_get
         ontology_find
         ontology_links
         ontology_query
         no write tool — with no roles configured, the deployment is read-only

objects
         Customer     key=customer_id    instances=2   may write: delivery-lead
         Project      key=project_id     instances=3   may write: delivery-lead, system
         Person       key=employee_id    instances=3   may write: delivery-lead
         Issue        key=issue_id       instances=5   may write: delivery-lead, engineer
         Document     key=document_id    instances=3   may write: delivery-lead

Say who may write and the write tool appears, along with a tool per declared action. Declare a lifecycle and the states arrive with the rules that govern them — who may move something, and which moves need somebody else's verdict:

node packages/ontology/scripts/show-derivation.ts packages/ontology/tests/fixtures/review-delivery   --roles '[{"prefix":"session-","role":"engineer"}]'
tools    3 read, 1 write
         ontology_get
         ontology_find
         ontology_links
         ontology_write   (registered because roles are configured)

objects
         Deliverable  key=deliverable_id instances=1   may write: engineer
                      states: draft → signed_off
                      sign_off: draft → signed_off   by: engineer   needs a Signoff (decision=signed)
         Signoff      key=signoff_id     instances=1   may write: reviewer

That second run is a governance model in fifteen lines, and not one of them was written by hand: it is the domain, read back. The enforcement behind those lines is described under Governance and checked by 393 tests.

What it looks like when an agent uses it

A real session, with the query modes the agent chose and the answer it reached — including two details that only a declared world can produce: it refused to invent a project name the schema does not declare, and it drew a conclusion that needed the graph rather than a lookup.

docs/agent-session.md — and the same domain asked in Chinese, where the agent reached the same four declared queries

Status

Working and demonstrated, not finished.

MilestoneState
Plugin route — a third-party package installs and mounts in DSH✅ verified end to end
Ontology engine — schema parsing, instance loading, relation derivation✅ covered by the suite
Derived tool surface — reads✅ verified with real agent turns
Derived governance — writes, permissions, audit✅ verified with a real refusal and a real write
Write guard — every tool call, not only ours✅ mounted globally; a real refusal and a real shell-redirect refusal observed, both audited
Declared lifecycle — states, transitions, who may move them✅ enforced on the write path; a real refusal and a real promotion observed, both audited
Reads default to the vouched-for state✅ and say how many they left out, rather than narrowing quietly
Declared counters — who may record usage✅ granted apart from written_by; verified live by a role that may count and may not author
Declared actions — a named way to write, not only the generic tool✅ one tool per declaration; verified live, preconditions and role narrowing included
Declared review — a move that needs an independent verdict✅ enforced against provenance the platform recorded; a promotion went through on another session's verdict and was refused without one
Declared value sets — a field's values as a closed set, not a hint✅ a value outside it is refused with value-not-declared, through the generic write tool and through a declared action
Declared-domain validation — an exit code a pipeline can read✅ a sound domain passes, a malformed one fails naming the declaration that was dropped
Example domains — five of them, one mechanism each✅ end to end; the fifth describes the systems outside the deployment, and is the first to declare a tool_grants entry
Capability layer — a second plugin that adds an ability✅ three packages: a project's tests, a language server, and the lineage around a session; two declare what they touch, and the third is a read that declares nothing
Mode role — who a mode's agents are, and the prompt they work under✅ its own package; it registers no tool, and the role it publishes is the one filter the rules block applies
Architecture — the route written down✅ docs/architecture.md, with each limit named beside the file that states it
Workspace UI✅ a page inside DSH's own interface, not a second server
Published to npmprepared, not published

393 tests in total.

The engine and every derivation are testable without a runtime, an API key, or an agent, because the package depends on nothing from DSH. That is why the test suite runs in under a second.

What is deliberately not here

Scope is a design decision, so what this project refuses is worth naming as plainly as what it does. Each of these was considered and declined, not overlooked.

  • Not a second UI. The panel is a page inside DSH's own interface. An earlier standalone workspace was retired the moment that turned out to be unnecessary — it had become a second place to stand, reimplementing one that was already there.
  • Not a memory layer, a retriever, or an agent framework. The host already has session history, workspace instructions, skills and subagents. The mechanism it lacks is knowing what the enterprise's objects are; a second one of anything above would be a second source of truth.
  • Not a fork, and not a wrapper around one: a plugin, tracking upstream releases.
  • Not credential storage. A domain may point at a secret; the value stays in the host's credential seam and never enters the domain, the audit trail, or anything a model reads.
  • Not an integration runtime. A domain declares the systems outside it — what each one is for, who depends on it, how this deployment reaches it, which pitfalls that path is known for — and which tool may touch one. Making the call is a capability's work, and the capability is deliberately thin: packages/external runs a program the deployment configured and reports what it did. There is no client library per vendor and no protocol implemented here, so the repository still holds no knowledge of anybody's API — it holds the mechanism, the grant and the trail.
  • Not the working record. The decision log, the verified facts and the environment pitfalls are kept locally and are not published. It is written as notes, in the language of the work, which means it keeps collecting names and paths that do not belong in public — sanitising it was a losing game, and the file that said so was the file that broke the rule. What a reader loses is the sight of a working process; what it stops is the leak that process produces.

And the honest half — what a reader will find missing:

MissingWhere it stands
One artefact showing a task start to finish — a recording, not a descriptionsessions are recorded as documents and refusals as audit entries, but there is no single thing a stranger can watch
deepwork init — a domain to copy and renamethe six examples are the starter material
Validation on several real domainsone production domain has been through it — 17 object types, 269 instances, 214 relations — and that run is what put the two-level diagnostics in. One is not several
A connector for any particular systemthe mechanism is in packages/external — one tool that runs a program the deployment configures — so what is missing is not the call but the other half of a connector: no client per vendor, no protocol per product, and no vendor is named anywhere in this repository

Quick start

git clone <this repo> && cd deepwork
corepack pnpm install
mkdir -p dist
cd packages/ontology && corepack pnpm run build && npm pack --pack-destination ../../dist && cd ../..

# point a DSH profile at the example domain
export DSH_HOME="$(pwd)/.dsh-home"
./node_modules/.bin/dsh --profile headless --dump-config > /dev/null
./node_modules/.bin/dsh plugin --profile headless add ./dist/deepwork-ontology-0.1.0.tgz

export DEEPWORK_ONTOLOGY_ROOT="$(pwd)/packages/ontology/examples/project-delivery"
./node_modules/.bin/dsh headless "Analyse the risk on project P-2026-001 and give me a management briefing."

To allow writes, say who may write. The role map is the gate:

export DEEPWORK_ONTOLOGY_ROLES='[{"prefix":"session-","role":"delivery-lead"}]'

A rule whose prefix is * matches every caller, and is how a deployment states the role it acts as instead of inferring one from ids it does not generate. Prefer it unless one process hosts several kinds of agent: an id-shaped rule is an assumption about the host's naming, and when the host renames its agents the rule stops matching and every write is refused — which reads as a permissions problem rather than as the stale assumption it is. * is one character, so any real prefix still beats it and a mixed deployment can name its agents individually.

Seeing what a domain derives

The panel lives inside DSH's own interface rather than beside it: one process, one origin, no second server to run and no RPC layer. Install the plugin into a profile, open DSH's web UI, and the Ontology row in the sidebar shows what the engine derived from the domain — the rules a type declares and who may make each move, the declared actions, the tool definitions with the JSON Schema of every argument, the object types with who may write them, and the relations with the field each one reads.

Put a role map in the roles box and the write tool appears in the listing; leave it empty and the page explains why it does not. That is the governance model answering a question out loud, from the running system, instead of from a document.

The panel answers from the domain the deployment loaded, and only that one: no path arrives from the browser, so a request cannot name a domain at all. Reading some other domain would mean starting another process with DEEPWORK_ONTOLOGY_ROOT pointing at it.

Checking a domain

The panel answers "what did this domain derive". npm run validate answers "did any of it fail to happen", which is the question that matters before a domain is deployed:

npm run validate
npm run validate -- packages/ontology/tests/fixtures/broken

Exit 0 means every declaration took effect. Exit 1 lists what did not, each with the name of the thing that was dropped — an action pointing at an undeclared type, a field the type does not have, a function whose mode is unavailable. Pass --role prefix=role to make the write and action halves reachable; without it the command says so rather than reporting a shorter list as a clean one, because with no roles nothing derives there and "declared equals derived" has nothing to compare.

A schema file that cannot be read at all is the one failure that stops before there is a domain to check, so it is reported as a failing domain rather than as a diagnostic.

Problems and notes

A report has two levels, and the line between them is who has to act.

Problems need somebody to act, and they fail the run: a declaration that cannot take effect (an action pointing at a type that does not exist, a field the type does not have, a transition naming a state that was never declared), or an instance file that could not be read. Notes are facts about the data that need nobody to act, and never fail: a declared directory with nothing in it yet, a relation no instance has wired, an instance whose identity fell back to its filename. Notes are still counted and printed — "nothing to fix" must not read as "nothing to see", since an empty query result is exactly what those notes explain.

The line was drawn by real data, not by taste. A production domain — 17 object types, 269 instances, 214 relations — loaded and answered correctly and produced nine unwired relations and three empty directories. Judged as failures, that report had nothing green in it, and a check that is always red is a check nobody reads. Split, the same run says what it should: four things the data has not grown into yet, and no defects at all.

Because the first version of that command did not split them, and because the fixture domains it was built against were small, new, and written by the same hand, nothing caught it until a real domain was put through. packages/ontology/tests/fixtures/ahead-of-its-data now pins the policy.

What is in here

PathWhat it is
`packages/ontol