Skip to content

feat(core): F15 organization context — shared company record + per-datasource ontology (ACE-067–070)#143

Merged
ashwin-agami merged 2 commits into
mainfrom
feat/ace-067-org-context
Jul 26, 2026
Merged

feat(core): F15 organization context — shared company record + per-datasource ontology (ACE-067–070)#143
ashwin-agami merged 2 commits into
mainfrom
feat/ace-067-org-context

Conversation

@vishalkalbi27

Copy link
Copy Markdown
Collaborator

What & why

A deployment can connect several databases under one company. Today org == profile == datasource are fused 1:1:1, so company-wide facts (org_id, name, description, fiscal year, glossary) get duplicated into every per-profile org.yaml and drift. F15 gives the deployment one shared company record, written once, while each datasource keeps its own vocabulary — so Account resolves to a customer in the CRM and a GL account in the ERP, and a federated question carries the same company context once plus both vocabularies. Stays single-tenant (N=1); a different company is a different folder.

Companion to F14 (portable org_id, #141): F14 gave identity, F15 gives the content + relocates the id into the record it should have lived in.

Specs

Spec Slice
ACE-067 OrgRecord/DisplayConventions types; new org_record.py (ensure/load_org_record over <artifacts_dir>/organization.yaml); relocate org_id into the record (mint-once, immutable, idempotent legacy lift) and delete F14's adopt-sibling scan; resolved_org_id reads the record.
ACE-068 013_organization.sql — the one table keyed on org_id alone; write/load_organization_record wired once-per-run into model_deploy.
ACE-069 Two-level compose_org_context (company block once + per-datasource ontology), wired into both the local CLI (org-context) and the served path (get_datasource_schema); byte-identical graceful degradation when no record exists; content-routing rule documented.
ACE-070 agami-connect onboarding UX — author company context once, attach subsequent databases under the org, route a different company to a new folder.

Key design decision — shared organization table with agami-hosted

The Postgres table is deliberately named organization and is a column superset of agami-hosted's tenant organization table (org_name + created_at present, doc DEFAULT '{}'), so one physical table serves both repos. Two consequences, both load-bearing:

  • The write is an FK-safe UPSERT (ON CONFLICT DO UPDATE), not the clear-then-insert used elsewhere in model_store.py — because in the hosted stack org_membership.org_id and license.org_id foreign-key this row, so a DELETE on redeploy would crash under PRAGMA foreign_keys=ON.
  • Hosted's existing INSERT (org_id, org_name, created_at) keeps working untouched (the doc DEFAULT fills the gap; COALESCE preserves hosted-owned columns on upsert).

Scope is agami-core only — no agami-hosted changes are required for correctness now.

Backward compatibility & safety

  • Pre-F15 deployments load and compose unchanged (degradation returns byte-identical pre-F15 output).
  • No data egress — organization.yaml is local file I/O; the privacy gate stays green.
  • organization.yaml is written with default perms (not chmod 600) — it's a deploy-read model file, and mode-600 model files trip the known deploy container crash-loop.

Verification

  • 1552 tests pass (uv run dev.py check: ruff + tests + gitleaks, all green), incl. the 8 F14 regression tests.
  • 97% patch coverage on the diff.
  • The crux test: a re-write_organization_record survives an FK-referencing org_membership row under PRAGMA foreign_keys=ON (a DELETE-based write would fail there), plus a hosted-coexistence test.

🤖 Generated with Claude Code

…tasource ontology (ACE-067–070)

A deployment can connect several databases under ONE company. Today org == profile ==
datasource are fused 1:1:1, so company-wide facts (org_id, name, description, fiscal year,
glossary) get duplicated into every per-profile org.yaml and drift. F15 gives the deployment
one shared company record, written once, while each datasource keeps its own vocabulary — so
"Account" resolves to a customer in the CRM and a GL account in the ERP, and a federated
question carries the same company context once plus both vocabularies. Stays single-tenant (N=1).

- ACE-067: OrgRecord/DisplayConventions types; new org_record.py (ensure/load_org_record over
  <artifacts_dir>/organization.yaml); relocate org_id into the record (mint-once, immutable,
  idempotent legacy lift) and delete F14's adopt-sibling scan; resolved_org_id reads the record.
- ACE-068: 013_organization.sql (the one table keyed on org_id alone); write/load_organization_record
  wired once-per-run into model_deploy. The write is an FK-SAFE UPSERT (ON CONFLICT DO UPDATE), not
  the clear-then-insert used elsewhere, and the table is a COLUMN SUPERSET of agami-hosted's tenant
  `organization` (org_name + created_at present, doc DEFAULT '{}') so one physical table serves both
  and a redeploy can't violate the hosted org_membership/license foreign keys.
- ACE-069: two-level compose_org_context (company block once + per-datasource ontology), wired into
  both the local CLI (org-context) and the served path (get_datasource_schema); byte-identical
  graceful degradation when no record exists; content-routing rule documented.
- ACE-070: agami-connect onboarding UX — author company context once, attach subsequent databases
  under the org, and route a different company to a new folder.

Scope: agami-core only. Backward-compatible — pre-F15 deployments load and compose unchanged. No
data egress (organization.yaml is local file I/O; the privacy gate stays green). 1552 tests pass,
including the FK-safety crux (a re-upsert survives an FK-referencing membership row under
PRAGMA foreign_keys=ON) and hosted coexistence.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements F15 “organization context” by introducing a deployment-level company record (shared across datasources) alongside per-datasource ontologies, wiring the two-level context into both the CLI (org-context) and the served schema path (get_datasource_schema), and persisting the company record into a new organization table.

Changes:

  • Add a deployment-level OrgRecord (<artifacts_dir>/organization.yaml + root ORGANIZATION.md) and relocate org_id resolution to prefer the record (with legacy fallbacks).
  • Add migration 013_organization.sql and deploy-time upsert logic to write a single FK-safe organization row keyed by org_id only.
  • Implement two-level context composition (compose_org_context) and integrate it into both CLI output and the served schema context, with tests covering degradation/compatibility.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/test_schema.py Asserts the new organization table exists with the expected PK/columns.
tests/test_organization_context_e2e.py E2E coverage for two-level org context across CLI + served schema path.
tests/test_org_record.py Validates record mint/lift semantics and resolver precedence.
tests/test_org_record_deploy.py Validates DB upsert semantics (FK-safe) + hosted coexistence + shape.
tests/test_compose_org_context.py Unit tests for two-level composition + byte-identical degradation.
plugins/agami/skills/agami-connect/SKILL.md Updates onboarding UX to route “same vs different company” and author company context once.
plugins/agami/shared/organization-context-format.md Documents the two-level routing and composition behavior.
packages/agami-core/src/tools.py Reads + composes two-level domain context for get_datasource_schema.
packages/agami-core/src/semantic_model/org_record.py New module implementing load/ensure/write for organization.yaml.
packages/agami-core/src/semantic_model/org_draft.py Adds compose_org_context and helpers for company + datasource blocks.
packages/agami-core/src/semantic_model/models.py Introduces OrgRecord and DisplayConventions model types.
packages/agami-core/src/semantic_model/cli.py Updates org-context command to render two-level composition.
packages/agami-core/src/semantic_model/build.py Updates org-id ensure flow to use the deployment record (and supports dry-run).
packages/agami-core/src/model_store.py Adds DB write/load for the organization record with FK-safe UPSERT.
packages/agami-core/src/model_deploy.py Deploys the org record + company narrative once per run (deployment-level).
packages/agami-core/src/migrations/core/013_organization.sql Adds organization table keyed on org_id alone with hosted-compatible columns/defaults.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/agami-core/src/tools.py Outdated
Comment on lines +866 to +870
org_md_raw, user_md_raw = _domain_memory(profile)
domain_context = _OD.compose_context(org_md_raw, org)
# Two-level (F15 / ACE-069): the shared COMPANY block from the deployment record + this datasource's
# source-specific narrative + derived summary. No record ⇒ compose_org_context degrades to the exact
# pre-F15 single-level output, so a deployment without a record is unaffected.
record, company_md = _company_context(_current_org_id())
`Yes` → write to the **per-profile** `<artifacts_dir>/<profile>/ORGANIZATION.md` under `# About this database` (source-specific narrative only). `Skip` → leave it absent; Phase 2f writes a short per-database starter.

`Yes — I'll type it now (Other field)` → write their words to `<artifacts_dir>/<profile>/ORGANIZATION.md` under `# About this database`. **Their narrative ONLY** — do NOT append model facts (subject areas, metrics, glossary). Those are *derived from the model at read time* (the query path assembles them via `cli org-context`), so they never get baked into the editable prose file where a human could clobber them. `Skip — I'll auto-fill it from my data (Recommended)`leave ORGANIZATION.md absent for now; Phase 2f writes a short human-narrative starter. `chmod 600` whatever you write. See [`shared/organization-context-format.md`](../../shared/organization-context-format.md).
`chmod 600` whatever you write. See [`shared/organization-context-format.md`](../../shared/organization-context-format.md) for the content-routing rule (company-wideroot; source-specific → per-profile; per-column units → the structured model; personal → `USER_MEMORY.md`).
| Level | Where | Holds | Written |
|---|---|---|---|
| **Company** (the deployment) | `<artifacts_dir>/organization.yaml` (the `OrgRecord`) + `<artifacts_dir>/ORGANIZATION.md` (company narrative) | company name/description, `fiscal_year_start_month`, `display_conventions` (currency/rounding/week_start), the company-wide `glossary` — and the company narrative prose | once, at first onboarding; edited via `/agami-model` |
| **Datasource** (each profile) | `<artifacts_dir>/<profile>/org.yaml` + `<profile>/ORGANIZATION.md` | that source's ontology (`key_terminology`, subject areas, …) and a **source-specific** narrative only | per profile |
@sandeep-agami

Copy link
Copy Markdown
Collaborator

@ashwin-agami - please review as well.


Free-form Markdown describing **what this database is about** in the human's own words: the company / product, what the data represents, who the users are. One file per profile at `<artifacts_dir>/<profile>/ORGANIZATION.md` — context is database-specific.

## Two levels: the company record vs the per-datasource narrative (F15)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Internal project related details don't need to go into the skill.

- **Re-introspect `<profile>`** — refresh the structure from the live DB (new/changed tables, columns, FKs) while preserving descriptions, entities, metrics, caveats, and sign-offs (the `reintrospect` path).
- **Open model explorer** — browse + curate the existing model and review/sign off the trust layer (`/agami-model`).
- **Onboard another database** — set up a **different** database (a different connection) under a **new** profile, leaving `<profile>` untouched. On this choice, **start a fresh onboarding for a new profile**: jump to the profile-naming step (Phase 0a's naming question) → have the user name the new profile (must differ from `<profile>` and any existing `[section]` in `<artifacts_dir>/local/credentials`) and pick its DB type → write that profile's `credentials.example` → run the full flow for it. Never reuse or overwrite the current profile's credentials or model.
- **Onboard another database** — set up a **different** database (a different connection), leaving `<profile>` untouched. On this choice, **first ask whether it's the same company or a different one** (F15 — see 1.1a), then start a fresh onboarding: jump to the profile-naming step (Phase 0a's naming question) → have the user name the new profile (must differ from `<profile>` and any existing `[section]` in `<artifacts_dir>/local/credentials`) and pick its DB type → write that profile's `credentials.example` → run the full flow for it. Never reuse or overwrite the current profile's credentials or model.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove F15 and similar references from the PR

… + review fixes

Follow-up to the F15 organization-context work, addressing testing feedback and PR review:

- Populate the root record: onboarding now writes the company name + description via a new
  `sm set-org` CLI command (set_org_fields), and the seeded connect todo list includes a
  mandatory organization-context step so the company question is actually asked (it was being
  skipped because it wasn't in the plan).
- Auto-maintained `datasources` list on OrgRecord: rebuilt from the on-disk profile dirs on every
  onboard (build.write_tree) and deploy (model_deploy), carried into the Postgres organization
  row's doc. Never hand-edited, so it can't drift.
- Keep per-profile ORGANIZATION.md as the datasource narrative (restored the per-database
  question every onboard); the root organization.yaml is purely additive.

Review fixes (PR #143):
- Served context path opens ONE DB connection instead of two: _domain_memory + _company_context
  collapsed into a single _context_sources() that reads per-datasource memory, the org record, and
  the company narrative on one Store (hot tool path).
- chmod 644, not 600, on ORGANIZATION.md narrative files — non-secret model files that must stay
  readable in deploy bundles / for teammates (600 causes the deploy container crash-loop). Secrets
  (local/, credentials, .config) keep 600.
- Fully-rooted <artifacts_dir>/<profile>/ORGANIZATION.md paths in the shared doc.
- Scrubbed remaining internal project references from the plugin skills/shared docs (instructions only).

Verified: full gate green (ruff + tests + gitleaks), 99% patch coverage. Second-datasource scenario
verified — same org_id, name/description preserved, datasources list grows to include both.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ashwin-agami
ashwin-agami merged commit d7ae4ae into main Jul 26, 2026
6 checks passed
@ashwin-agami
ashwin-agami deleted the feat/ace-067-org-context branch July 26, 2026 06:40
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 26, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants